/* ** Copyright (c) 2008 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains an interface between the TH scripting language ** (an independent project) and fossil. */ #include "config.h" #include "th_main.h" #include "sqlite3.h" #if INTERFACE /* ** Flag parameters to the Th_FossilInit() routine used to control the ** interpreter creation and initialization process. */ #define TH_INIT_NONE ((u32)0x00000000) /* No flags. */ #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */ #define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/ /* output. */ #define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */ /* ** Useful and/or "well-known" combinations of flag values. */ #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ #define TH_INIT_HOOK (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP) #define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */ #endif /* ** Flags set by functions in this file to keep track of integration state ** information. These flags should not be used outside of this file. */ #define TH_STATE_CONFIG ((u32)0x00000200) /* We opened the config. */ #define TH_STATE_REPOSITORY ((u32)0x00000400) /* We opened the repository. */ #define TH_STATE_MASK ((u32)0x00000600) /* All possible state flags. */ #ifdef FOSSIL_ENABLE_TH1_HOOKS /* ** 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" #endif /* ** These macros are used within this file to detect if the repository and ** configuration ("user") database are currently open. */ #define Th_IsRepositoryOpen() (g.repositoryOpen) #define Th_IsConfigOpen() (g.zConfigDbName!=0) /* ** When memory debugging is enabled, use our custom memory allocator. */ #if defined(TH_MEMDEBUG) /* ** 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. */ static int nOutstandingMalloc = 0; /* ** Implementations of malloc() and free() to pass to the interpreter. */ static void *xMalloc(unsigned int n){ void *p = fossil_malloc(n); if( p ){ nOutstandingMalloc++; } return p; } static void xFree(void *p){ if( p ){ nOutstandingMalloc--; } free(p); } static Th_Vtab vtab = { xMalloc, xFree }; /* ** Returns the number of outstanding TH1 memory allocations. */ int Th_GetOutstandingMalloc(){ return nOutstandingMalloc; } #endif /* ** Generate a TH1 trace message if debugging is enabled. */ void Th_Trace(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); blob_vappendf(&g.thLog, zFormat, ap); va_end(ap); } /* ** Forces input and output to be done via the CGI subsystem. */ void Th_ForceCgi(int fullHttpReply){ g.httpOut = stdout; g.httpIn = stdin; fossil_binary_mode(g.httpOut); fossil_binary_mode(g.httpIn); g.cgiOutput = 1; g.fullHttpReply = fullHttpReply; } /* ** Checks if the TH1 trace log needs to be enabled. If so, prepares ** it for use. */ void Th_InitTraceLog(){ g.thTrace = find_option("th-trace", 0, 0)!=0; if( g.thTrace ){ g.fAnyTrace = 1; blob_zero(&g.thLog); } } /* ** Prints the entire contents of the TH1 trace log to the standard ** output channel. */ void Th_PrintTraceLog(){ if( g.thTrace ){ fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n"); fossil_print("%s", blob_str(&g.thLog)); fossil_print("\n------------------- END TRACE LOG -------------------\n"); } } /* ** - adopted from ls_cmd_rev in checkin.c ** - adopted commands/error handling for usage within th1 ** - interface adopted to allow result creation as TH1 List ** ** Takes a check-in identifier in zRev and an optiona glob pattern in zGLOB ** as parameter returns a TH list in pzList,pnList with filenames matching ** glob pattern with the checking */ static void dir_cmd_rev( Th_Interp *interp, char **pzList, int *pnList, const char *zRev, /* Revision string given */ const char *zGlob, /* Glob pattern given */ int bDetails ){ Stmt q; char *zOrderBy = "pathname COLLATE nocase"; int rid; rid = th1_name_to_typed_rid(interp, zRev, "ci"); compute_fileage(rid, zGlob); db_prepare(&q, "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" " blob.size\n" " FROM fileage, blob\n" " WHERE blob.rid=fileage.fid \n" " ORDER BY %s;", zOrderBy /*safe-for-%s*/ ); while( db_step(&q)==SQLITE_ROW ){ const char *zFile = db_column_text(&q, 1); if( bDetails ){ const char *zTime = db_column_text(&q, 0); int size = db_column_int(&q, 2); char zSize[50]; char *zSubList = 0; int nSubList = 0; sqlite3_snprintf(sizeof(zSize), zSize, "%d", size); Th_ListAppend(interp, &zSubList, &nSubList, zFile, -1); Th_ListAppend(interp, &zSubList, &nSubList, zSize, -1); Th_ListAppend(interp, &zSubList, &nSubList, zTime, -1); Th_ListAppend(interp, pzList, pnList, zSubList, -1); Th_Free(interp, zSubList); }else{ Th_ListAppend(interp, pzList, pnList, zFile, -1); } } db_finalize(&q); } /* ** TH1 command: dir CHECKIN ?GLOB? ?DETAILS? ** ** Returns a list containing all files in CHECKIN. If GLOB is given only ** the files matching the pattern GLOB within CHECKIN will be returned. ** If DETAILS is non-zero, the result will be a list-of-lists, with each ** element containing at least three elements: the file name, the file ** size (in bytes), and the file last modification time (relative to the ** time zone configured for the repository). */ static int dirCmd( Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ const char *zGlob = 0; int bDetails = 0; if( argc<2 || argc>4 ){ return Th_WrongNumArgs(interp, "dir CHECKIN ?GLOB? ?DETAILS?"); } if( argc>=3 ){ zGlob = argv[2]; } if( argc>=4 && Th_ToInt(interp, argv[3], argl[3], &bDetails) ){ return TH_ERROR; } if( Th_IsRepositoryOpen() ){ char *zList = 0; int nList = 0; dir_cmd_rev(interp, &zList, &nList, argv[1], zGlob, bDetails); Th_SetResult(interp, zList, nList); Th_Free(interp, zList); return TH_OK; }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: httpize STRING ** ** Escape all characters of STRING which have special meaning in URI ** components. Return a new string result. */ static int httpizeCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "httpize STRING"); } zOut = httpize((char*)argv[1], argl[1]); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** True if output is enabled. False if disabled. */ static int enableOutput = 1; /* ** TH1 command: enable_output BOOLEAN ** ** Enable or disable the puts, wiki, combobox and copybtn commands. */ static int enableOutputCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc; if( argc<2 || argc>3 ){ return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); } rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); if( g.thTrace ){ Th_Trace("enable_output {%.*s} -> %d
\n", argl[1],argv[1],enableOutput); } return rc; } /* ** TH1 command: enable_htmlify ?BOOLEAN? ** ** Enable or disable the HTML escaping done by all output which ** originates from TH1 (via sendText()). ** ** If passed no arguments it instead returns 0 or 1 to indicate the ** current state. */ static int enableHtmlifyCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc = 0, buul; if( argc>3 ){ return Th_WrongNumArgs(interp, "enable_htmlify [TRACE_LABEL] ?BOOLEAN?"); } buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1; Th_SetResultInt(g.interp, buul); if(argc>1){ if( g.thTrace ){ Th_Trace("enable_htmlify {%.*s} -> %d
\n", argl[1],argv[1],buul); } rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul); if(!rc){ if(buul){ g.th1Flags &= ~TH_INIT_NO_ENCODE; }else{ g.th1Flags |= TH_INIT_NO_ENCODE; } } } return rc; } /* ** Returns a name for a TH1 return code. */ const char *Th_ReturnCodeName(int rc, int nullIfOk){ static char zRc[32]; switch( rc ){ case TH_OK: return nullIfOk ? 0 : "TH_OK"; case TH_ERROR: return "TH_ERROR"; case TH_BREAK: return "TH_BREAK"; case TH_RETURN: return "TH_RETURN"; case TH_CONTINUE: return "TH_CONTINUE"; case TH_RETURN2: return "TH_RETURN2"; default: { sqlite3_snprintf(sizeof(zRc), zRc, "TH1 return code %d", rc); } } return zRc; } /* See Th_SetOutputBlob() */ static Blob * pThOut = 0; /* ** Sets the th1-internal output-redirection blob and returns the ** previous value. That blob is used by certain output-generation ** routines to emit its output. It returns the previous value so that ** a routine can temporarily replace the buffer with its own and ** restore it when it's done. */ Blob * Th_SetOutputBlob(Blob * pOut){ Blob * tmp = pThOut; pThOut = pOut; return tmp; } /* ** Send text to the appropriate output: If pOut is not NULL, it is ** appended there, else to the console or to the CGI reply buffer. ** Escape all characters with special meaning to HTML if the encode ** parameter is true, with the exception that that flag is ignored if ** g.th1Flags has the TH_INIT_NO_ENCODE flag. ** ** If pOut is NULL and the global pThOut is not then that blob ** is used for output. */ static void sendText(Blob * pOut, const char *z, int n, int encode){ if(0==pOut && pThOut!=0){ pOut = pThOut; } if(TH_INIT_NO_ENCODE & g.th1Flags){ encode = 0; } if( enableOutput && n ){ if( n<0 ) n = strlen(z); if( encode ){ z = htmlize(z, n); n = strlen(z); } if(pOut!=0){ blob_append(pOut, z, n); }else if( g.cgiOutput ){ cgi_append_content(z, n); }else{ fwrite(z, 1, n, stdout); fflush(stdout); } if( encode ) free((char*)z); } } /* ** error-reporting counterpart of sendText(). */ static void sendError(Blob * pOut, const char *z, int n, int forceCgi){ int savedEnable = enableOutput; enableOutput = 1; if( forceCgi || g.cgiOutput ){ sendText(pOut, "

", -1, 0); } sendText(pOut,"ERROR: ", -1, 0); sendText(pOut,(char*)z, n, 1); sendText(pOut,forceCgi || g.cgiOutput ? "

" : "\n", -1, 0); enableOutput = savedEnable; } /* ** Convert name to an rid. This function was copied from name_to_typed_rid() ** in name.c; however, it has been modified to report TH1 script errors instead ** of "fatal errors". */ int th1_name_to_typed_rid( Th_Interp *interp, const char *zName, const char *zType ){ int rid; if( zName==0 || zName[0]==0 ) return 0; rid = symbolic_name_to_rid(zName, zType); if( rid<0 ){ Th_SetResult(interp, "ambiguous name", -1); }else if( rid==0 ){ Th_SetResult(interp, "name not found", -1); } return rid; } /* ** Attempt to lookup the specified check-in and file name into an rid. ** This function was copied from artifact_from_ci_and_filename() in ** info.c; however, it has been modified to report TH1 script errors ** instead of "fatal errors". */ int th1_artifact_from_ci_and_filename( Th_Interp *interp, const char *zCI, const char *zFilename ){ int cirid; Blob err; Manifest *pManifest; ManifestFile *pFile; if( zCI==0 ){ Th_SetResult(interp, "invalid check-in", -1); return 0; } if( zFilename==0 ){ Th_SetResult(interp, "invalid file name", -1); return 0; } cirid = th1_name_to_typed_rid(interp, zCI, "*"); blob_zero(&err); pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err); if( pManifest==0 ){ if( blob_size(&err)>0 ){ Th_SetResult(interp, blob_str(&err), blob_size(&err)); }else{ Th_SetResult(interp, "manifest not found", -1); } blob_reset(&err); return 0; } blob_reset(&err); manifest_file_rewind(pManifest); while( (pFile = manifest_file_next(pManifest,0))!=0 ){ if( fossil_strcmp(zFilename, pFile->zName)==0 ){ int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); manifest_destroy(pManifest); return rid; } } Th_SetResult(interp, "file name not found in manifest", -1); return 0; } /* ** TH1 command: nonce ** ** Returns the value of the cryptographic nonce for the request being ** processed. */ static int nonceCmd( Th_Interp *interp, void *pConvert, int argc, const char **argv, int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "nonce"); } Th_SetResult(interp, style_nonce(), -1); return TH_OK; } /* ** TH1 command: puts STRING ** TH1 command: html STRING ** ** Output STRING escaped for HTML (puts) or unchanged (html). */ static int putsCmd( Th_Interp *interp, void *pConvert, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "puts STRING"); } sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert); return TH_OK; } /* ** TH1 command: redirect URL ?withMethod? ** ** Issues an HTTP redirect to the specified URL and then exits the process. ** By default, an HTTP status code of 302 is used. If the optional withMethod ** argument is present and non-zero, an HTTP status code of 307 is used, which ** should force the user agent to preserve the original method for the request ** (e.g. GET, POST) instead of (possibly) forcing the user agent to change the ** method to GET. */ static int redirectCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int withMethod = 0; if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "redirect URL ?withMethod?"); } if( argc==3 ){ if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ return TH_ERROR; } } if( withMethod ){ cgi_redirect_with_method(argv[1]); }else{ cgi_redirect(argv[1]); } Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */ return TH_OK; } /* ** TH1 command: insertCsrf ** ** While rendering a form, call this command to add the Anti-CSRF token ** as a hidden element of the form. */ static int insertCsrfCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "insertCsrf"); } login_insert_csrf_secret(); return TH_OK; } /* ** TH1 command: verifyCsrf ** ** Before using the results of a form, first call this command to verify ** that this Anti-CSRF token is present and is valid. If the Anti-CSRF token ** is missing or is incorrect, that indicates a cross-site scripting attack. ** If the event of an attack is detected, an error message is generated and ** all further processing is aborted. */ static int verifyCsrfCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "verifyCsrf"); } if( !cgi_csrf_safe(2) ){ fossil_fatal("possible CSRF attack"); } return TH_OK; } /* ** TH1 command: verifyLogin ** ** Returns non-zero if the specified user name and password represent a ** valid login for the repository. */ static int verifyLoginCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ const char *zUser; const char *zPass; int uid; if( argc!=3 ){ return Th_WrongNumArgs(interp, "verifyLogin userName password"); } zUser = argv[1]; zPass = argv[2]; uid = login_search_uid(&zUser, zPass); Th_SetResultInt(interp, uid!=0); if( uid==0 ) sqlite3_sleep(100); return TH_OK; } /* ** TH1 command: markdown STRING ** ** Renders the input string as markdown. The result is a two-element list. ** The first element is the text-only title string. The second element ** contains the body, rendered as HTML. */ static int markdownCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ Blob src, title, body; char *zValue = 0; int nValue = 0; if( argc!=2 ){ return Th_WrongNumArgs(interp, "markdown STRING"); } blob_zero(&src); blob_init(&src, (char*)argv[1], argl[1]); blob_zero(&title); blob_zero(&body); markdown_to_html(&src, &title, &body); Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); Th_SetResult(interp, zValue, nValue); Th_Free(interp, zValue); return TH_OK; } /* ** TH1 command: decorate STRING ** TH1 command: wiki STRING ** ** Render the input string as wiki. For the decorate command, only links ** are handled. */ static int wikiCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p; if( argc!=2 ){ return Th_WrongNumArgs(interp, "wiki STRING"); } if( enableOutput ){ Blob src; blob_init(&src, (char*)argv[1], argl[1]); wiki_convert(&src, 0, flags); blob_reset(&src); } return TH_OK; } /* ** TH1 command: htmlize STRING ** ** Escape all characters of STRING which have special meaning in HTML. ** Return a new string result. */ static int htmlizeCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "htmlize STRING"); } zOut = htmlize((char*)argv[1], argl[1]); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** TH1 command: encode64 STRING ** ** Encode the specified string using Base64 and return the result. */ static int encode64Cmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "encode64 STRING"); } zOut = encode64((char*)argv[1], argl[1]); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** TH1 command: date ** ** Return a string which is the current time and date. If the ** -local option is used, the date appears using localtime instead ** of UTC. */ static int dateCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ char *zOut; if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){ zOut = db_text("??", "SELECT datetime('now',toLocal())"); }else{ zOut = db_text("??", "SELECT datetime('now')"); } Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } /* ** TH1 command: hascap STRING... ** TH1 command: anoncap STRING... ** ** Return true if the current user (hascap) or if the anonymous user ** (anoncap) has all of the capabilities listed in STRING. */ static int hascapCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc = 1, i; char *zCapList = 0; int nCapList = 0; if( argc<2 ){ return Th_WrongNumArgs(interp, "hascap STRING ..."); } for(i=1; rc==1 && i %d
\n", argv[0], nCapList, zCapList, rc); Th_Free(interp, zCapList); } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: capexpr CAPABILITY-EXPR ** ** Nmemonic: "CAPability EXPRression" ** ** The capability expression is a list. Each term of the list is a cluster ** of capability letters. The overall expression is true if any one term ** is true. A single term is true if all letters within that term are true. ** Or, if the term begins with "!", then the term is true if none of the ** terms or true. Or, if the term begins with "@" then the term is true ** if all of the capability letters in that term are available to the ** "anonymous" user. Or, if the term is "*" then it is always true. ** ** Examples: ** ** capexpr {j o r} True if any one of j, o, or r are available ** capexpr {oh} True if both o and h are available ** capexpr {@2 @3 4 5 6} 2 or 3 available for anonymous or one of ** 4, 5 or 6 is available for the user */ int capexprCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ char **azCap; int *anCap; int nCap; int rc; int i; if( argc!=2 ){ return Th_WrongNumArgs(interp, "capexpr EXPR"); } rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap); if( rc ) return rc; rc = 0; for(i=0; i %d
\n", argl[1], argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: hasfeature STRING ** ** Return true if the fossil binary has the given compile-time feature ** enabled. The set of features includes: ** ** "ssl" = FOSSIL_ENABLE_SSL ** "legacyMvRm" = FOSSIL_ENABLE_LEGACY_MV_RM ** "execRelPaths" = FOSSIL_ENABLE_EXEC_REL_PATHS ** "th1Docs" = FOSSIL_ENABLE_TH1_DOCS ** "th1Hooks" = FOSSIL_ENABLE_TH1_HOOKS ** "tcl" = FOSSIL_ENABLE_TCL ** "useTclStubs" = USE_TCL_STUBS ** "tclStubs" = FOSSIL_ENABLE_TCL_STUBS ** "tclPrivateStubs" = FOSSIL_ENABLE_TCL_PRIVATE_STUBS ** "json" = FOSSIL_ENABLE_JSON ** "markdown" = FOSSIL_ENABLE_MARKDOWN ** "unicodeCmdLine" = !BROKEN_MINGW_CMDLINE ** "dynamicBuild" = FOSSIL_DYNAMIC_BUILD ** "mman" = USE_MMAN_H ** "see" = USE_SEE ** ** Specifying an unknown feature will return a value of false, it will not ** raise a script error. */ static int hasfeatureCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc = 0; const char *zArg; if( argc!=2 ){ return Th_WrongNumArgs(interp, "hasfeature STRING"); } zArg = (const char *)argv[1]; if(NULL==zArg){ /* placeholder for following ifdefs... */ } #if defined(FOSSIL_ENABLE_SSL) else if( 0 == fossil_strnicmp( zArg, "ssl\0", 4 ) ){ rc = 1; } #endif else if( 0 == fossil_strnicmp( zArg, "legacyMvRm\0", 11 ) ){ rc = 1; } #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS) else if( 0 == fossil_strnicmp( zArg, "execRelPaths\0", 13 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_TH1_DOCS) else if( 0 == fossil_strnicmp( zArg, "th1Docs\0", 8 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_TH1_HOOKS) else if( 0 == fossil_strnicmp( zArg, "th1Hooks\0", 9 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_TCL) else if( 0 == fossil_strnicmp( zArg, "tcl\0", 4 ) ){ rc = 1; } #endif #if defined(USE_TCL_STUBS) else if( 0 == fossil_strnicmp( zArg, "useTclStubs\0", 12 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_TCL_STUBS) else if( 0 == fossil_strnicmp( zArg, "tclStubs\0", 9 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) else if( 0 == fossil_strnicmp( zArg, "tclPrivateStubs\0", 16 ) ){ rc = 1; } #endif #if defined(FOSSIL_ENABLE_JSON) else if( 0 == fossil_strnicmp( zArg, "json\0", 5 ) ){ rc = 1; } #endif #if !defined(BROKEN_MINGW_CMDLINE) else if( 0 == fossil_strnicmp( zArg, "unicodeCmdLine\0", 15 ) ){ rc = 1; } #endif #if defined(FOSSIL_DYNAMIC_BUILD) else if( 0 == fossil_strnicmp( zArg, "dynamicBuild\0", 13 ) ){ rc = 1; } #endif #if defined(USE_MMAN_H) else if( 0 == fossil_strnicmp( zArg, "mman\0", 5 ) ){ rc = 1; } #endif #if defined(USE_SEE) else if( 0 == fossil_strnicmp( zArg, "see\0", 4 ) ){ rc = 1; } #endif else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ rc = 1; } if( g.thTrace ){ Th_Trace("[hasfeature %#h] => %d
\n", argl[1], zArg, rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: tclReady ** ** Return true if the fossil binary has the Tcl integration feature ** enabled and it is currently available for use by TH1 scripts. ** */ static int tclReadyCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc = 0; if( argc!=1 ){ return Th_WrongNumArgs(interp, "tclReady"); } #if defined(FOSSIL_ENABLE_TCL) if( g.tcl.interp ){ rc = 1; } #endif if( g.thTrace ){ Th_Trace("[tclReady] => %d
\n", rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: anycap STRING ** ** Return true if the current user user ** has any one of the capabilities listed in STRING. */ static int anycapCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc = 0; int i; if( argc!=2 ){ return Th_WrongNumArgs(interp, "anycap STRING"); } for(i=0; rc==0 && i %d
\n", argl[1], argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } /* ** TH1 command: combobox NAME TEXT-LIST NUMLINES ** ** Generate an HTML combobox. NAME is both the name of the ** CGI parameter and the name of a variable that contains the ** currently selected value. TEXT-LIST is a list of possible ** values for the combobox. NUMLINES is 1 for a true combobox. ** If NUMLINES is greater than one then the display is a listbox ** with the number of lines given. */ static int comboboxCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=4 ){ return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); } if( enableOutput ){ int height; Blob name; int nValue; const char *zValue; char *z, *zH; int nElem; int *aszElem; char **azElem; int i; if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem); blob_init(&name, (char*)argv[1], argl[1]); zValue = Th_Fetch(blob_str(&name), &nValue); zH = htmlize(blob_buffer(&name), blob_size(&name)); z = mprintf("", -1, 0); Th_Free(interp, azElem); } return TH_OK; } /* ** TH1 command: copybtn TARGETID FLIPPED TEXT ?COPYLENGTH? ** ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js ** Javascript module, and generates HTML elements with the following IDs: ** ** TARGETID: The wrapper around TEXT. ** copy-TARGETID: The for the copy button. ** ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT. ** ** The optional COPYLENGTH argument defines the length of the substring of TEXT ** copied to clipboard: ** ** <= 0: No limit (default if the argument is omitted). ** >= 3: Truncate TEXT after COPYLENGTH (single-byte) characters. ** 1: Use the "hash-digits" setting as the limit. ** 2: Use the length appropriate for URLs as the limit (defined at ** compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16). */ static int copybtnCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=4 && argc!=5 ){ return Th_WrongNumArgs(interp, "copybtn TARGETID FLIPPED TEXT ?COPYLENGTH?"); } if( enableOutput ){ int flipped = 0; int copylength = 0; char *zResult; if( Th_ToInt(interp, argv[2], argl[2], &flipped) ) return TH_ERROR; if( argc==5 ){ if( Th_ToInt(interp, argv[4], argl[4], ©length) ) return TH_ERROR; } zResult = style_copy_button( /*bOutputCGI==*/0, /*TARGETID==*/(char*)argv[1], flipped, copylength, "%h", /*TEXT==*/(char*)argv[3]); sendText(0,zResult, -1, 0); free(zResult); } return TH_OK; } /* ** TH1 command: linecount STRING MAX MIN ** ** Return one more than the number of \n characters in STRING. But ** never return less than MIN or more than MAX. */ static int linecntCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ const char *z; int size, n, i; int iMin, iMax; if( argc!=4 ){ return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); } if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; z = argv[1]; size = argl[1]; for(n=1, i=0; i=iMax ) break; } } if( niMax ) n = iMax; Th_SetResultInt(interp, n); return TH_OK; } /* ** TH1 command: repository ?BOOLEAN? ** ** Return the fully qualified file name of the open repository or an empty ** string if one is not currently open. Optionally, it will attempt to open ** the repository if the boolean argument is non-zero. */ static int repositoryCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "repository ?BOOLEAN?"); } if( argc==2 ){ int openRepository = 0; if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){ return TH_ERROR; } if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0); } Th_SetResult(interp, g.zRepositoryName, -1); return TH_OK; } /* ** TH1 command: checkout ?BOOLEAN? ** ** Return the fully qualified directory name of the current check-out or an ** empty string if it is not available. Optionally, it will attempt to find ** the current check-out, opening the configuration ("user") database and the ** repository as necessary, if the boolean argument is non-zero. */ static int checkoutCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "checkout ?BOOLEAN?"); } if( argc==2 ){ int openCheckout = 0; if( Th_ToInt(interp, argv[1], argl[1], &openCheckout) ){ return TH_ERROR; } if( openCheckout ) db_open_local(0); } Th_SetResult(interp, g.zLocalRoot, -1); return TH_OK; } /* ** TH1 command: trace STRING ** ** Generate a TH1 trace message if debugging is enabled. */ static int traceCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "trace STRING"); } if( g.thTrace ){ Th_Trace("%s", argv[1]); } Th_SetResult(interp, 0, 0); return TH_OK; } /* ** TH1 command: globalState NAME ?DEFAULT? ** ** Returns a string containing the value of the specified global state ** variable -OR- the specified default value. Currently, the supported ** items are: ** ** "checkout" = The active local check-out directory, if any. ** "configuration" = The active configuration database file name, ** if any. ** "executable" = The fully qualified executable file name. ** "flags" = The TH1 initialization flags. ** "log" = The error log file name, if any. ** "repository" = The active local repository file name, if ** any. ** "top" = The base path for the active server instance, ** if applicable. ** "user" = The active user name, if any. ** "vfs" = The SQLite VFS in use, if overridden. ** ** Attempts to query for unsupported global state variables will result ** in a script error. Additional global state variables may be exposed ** in the future. ** ** See also: checkout, repository, setting */ static int globalStateCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ const char *zDefault = 0; if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "globalState NAME ?DEFAULT?"); } if( argc==3 ){ zDefault = argv[2]; } if( fossil_strnicmp(argv[1], "checkout\0", 9)==0 ){ Th_SetResult(interp, g.zLocalRoot ? g.zLocalRoot : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "configuration\0", 14)==0 ){ Th_SetResult(interp, g.zConfigDbName ? g.zConfigDbName : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "executable\0", 11)==0 ){ Th_SetResult(interp, g.nameOfExe ? g.nameOfExe : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "flags\0", 6)==0 ){ Th_SetResultInt(interp, g.th1Flags); return TH_OK; }else if( fossil_strnicmp(argv[1], "log\0", 4)==0 ){ Th_SetResult(interp, g.zErrlog ? g.zErrlog : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "repository\0", 11)==0 ){ Th_SetResult(interp, g.zRepositoryName ? g.zRepositoryName : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "top\0", 4)==0 ){ Th_SetResult(interp, g.zTop ? g.zTop : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){ Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1); return TH_OK; }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); return TH_OK; }else{ Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]); return TH_ERROR; } } /* ** TH1 command: getParameter NAME ?DEFAULT? ** ** Return the value of the specified query parameter or the specified default ** value when there is no matching query parameter. */ static int getParameterCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ const char *zDefault = 0; if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); } if( argc==3 ){ zDefault = argv[2]; } Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1); return TH_OK; } /* ** TH1 command: setParameter NAME VALUE ** ** Sets the value of the specified query parameter. */ static int setParameterCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=3 ){ return Th_WrongNumArgs(interp, "setParameter NAME VALUE"); } cgi_replace_parameter(mprintf("%s", argv[1]), mprintf("%s", argv[2])); return TH_OK; } /* ** TH1 command: reinitialize ?FLAGS? ** ** Reinitializes the TH1 interpreter using the specified flags. */ static int reinitializeCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ u32 flags = TH_INIT_DEFAULT; if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "reinitialize ?FLAGS?"); } if( argc==2 ){ int iFlags; if( Th_ToInt(interp, argv[1], argl[1], &iFlags) ){ return TH_ERROR; }else{ flags = (u32)iFlags; } } Th_FossilInit(flags & ~TH_INIT_FORBID_MASK); Th_SetResult(interp, 0, 0); return TH_OK; } /* ** TH1 command: render STRING ** ** Renders the template and writes the results. */ static int renderCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc; if( argc!=2 ){ return Th_WrongNumArgs(interp, "render STRING"); } rc = Th_Render(argv[1]); Th_SetResult(interp, 0, 0); return rc; } /* ** TH1 command: defHeader TITLE ** ** Returns the default page header. */ static int defHeaderCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "defHeader"); } Th_SetResult(interp, get_default_header(), -1); return TH_OK; } /* ** TH1 command: styleHeader TITLE ** ** Render the configured style header for the selected skin. */ static int styleHeaderCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "styleHeader TITLE"); } if( Th_IsRepositoryOpen() ){ style_header("%s", argv[1]); Th_SetResult(interp, 0, 0); return TH_OK; }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: styleFooter ** ** Render the configured style footer for the selected skin. */ static int styleFooterCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 ){ return Th_WrongNumArgs(interp, "styleFooter"); } if( Th_IsRepositoryOpen() ){ style_finish_page(); Th_SetResult(interp, 0, 0); return TH_OK; }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: styleScript ?BUILTIN-FILENAME? ** ** Render the js.txt file from the current skin. Or, if an argument ** is supplied, render the built-in filename given. ** ** By "rendering" we mean that the script is loaded and run through ** TH1 to expand variables and process ... script. Contrast ** with the "builtin_request_js BUILTIN-FILENAME" command which just ** loads the file as-is without interpretation. */ static int styleScriptCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "styleScript ?BUILTIN_NAME?"); } if( Th_IsRepositoryOpen() ){ const char *zScript; if( argc==2 ){ zScript = (const char*)builtin_file(argv[1], 0); }else{ zScript = skin_get("js"); } if( zScript==0 ) zScript = ""; Th_Render(zScript); Th_SetResult(interp, 0, 0); return TH_OK; }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: submenu link LABEL URL ** ** Add a hyperlink to the submenu. */ static int submenuCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=4 || memcmp(argv[1],"link",5)!=0 ){ return Th_WrongNumArgs(interp, "submenu link LABEL URL"); } if( argl[2]==0 ){ Th_SetResult(interp, "link's LABEL is empty", -1); return TH_ERROR; } if( argl[3]==0 ){ Th_SetResult(interp, "link's URL is empty", -1); return TH_ERROR; } /* ** Label and URL are unescaped because it is expected that ** style_finish_page() provides propper escaping via %h format. */ style_submenu_element( fossil_strdup(argv[2]), "%s", argv[3] ); Th_SetResult(interp, 0, 0); return TH_OK; } /* ** TH1 command: builtin_request_js NAME ** ** Request that the built-in javascript file called NAME be added to the ** end of the generated page. ** ** See also: styleScript */ static int builtinRequestJsCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "builtin_request_js NAME"); } builtin_request_js(argv[1]); return TH_OK; } /* ** TH1 command: artifact ID ?FILENAME? ** ** Attempts to locate the specified artifact and return its contents. An ** error is generated if the repository is not open or the artifact cannot ** be found. */ static int artifactCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "artifact ID ?FILENAME?"); } if( Th_IsRepositoryOpen() ){ int rid; Blob content; if( argc==3 ){ rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]); }else{ rid = th1_name_to_typed_rid(interp, argv[1], "*"); } if( rid!=0 && content_get(rid, &content) ){ Th_SetResult(interp, blob_str(&content), blob_size(&content)); blob_reset(&content); return TH_OK; }else{ return TH_ERROR; } }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: cgiHeaderLine line ** ** Adds the specified line to the CGI header. */ static int cgiHeaderLineCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "cgiHeaderLine line"); } cgi_append_header(argv[1]); return TH_OK; } /* ** TH1 command: unversioned content FILENAME ** ** Attempts to locate the specified unversioned file and return its contents. ** An error is generated if the repository is not open or the unversioned file ** cannot be found. */ static int unversionedContentCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=3 ){ return Th_WrongNumArgs(interp, "unversioned content FILENAME"); } if( Th_IsRepositoryOpen() ){ Blob content; if( unversioned_content(argv[2], &content)!=0 ){ Th_SetResult(interp, blob_str(&content), blob_size(&content)); blob_reset(&content); return TH_OK; }else{ return TH_ERROR; } }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } /* ** TH1 command: unversioned list ** ** Returns a list of the names of all unversioned files held in the local ** repository. An error is generated if the repository is not open. */ static int unversionedListCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "unversioned list"); } if( Th_IsRepositoryOpen() ){ Stmt q; char *zList = 0; int nList = 0; db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL" " ORDER BY name"); while( db_step(&q)==SQLITE_ROW ){ Th_ListAppend(interp, &zList, &nList, db_column_text(&q,0), -1); } db_finalize(&q); Th_SetResult(interp, zList, nList); Th_Free(interp, zList); return TH_OK; }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } static int unversionedCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ static const Th_SubCommand aSub[] = { { "content", unversionedContentCmd }, { "list", unversionedListCmd }, { 0, 0 } }; return Th_CallSubCommand(interp, p, argc, argv, argl, aSub); } /* ** TH1 command: utime ** ** Return the number of microseconds of CPU time consumed by the current ** process in user space. */ static int utimeCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ sqlite3_uint64 x; char zUTime[50]; fossil_cpu_times(&x, 0); sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } /* ** TH1 command: stime ** ** Return the number of microseconds of CPU time consumed by the current ** process in system space. */ static int stimeCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ sqlite3_uint64 x; char zUTime[50]; fossil_cpu_times(0, &x); sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } /* ** TH1 command: randhex N ** ** Return N*2 random hexadecimal digits with N<50. If N is omitted, ** use a value of 10. */ static int randhexCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int n; unsigned char aRand[50]; unsigned char zOut[100]; if( argc!=1 && argc!=2 ){ return Th_WrongNumArgs(interp, "repository ?BOOLEAN?"); } if( argc==2 ){ if( Th_ToInt(interp, argv[1], argl[1], &n) ){ return TH_ERROR; } if( n<1 ) n = 1; if( n>(int)sizeof(aRand) ) n = sizeof(aRand); }else{ n = 10; } sqlite3_randomness(n, aRand); encode16(aRand, zOut, n); Th_SetResult(interp, (const char *)zOut, -1); return TH_OK; } /* ** Run sqlite3_step() while suppressing error messages sent to the ** rendered webpage or to the console. */ static int ignore_errors_step(sqlite3_stmt *pStmt){ int rc; g.dbIgnoreErrors++; rc = sqlite3_step(pStmt); g.dbIgnoreErrors--; return rc; } /* ** TH1 command: query [-nocomplain] SQL CODE ** ** Run the SQL query given by the SQL argument. For each row in the result ** set, run CODE. ** ** In SQL, parameters such as $var are filled in using the value of variable ** "var". Result values are stored in variables with the column name prior ** to each invocation of CODE. */ static int queryCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ sqlite3_stmt *pStmt; int rc; const char *zSql; int nSql; const char *zTail; int n, i; int res = TH_OK; int nVar; char *zErr = 0; int noComplain = 0; if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){ argc--; argv++; argl++; noComplain = 1; } if( argc!=3 ){ return Th_WrongNumArgs(interp, "query SQL CODE"); } if( g.db==0 ){ if( noComplain ) return TH_OK; Th_ErrorMessage(interp, "database is not open", 0, 0); return TH_ERROR; } zSql = argv[1]; nSql = argl[1]; while( res==TH_OK && nSql>0 ){ zErr = 0; report_restrict_sql(&zErr); g.dbIgnoreErrors++; rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail); g.dbIgnoreErrors--; report_unrestrict_sql(); if( rc!=0 || zErr!=0 ){ if( noComplain ) return TH_OK; Th_ErrorMessage(interp, "SQL error: ", zErr ? zErr : sqlite3_errmsg(g.db), -1); return TH_ERROR; } n = (int)(zTail - zSql); zSql += n; nSql -= n; if( pStmt==0 ) continue; nVar = sqlite3_bind_parameter_count(pStmt); for(i=1; i<=nVar; i++){ const char *zVar = sqlite3_bind_parameter_name(pStmt, i); int szVar = zVar ? th_strlen(zVar) : 0; if( szVar>1 && zVar[0]=='$' && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ int nVal; const char *zVal = Th_GetResult(interp, &nVal); sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT); } } while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ int nCol = sqlite3_column_count(pStmt); for(i=0; i%#h}
\n", argl[2], argv[2]); } res = Th_Eval(interp, 0, argv[2], argl[2]); if( g.thTrace ){ int nTrRes; char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); Th_Trace("[query_eval] => %h {%#h}
\n", Th_ReturnCodeName(res, 0), nTrRes, zTrRes); } if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; } rc = sqlite3_finalize(pStmt); if( rc!=SQLITE_OK ){ if( noComplain ) return TH_OK; Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1); return TH_ERROR; } } return res; } /* ** TH1 command: setting name ** ** Gets and returns the value of the specified Fossil setting. */ #define SETTING_WRONGNUMARGS "setting ?-strict? ?--? name" static int settingCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc; int strict = 0; int nArg = 1; char *zValue; if( argc<2 || argc>4 ){ return Th_WrongNumArgs(interp, SETTING_WRONGNUMARGS); } if( fossil_strcmp(argv[nArg], "-strict")==0 ){ strict = 1; nArg++; } if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; if( nArg+1!=argc ){ return Th_WrongNumArgs(interp, SETTING_WRONGNUMARGS); } zValue = db_get(argv[nArg], 0); if( zValue!=0 ){ Th_SetResult(interp, zValue, -1); rc = TH_OK; }else if( strict ){ Th_ErrorMessage(interp, "no value for setting \"", argv[nArg], -1); rc = TH_ERROR; }else{ Th_SetResult(interp, 0, 0); rc = TH_OK; } if( g.thTrace ){ Th_Trace("[setting %s%#h] => %d
\n", strict ? "strict " : "", argl[nArg], argv[nArg], rc); } return rc; } /* ** TH1 command: glob_match ?-one? ?--? patternList string ** ** Checks the string against the specified glob pattern -OR- list of glob ** patterns and returns non-zero if there is a match. */ #define GLOB_MATCH_WRONGNUMARGS "glob_match ?-one? ?--? patternList string" static int globMatchCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc; int one = 0; int nArg = 1; Glob *pGlob = 0; if( argc<3 || argc>5 ){ return Th_WrongNumArgs(interp, GLOB_MATCH_WRONGNUMARGS); } if( fossil_strcmp(argv[nArg], "-one")==0 ){ one = 1; nArg++; } if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; if( nArg+2!=argc ){ return Th_WrongNumArgs(interp, GLOB_MATCH_WRONGNUMARGS); } if( one ){ Th_SetResultInt(interp, sqlite3_strglob(argv[nArg], argv[nArg+1])==0); rc = TH_OK; }else{ pGlob = glob_create(argv[nArg]); if( pGlob ){ Th_SetResultInt(interp, glob_match(pGlob, argv[nArg+1])); rc = TH_OK; }else{ Th_SetResult(interp, "unable to create glob from pattern list", -1); rc = TH_ERROR; } glob_free(pGlob); } return rc; } /* ** TH1 command: regexp ?-nocase? ?--? exp string ** ** Checks the string against the specified regular expression and returns ** non-zero if it matches. If the regular expression is invalid or cannot ** be compiled, an error will be generated. */ #define REGEXP_WRONGNUMARGS "regexp ?-nocase? ?--? exp string" static int regexpCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int rc; int noCase = 0; int nArg = 1; ReCompiled *pRe = 0; const char *zErr; if( argc<3 || argc>5 ){ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); } if( fossil_strcmp(argv[nArg], "-nocase")==0 ){ noCase = 1; nArg++; } if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; if( nArg+2!=argc ){ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); } zErr = re_compile(&pRe, argv[nArg], noCase); if( !zErr ){ Th_SetResultInt(interp, re_match(pRe, (const unsigned char *)argv[nArg+1], argl[nArg+1])); rc = TH_OK; }else{ Th_SetResult(interp, zErr, -1); rc = TH_ERROR; } re_free(pRe); return rc; } /* ** TH1 command: http ?-asynchronous? ?--? url ?payload? ** ** Perform an HTTP or HTTPS request for the specified URL. If a ** payload is present, it will be interpreted as text/plain and ** the POST method will be used; otherwise, the GET method will ** be used. Upon success, if the -asynchronous option is used, an ** empty string is returned as the result; otherwise, the response ** from the server is returned as the result. Synchronous requests ** are not currently implemented. */ #define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?" static int httpCmd( Th_Interp *interp, void *p, int argc, const char **argv, int *argl ){ int nArg = 1; int fAsynchronous = 0; const char *zType, *zRegexp; Blob payload; ReCompiled *pRe = 0; UrlData urlData; if( argc<2 || argc>5 ){ return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); } if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ fAsynchronous = 1; nArg++; } if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; if( nArg+1!=argc && nArg+2!=argc ){ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); } memset(&urlData, '\0', sizeof(urlData)); url_parse_local(argv[nArg], 0, &urlData); if( urlData.isSsh || urlData.isFile ){ Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0); return TH_ERROR; } zRegexp = db_get("th1-uri-regexp", 0); if( zRegexp && zRegexp[0] ){ const char *zErr = re_compile(&pRe, zRegexp, 0); if( zErr ){ Th_SetResult(interp, zErr, -1); return TH_ERROR; } } if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){ Th_SetResult(interp, "url not allowed", -1); re_free(pRe); return TH_ERROR; } re_free(pRe); blob_zero(&payload); if( nArg+2==argc ){ blob_append(&payload, argv[nArg+1], argl[nArg+1]); zType = "POST"; }else{ zType = "GET"; } if( fAsynchronous ){ const char *zSep, *zParams; Blob hdr; zParams = strrchr(argv[nArg], '?'); if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){ zSep = ""; }else{ zSep = "/"; } blob_zero(&hdr); blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n", zType, zSep, urlData.path, zParams ? zParams : ""); if( urlData.proxyAuth ){ blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth); } if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){ char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]); char *zEncoded = encode64(zCredentials, -1); blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded); fossil_free(zEncoded); fossil_free(zCredentials); } blob_appendf(&hdr, "Host: %s\r\n" "User-Agent: %s\r\n", urlData.hostname, get_user_agent()); if( zType[0]=='P' ){ blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n\r\n", blob_size(&payload)); }else{ blob_appendf(&hdr, "\r\n"); } if( transport_open(&urlData) ){ Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0); blob_reset(&hdr); blob_reset(&payload); return TH_ERROR; } transport_send(&urlData, &hdr); transport_send(&urlData, &payload); blob_reset(&hdr); blob_reset(&payload); transport_close(&urlData); Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */ return TH_OK; }else{ Th_ErrorMessage(interp, "synchronous requests are not yet implemented", 0, 0); blob_reset(&payload); return TH_ERROR; } } /* ** TH1 command: captureTh1 STRING ** ** Evaluates the given string as TH1 code and captures any of its ** TH1-generated output as a string (instead of it being output), ** which becomes the result of the function. */ static int captureTh1Cmd( Th_Interp *interp, void *pConvert, int argc, const char **argv, int *argl ){ Blob out = empty_blob; Blob * pOrig; const char * zStr; int nStr, rc; if( argc!=2 ){ return Th_WrongNumArgs(interp, "captureTh1 STRING"); } pOrig = Th_SetOutputBlob(&out); zStr = argv[1]; nStr = argl[1]; rc = Th_Eval(g.interp, 0, zStr, nStr); Th_SetOutputBlob(pOrig); if(0==rc){ Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); } blob_reset(&out); return rc; } /* ** Attempts to open the configuration ("user") database. Optionally, also ** attempts to try to find the repository and open it. */ void Th_OpenConfig( int openRepository ){ if( openRepository && !Th_IsRepositoryOpen() ){ db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0); if( Th_IsRepositoryOpen() ){ g.th1Flags |= TH_STATE_REPOSITORY; }else{ g.th1Flags &= ~TH_STATE_REPOSITORY; } } if( !Th_IsConfigOpen() ){ db_open_config(0, 1); if( Th_IsConfigOpen() ){ g.th1Flags |= TH_STATE_CONFIG; }else{ g.th1Flags &= ~TH_STATE_CONFIG; } } } /* ** Attempts to close the configuration ("user") database. Optionally, also ** attempts to close the repository. */ void Th_CloseConfig( int closeRepository ){ if( g.th1Flags & TH_STATE_CONFIG ){ db_close_config(); g.th1Flags &= ~TH_STATE_CONFIG; } if( closeRepository && (g.th1Flags & TH_STATE_REPOSITORY) ){ db_close(1); g.th1Flags &= ~TH_STATE_REPOSITORY; } } /* ** Make sure the interpreter has been initialized. Initialize it if ** it has not been already. ** ** The interpreter is stored in the g.interp global variable. */ void Th_FossilInit(u32 flags){ int wasInit = 0; int needConfig = flags & TH_INIT_NEED_CONFIG; int forceReset = flags & TH_INIT_FORCE_RESET; int forceTcl = flags & TH_INIT_FORCE_TCL; int forceSetup = flags & TH_INIT_FORCE_SETUP; int noRepo = flags & TH_INIT_NO_REPO; static unsigned int aFlags[] = {0, 1, WIKI_LINKSONLY}; static int anonFlag = LOGIN_ANON; static int zeroInt = 0; static struct _Command { const char *zName; Th_CommandProc xProc; void *pContext; } aCommand[] = { {"anoncap", hascapCmd, (void*)&anonFlag}, {"anycap", anycapCmd, 0}, {"artifact", artifactCmd, 0}, {"builtin_request_js", builtinRequestJsCmd, 0}, {"capexpr", capexprCmd, 0}, {"captureTh1", captureTh1Cmd, 0}, {"cgiHeaderLine", cgiHeaderLineCmd, 0}, {"checkout", checkoutCmd, 0}, {"combobox", comboboxCmd, 0}, {"copybtn", copybtnCmd, 0}, {"date", dateCmd, 0}, {"decorate", wikiCmd, (void*)&aFlags[2]}, {"defHeader", defHeaderCmd, 0}, {"dir", dirCmd, 0}, {"enable_htmlify",enableHtmlifyCmd, 0}, {"enable_output", enableOutputCmd, 0}, {"encode64", encode64Cmd, 0}, {"getParameter", getParameterCmd, 0}, {"glob_match", globMatchCmd, 0}, {"globalState", globalStateCmd, 0}, {"httpize", httpizeCmd, 0}, {"hascap", hascapCmd, (void*)&zeroInt}, {"hasfeature", hasfeatureCmd, 0}, {"html", putsCmd, (void*)&aFlags[0]}, {"htmlize", htmlizeCmd, 0}, {"http", httpCmd, 0}, {"insertCsrf", insertCsrfCmd, 0}, {"linecount", linecntCmd, 0}, {"markdown", markdownCmd, 0}, {"nonce", nonceCmd, 0}, {"puts", putsCmd, (void*)&aFlags[1]}, {"query", queryCmd, 0}, {"randhex", randhexCmd, 0}, {"redirect", redirectCmd, 0}, {"regexp", regexpCmd, 0}, {"reinitialize", reinitializeCmd, 0}, {"render", renderCmd, 0}, {"repository", repositoryCmd, 0}, {"searchable", searchableCmd, 0}, {"setParameter", setParameterCmd, 0}, {"setting", settingCmd, 0}, {"styleFooter", styleFooterCmd, 0}, {"styleHeader", styleHeaderCmd, 0}, {"styleScript", styleScriptCmd, 0}, {"submenu", submenuCmd, 0}, {"tclReady", tclReadyCmd, 0}, {"trace", traceCmd, 0}, {"stime", stimeCmd, 0}, {"unversioned", unversionedCmd, 0}, {"utime", utimeCmd, 0}, {"verifyCsrf", verifyCsrfCmd, 0}, {"verifyLogin", verifyLoginCmd, 0}, {"wiki", wikiCmd, (void*)&aFlags[0]}, {0, 0, 0} }; if( g.thTrace ){ Th_Trace("th1-init 0x%x => 0x%x
\n", g.th1Flags, flags); } if( needConfig ){ /* ** This function uses several settings which may be defined in the ** repository and/or the global configuration. Since the caller ** passed a non-zero value for the needConfig parameter, make sure ** the necessary database connections are open prior to continuing. */ Th_OpenConfig(!noRepo); } if( forceReset || forceTcl || g.interp==0 ){ int created = 0; int i; if( g.interp==0 ){ Th_Vtab *pVtab = 0; #if defined(TH_MEMDEBUG) if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){ pVtab = &vtab; if( g.thTrace ){ Th_Trace("th1-init MEMDEBUG ENABLED
\n"); } } #endif g.interp = Th_CreateInterp(pVtab); created = 1; } if( forceReset || created ){ th_register_language(g.interp); /* Basic scripting commands. */ } #ifdef FOSSIL_ENABLE_TCL if( forceTcl || fossil_getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ if( !g.tcl.setup ){ g.tcl.setup = db_get("tcl-setup", 0); /* Grab Tcl setup script. */ } th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ } #endif for(i=0; i %h
\n", g.th1Setup, Th_ReturnCodeName(rc, 0)); } } g.th1Flags &= ~TH_INIT_MASK; g.th1Flags |= (flags & TH_INIT_MASK); } /* ** Store a string value in a variable in the interpreter if the variable ** does not already exist. */ void Th_MaybeStore(const char *zName, const char *zValue){ Th_FossilInit(TH_INIT_DEFAULT); if( zValue && !Th_ExistsVar(g.interp, zName, -1) ){ if( g.thTrace ){ Th_Trace("maybe_set %h {%h}
\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); } } /* ** Store a string value in a variable in the interpreter. */ void Th_Store(const char *zName, const char *zValue){ Th_FossilInit(TH_INIT_DEFAULT); if( zValue ){ if( g.thTrace ){ Th_Trace("set %h {%h}
\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); } } /* ** Appends an element to a TH1 list value. This function is called by the ** transfer subsystem; therefore, it must be very careful to avoid doing ** any unnecessary work. To that end, the TH1 subsystem will not be called ** or initialized if the list pointer is zero (i.e. which will be the case ** when TH1 transfer hooks are disabled). */ void Th_AppendToList( char **pzList, int *pnList, const char *zElem, int nElem ){ if( pzList && zElem ){ Th_FossilInit(TH_INIT_DEFAULT); Th_ListAppend(g.interp, pzList, pnList, zElem, nElem); } } /* ** Stores a list value in the specified TH1 variable using the specified ** array of strings as the source of the element values. */ void Th_StoreList( const char *zName, char **pzList, int nList ){ Th_FossilInit(TH_INIT_DEFAULT); if( pzList ){ char *zValue = 0; int nValue = 0; int i; for(i=0; i\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, nValue); Th_Free(g.interp, zValue); } } /* ** Store an integer value in a variable in the interpreter. */ void Th_StoreInt(const char *zName, int iValue){ Blob value; char *zValue; Th_FossilInit(TH_INIT_DEFAULT); blob_zero(&value); blob_appendf(&value, "%d", iValue); zValue = blob_str(&value); if( g.thTrace ){ Th_Trace("set %h {%h}
\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); blob_reset(&value); } /* ** Unset a variable. */ void Th_Unstore(const char *zName){ if( g.interp ){ Th_UnsetVar(g.interp, (char*)zName, -1); } } /* ** Retrieve a string value from the interpreter. If no such ** variable exists, return NULL. */ char *Th_Fetch(const char *zName, int *pSize){ int rc; Th_FossilInit(TH_INIT_DEFAULT); rc = Th_GetVar(g.interp, (char*)zName, -1); if( rc==TH_OK ){ return (char*)Th_GetResult(g.interp, pSize); }else{ return 0; } } /* ** Return true if the string begins with the TH1 begin-script ** tag: . */ static int isBeginScriptTag(const char *z){ return z[0]=='<' && (z[1]=='t' || z[1]=='T') && (z[2]=='h' || z[2]=='H') && z[3]=='1' && z[4]=='>'; } /* ** Return true if the string begins with the TH1 end-script ** tag: . */ static int isEndScriptTag(const char *z){ return z[0]=='<' && z[1]=='/' && (z[2]=='t' || z[2]=='T') && (z[3]=='h' || z[3]=='H') && z[4]=='1' && z[5]=='>'; } /* ** If string z[0...] contains a valid variable name, return ** the number of characters in that name. Otherwise, return 0. */ static int validVarName(const char *z){ int i = 0; int inBracket = 0; if( z[0]=='<' ){ inBracket = 1; z++; } if( z[0]==':' && z[1]==':' && fossil_isalpha(z[2]) ){ z += 3; i += 3; }else if( fossil_isalpha(z[0]) ){ z ++; i += 1; }else{ return 0; } while( fossil_isalnum(z[0]) || z[0]=='_' ){ z++; i++; } if( inBracket ){ if( z[0]!='>' ) return 0; i += 2; } return i; } #ifdef FOSSIL_ENABLE_TH1_HOOKS /* ** This function determines if TH1 hooks are enabled for the repository. It ** may be necessary to open the repository and/or the configuration ("user") ** database from within this function. Before this function returns, any ** database opened will be closed again. This is very important because some ** commands do not expect the repository and/or the configuration ("user") ** database to be open prior to their own code doing so. */ int Th_AreHooksEnabled(void){ int rc; if( fossil_getenv("TH1_ENABLE_HOOKS")!=0 ){ return 1; } Th_OpenConfig(1); rc = db_get_boolean("th1-hooks", 0); Th_CloseConfig(1); return rc; } /* ** 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, unsigned int cmdFlags ){ int rc = TH_OK; if( !Th_AreHooksEnabled() ) return rc; Th_FossilInit(TH_INIT_HOOK); Th_Store("cmd_name", zName); Th_StoreList("cmd_args", g.argv, g.argc); 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(0,zResult, nResult, 0); }else{ /* ** There is no command hook handler "installed". This situation ** is NOT actually an error. */ rc = TH_OK; } } /* ** 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
\n", zName, Th_ReturnCodeName(rc, 0)); } /* ** Does our call to Th_FossilInit() result in opening a database? If so, ** clean it up now. This is very important because some commands do not ** expect the repository and/or the configuration ("user") database to be ** open prior to their own code doing so. */ if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); return rc; } /* ** 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, unsigned int cmdFlags ){ int rc = TH_OK; if( !Th_AreHooksEnabled() ) return rc; Th_FossilInit(TH_INIT_HOOK); Th_Store("cmd_name", zName); Th_StoreList("cmd_args", g.argv, g.argc); Th_StoreInt("cmd_flags", cmdFlags); rc = Th_Eval(g.interp, 0, "command_notify", -1); if( g.thTrace ){ Th_Trace("[command_notify {%h}] => %h
\n", zName, Th_ReturnCodeName(rc, 0)); } /* ** Does our call to Th_FossilInit() result in opening a database? If so, ** clean it up now. This is very important because some commands do not ** expect the repository and/or the configuration ("user") database to be ** open prior to their own code doing so. */ if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); 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, unsigned int cmdFlags ){ int rc = TH_OK; if( !Th_AreHooksEnabled() ) return rc; Th_FossilInit(TH_INIT_HOOK); Th_Store("web_name", zName); Th_StoreList("web_args", g.argv, g.argc); 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(0,zResult, nResult, 1); }else{ /* ** There is no webpage hook handler "installed". This situation ** is NOT actually an error. */ rc = TH_OK; } } /* ** 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
\n", zName, Th_ReturnCodeName(rc, 0)); } /* ** Does our call to Th_FossilInit() result in opening a database? If so, ** clean it up now. This is very important because some commands do not ** expect the repository and/or the configuration ("user") database to be ** open prior to their own code doing so. */ if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); return rc; } /* ** 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, unsigned int cmdFlags ){ int rc = TH_OK; if( !Th_AreHooksEnabled() ) return rc; Th_FossilInit(TH_INIT_HOOK); Th_Store("web_name", zName); Th_StoreList("web_args", g.argv, g.argc); Th_StoreInt("web_flags", cmdFlags); rc = Th_Eval(g.interp, 0, "webpage_notify", -1); if( g.thTrace ){ Th_Trace("[webpage_notify {%h}] => %h
\n", zName, Th_ReturnCodeName(rc, 0)); } /* ** Does our call to Th_FossilInit() result in opening a database? If so, ** clean it up now. This is very important because some commands do not ** expect the repository and/or the configuration ("user") database to be ** open prior to their own code doing so. */ if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1); return rc; } #endif #ifdef FOSSIL_ENABLE_TH1_DOCS /* ** This function determines if TH1 docs are enabled for the repository. */ int Th_AreDocsEnabled(void){ if( fossil_getenv("TH1_ENABLE_DOCS")!=0 ){ return 1; } return db_get_boolean("th1-docs", 0); } #endif #if INTERFACE /* ** Flags for use with Th_RenderToBlob. These must not overlap with ** TH_INIT_MASK. */ #define TH_R2B_MASK ((u32)0x0f000) #define TH_R2B_NO_VARS ((u32)0x01000) /* Disables eval of $vars and $ */ #endif /* ** If pOut is NULL, this works identically to Th_Render() and sends ** any TH1-generated output to stdin (in CLI mode) or the CGI buffer ** (in CGI mode), else it works just like that function but appends ** any TH1-generated output to the given blob. A bitmask of TH_R2B_xxx ** and/or TH_INIT_xxx flags may be passed as the 3rd argument, or 0 ** for default options. Note that this function necessarily calls ** Th_FossilInit(), which may unset flags used on previous calls ** unless mFlags is explicitly passed in. */ int Th_RenderToBlob(const char *z, Blob * pOut, u32 mFlags){ int i = 0; int n; int rc = TH_OK; char *zResult; Blob * const origOut = Th_SetOutputBlob(pOut); assert(0==(TH_R2B_MASK & TH_INIT_MASK) && "init/r2b mask conflict"); Th_FossilInit(mFlags & TH_INIT_MASK); while( z[i] ){ if( 0==(TH_R2B_NO_VARS & mFlags) && z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){ const char *zVar; int nVar; int encode = 1; sendText(pOut,z, i, 0); if( z[i+1]=='<' ){ /* Variables of the form $ are html escaped */ zVar = &z[i+2]; nVar = n-2; }else{ /* Variables of the form $aaa are output raw */ zVar = &z[i+1]; nVar = n; encode = 0; } rc = Th_GetVar(g.interp, (char*)zVar, nVar); z += i+1+n; i = 0; zResult = (char*)Th_GetResult(g.interp, &n); sendText(pOut,(char*)zResult, n, encode); }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ sendText(pOut,z, i, 0); z += i+5; for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} if( g.thTrace ){ Th_Trace("render_eval {
%#h
}
\n", i, z); } rc = Th_Eval(g.interp, 0, (const char*)z, i); if( g.thTrace ){ int nTrRes; char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); Th_Trace("[render_eval] => %h {%#h}
\n", Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); } if( rc!=TH_OK ) break; z += i; if( z[0] ){ z += 6; } i = 0; }else{ i++; } } if( rc==TH_ERROR ){ zResult = (char*)Th_GetResult(g.interp, &n); sendError(pOut,zResult, n, 1); }else{ sendText(pOut,z, i, 0); } Th_SetOutputBlob(origOut); return rc; } /* ** The z[] input contains text mixed with TH1 scripts. ** The TH1 scripts are contained within .... ** TH1 variables are $aaa or $. The first form of ** variable is literal. The second is run through htmlize ** before being inserted. ** ** This routine processes the template and writes the results to one ** of stdout, CGI, or an internal blob which was set up via a prior ** call to Th_SetOutputBlob(). */ int Th_Render(const char *z){ return Th_RenderToBlob(z, pThOut, g.th1Flags) /* Maintenance reminder: on most calls to Th_Render(), e.g. for ** outputing the site skin, pThOut will be 0, which means that ** Th_RenderToBlob() will output directly to the CGI buffer (in ** CGI mode) or stdout (in CLI mode). Recursive calls, however, ** e.g. via the "render" script function binding, need to use the ** pThOut blob in order to avoid out-of-order output if ** Th_SetOutputBlob() has been called. If it has not been called, ** pThOut will be 0, which will redirect the output to CGI/stdout, ** as appropriate. We need to pass on g.th1Flags for the case of ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get ** inadvertently toggled off by a recursive call. */; } /* ** COMMAND: test-th-render ** ** Usage: %fossil test-th-render FILE ** ** Read the content of the file named "FILE" as if it were a header or ** footer or ticket rendering script, evaluate it, and show the results ** on standard output. ** ** Options: ** --cgi Include a CGI response header in the output ** --http Include an HTTP response header in the output ** --open-config Open the configuration database ** --set-anon-caps Set anonymous login capabilities ** --set-user-caps Set user login capabilities ** --th-trace Trace TH1 execution (for debugging purposes) */ void test_th_render(void){ int forceCgi, fullHttpReply; Blob in; Th_InitTraceLog(); forceCgi = find_option("cgi", 0, 0)!=0; fullHttpReply = find_option("http", 0, 0)!=0; if( fullHttpReply ) forceCgi = 1; if( forceCgi ) Th_ForceCgi(fullHttpReply); if( find_option("open-config", 0, 0)!=0 ){ Th_OpenConfig(1); } if( find_option("set-anon-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS"); login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON); g.useLocalauth = 1; } if( find_option("set-user-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); login_set_capabilities(zCap ? zCap : "sx", 0); g.useLocalauth = 1; } verify_all_options(); if( g.argc<3 ){ usage("FILE"); } blob_zero(&in); blob_read_from_file(&in, g.argv[2], ExtFILE); Th_Render(blob_str(&in)); Th_PrintTraceLog(); if( forceCgi ) cgi_reply(); } /* ** COMMAND: test-th-eval ** ** Usage: %fossil test-th-eval SCRIPT ** ** Evaluate SCRIPT as if it were a header or footer or ticket rendering ** script and show the results on standard output. SCRIPT may be either ** a filename or a string of th1 script code. ** ** Options: ** --cgi Include a CGI response header in the output ** --http Include an HTTP response header in the output ** --open-config Open the configuration database ** --set-anon-caps Set anonymous login capabilities ** --set-user-caps Set user login capabilities ** --th-trace Trace TH1 execution (for debugging purposes) */ void test_th_eval(void){ int rc; const char *zRc; const char *zCode = 0; int forceCgi, fullHttpReply; Blob code = empty_blob; Th_InitTraceLog(); forceCgi = find_option("cgi", 0, 0)!=0; fullHttpReply = find_option("http", 0, 0)!=0; if( fullHttpReply ) forceCgi = 1; if( forceCgi ) Th_ForceCgi(fullHttpReply); if( find_option("open-config", 0, 0)!=0 ){ Th_OpenConfig(1); } if( find_option("set-anon-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS"); login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON); g.useLocalauth = 1; } if( find_option("set-user-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); login_set_capabilities(zCap ? zCap : "sx", 0); g.useLocalauth = 1; } verify_all_options(); if( g.argc!=3 ){ usage("script"); } if(file_isfile(g.argv[2], ExtFILE)){ blob_read_from_file(&code, g.argv[2], ExtFILE); zCode = blob_str(&code); }else{ zCode = g.argv[2]; } Th_FossilInit(TH_INIT_DEFAULT); rc = Th_Eval(g.interp, 0, zCode, -1); zRc = Th_ReturnCodeName(rc, 1); fossil_print("%s%s%s\n", zRc, zRc ? ": " : "", Th_GetResult(g.interp, 0)); Th_PrintTraceLog(); blob_reset(&code); if( forceCgi ) cgi_reply(); } /* ** COMMAND: test-th-source ** ** Usage: %fossil test-th-source FILE ** ** Evaluate the contents of the file named "FILE" as if it were a header ** or footer or ticket rendering script and show the results on standard ** output. ** ** Options: ** --cgi Include a CGI response header in the output ** --http Include an HTTP response header in the output ** --open-config Open the configuration database ** --set-anon-caps Set anonymous login capabilities ** --set-user-caps Set user login capabilities ** --th-trace Trace TH1 execution (for debugging purposes) ** --no-print-result Do not output the final result. Use if it ** interferes with script output. */ void test_th_source(void){ int rc; const char *zRc; int forceCgi, fullHttpReply, fNoPrintRc; Blob in; Th_InitTraceLog(); forceCgi = find_option("cgi", 0, 0)!=0; fullHttpReply = find_option("http", 0, 0)!=0; fNoPrintRc = find_option("no-print-result",0,0)!=0; if( fullHttpReply ) forceCgi = 1; if( forceCgi ) Th_ForceCgi(fullHttpReply); if( find_option("open-config", 0, 0)!=0 ){ Th_OpenConfig(1); } if( find_option("set-anon-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS"); login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON); g.useLocalauth = 1; } if( find_option("set-user-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); login_set_capabilities(zCap ? zCap : "sx", 0); g.useLocalauth = 1; } verify_all_options(); if( g.argc!=3 ){ usage("file"); } blob_zero(&in); blob_read_from_file(&in, g.argv[2], ExtFILE); Th_FossilInit(TH_INIT_DEFAULT); rc = Th_Eval(g.interp, 0, blob_str(&in), -1); zRc = Th_ReturnCodeName(rc, 1); if(0==fNoPrintRc){ fossil_print("%s%s%s\n", zRc, zRc ? ": " : "", Th_GetResult(g.interp, 0)); } Th_PrintTraceLog(); if( forceCgi ) cgi_reply(); } #ifdef FOSSIL_ENABLE_TH1_HOOKS /* ** COMMAND: test-th-hook ** ** Usage: %fossil test-th-hook TYPE NAME FLAGS ** ** Evaluates the TH1 script configured for the pre-operation (i.e. a command ** or web page) "hook" or post-operation "notification". The results of the ** script evaluation, if any, will be printed to the standard output channel. ** The NAME argument must be the name of a command or web page; however, it ** does not necessarily have to be a command or web page that is normally ** recognized by Fossil. The FLAGS argument will be used to set the value ** of the "cmd_flags" and/or "web_flags" TH1 variables, if applicable. The ** TYPE argument must be one of the following: ** ** cmdhook Executes the TH1 procedure [command_hook], after ** setting the TH1 variables "cmd_name", "cmd_args", ** and "cmd_flags" to appropriate values. ** ** cmdnotify Executes the TH1 procedure [command_notify], after ** setting the TH1 variables "cmd_name", "cmd_args", ** and "cmd_flags" to appropriate values. ** ** webhook Executes the TH1 procedure [webpage_hook], after ** setting the TH1 variables "web_name", "web_args", ** and "web_flags" to appropriate values. ** ** webnotify Executes the TH1 procedure [webpage_notify], after ** setting the TH1 variables "web_name", "web_args", ** and "web_flags" to appropriate values. ** ** Options: ** --cgi Include a CGI response header in the output ** --http Include an HTTP response header in the output ** --th-trace Trace TH1 execution (for debugging purposes) */ void test_th_hook(void){ int rc = TH_OK; int nResult = 0; char *zResult = 0; int forceCgi, fullHttpReply; Th_InitTraceLog(); forceCgi = find_option("cgi", 0, 0)!=0; fullHttpReply = find_option("http", 0, 0)!=0; if( fullHttpReply ) forceCgi = 1; if( forceCgi ) Th_ForceCgi(fullHttpReply); verify_all_options(); if( g.argc<5 ){ usage("TYPE NAME FLAGS"); } if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){ rc = Th_CommandHook(g.argv[3], (unsigned int)atoi(g.argv[4])); }else if( fossil_stricmp(g.argv[2], "cmdnotify")==0 ){ rc = Th_CommandNotify(g.argv[3], (unsigned int)atoi(g.argv[4])); }else if( fossil_stricmp(g.argv[2], "webhook")==0 ){ rc = Th_WebpageHook(g.argv[3], (unsigned int)atoi(g.argv[4])); }else if( fossil_stricmp(g.argv[2], "webnotify")==0 ){ rc = Th_WebpageNotify(g.argv[3], (unsigned int)atoi(g.argv[4])); }else{ fossil_fatal("Unknown TH1 hook %s", g.argv[2]); } if( g.interp ){ zResult = (char*)Th_GetResult(g.interp, &nResult); } sendText(0,"RESULT (", -1, 0); sendText(0,Th_ReturnCodeName(rc, 0), -1, 0); sendText(0,")", -1, 0); if( zResult && nResult>0 ){ sendText(0,": ", -1, 0); sendText(0,zResult, nResult, 0); } sendText(0,"\n", -1, 0); Th_PrintTraceLog(); if( forceCgi ) cgi_reply(); } #endif