1 /*
2 ** Copyright (c) 2008 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains an interface between the TH scripting language
19 ** (an independent project) and fossil.
20 */
21 #include "config.h"
22 #include "th_main.h"
23 #include "sqlite3.h"
24 
25 #if INTERFACE
26 /*
27 ** Flag parameters to the Th_FossilInit() routine used to control the
28 ** interpreter creation and initialization process.
29 */
30 #define TH_INIT_NONE        ((u32)0x00000000) /* No flags. */
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL   ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO     ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_NO_ENCODE   ((u32)0x00000020) /* Do not html-encode sendText() output. */
37 #define TH_INIT_MASK        ((u32)0x0000003F) /* All possible init flags. */
38 
39 /*
40 ** Useful and/or "well-known" combinations of flag values.
41 */
42 #define TH_INIT_DEFAULT     (TH_INIT_NONE)      /* Default flags. */
43 #define TH_INIT_HOOK        (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP)
44 #define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */
45 #endif
46 
47 /*
48 ** Flags set by functions in this file to keep track of integration state
49 ** information.  These flags should not be used outside of this file.
50 */
51 #define TH_STATE_CONFIG     ((u32)0x00000200) /* We opened the config. */
52 #define TH_STATE_REPOSITORY ((u32)0x00000400) /* We opened the repository. */
53 #define TH_STATE_MASK       ((u32)0x00000600) /* All possible state flags. */
54 
55 #ifdef FOSSIL_ENABLE_TH1_HOOKS
56 /*
57 ** These are the "well-known" TH1 error messages that occur when no hook is
58 ** registered to be called prior to executing a command or processing a web
59 ** page, respectively.  If one of these errors is seen, it will not be sent
60 ** or displayed to the remote user or local interactive user, respectively.
61 */
62 #define NO_COMMAND_HOOK_ERROR "no such command:  command_hook"
63 #define NO_WEBPAGE_HOOK_ERROR "no such command:  webpage_hook"
64 #endif
65 
66 /*
67 ** These macros are used within this file to detect if the repository and
68 ** configuration ("user") database are currently open.
69 */
70 #define Th_IsRepositoryOpen()     (g.repositoryOpen)
71 #define Th_IsConfigOpen()         (g.zConfigDbName!=0)
72 
73 /*
74 ** When memory debugging is enabled, use our custom memory allocator.
75 */
76 #if defined(TH_MEMDEBUG)
77 /*
78 ** Global variable counting the number of outstanding calls to malloc()
79 ** made by the th1 implementation. This is used to catch memory leaks
80 ** in the interpreter. Obviously, it also means th1 is not threadsafe.
81 */
82 static int nOutstandingMalloc = 0;
83 
84 /*
85 ** Implementations of malloc() and free() to pass to the interpreter.
86 */
xMalloc(unsigned int n)87 static void *xMalloc(unsigned int n){
88   void *p = fossil_malloc(n);
89   if( p ){
90     nOutstandingMalloc++;
91   }
92   return p;
93 }
xFree(void * p)94 static void xFree(void *p){
95   if( p ){
96     nOutstandingMalloc--;
97   }
98   free(p);
99 }
100 static Th_Vtab vtab = { xMalloc, xFree };
101 
102 /*
103 ** Returns the number of outstanding TH1 memory allocations.
104 */
Th_GetOutstandingMalloc()105 int Th_GetOutstandingMalloc(){
106   return nOutstandingMalloc;
107 }
108 #endif
109 
110 /*
111 ** Generate a TH1 trace message if debugging is enabled.
112 */
Th_Trace(const char * zFormat,...)113 void Th_Trace(const char *zFormat, ...){
114   va_list ap;
115   va_start(ap, zFormat);
116   blob_vappendf(&g.thLog, zFormat, ap);
117   va_end(ap);
118 }
119 
120 /*
121 ** Forces input and output to be done via the CGI subsystem.
122 */
Th_ForceCgi(int fullHttpReply)123 void Th_ForceCgi(int fullHttpReply){
124   g.httpOut = stdout;
125   g.httpIn = stdin;
126   fossil_binary_mode(g.httpOut);
127   fossil_binary_mode(g.httpIn);
128   g.cgiOutput = 1;
129   g.fullHttpReply = fullHttpReply;
130 }
131 
132 /*
133 ** Checks if the TH1 trace log needs to be enabled.  If so, prepares
134 ** it for use.
135 */
Th_InitTraceLog()136 void Th_InitTraceLog(){
137   g.thTrace = find_option("th-trace", 0, 0)!=0;
138   if( g.thTrace ){
139     g.fAnyTrace = 1;
140     blob_zero(&g.thLog);
141   }
142 }
143 
144 /*
145 ** Prints the entire contents of the TH1 trace log to the standard
146 ** output channel.
147 */
Th_PrintTraceLog()148 void Th_PrintTraceLog(){
149   if( g.thTrace ){
150     fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n");
151     fossil_print("%s", blob_str(&g.thLog));
152     fossil_print("\n------------------- END TRACE LOG -------------------\n");
153   }
154 }
155 
156 /*
157 ** - adopted from ls_cmd_rev in checkin.c
158 ** - adopted commands/error handling for usage within th1
159 ** - interface adopted to allow result creation as TH1 List
160 **
161 ** Takes a checkin identifier in zRev and an optiona glob pattern in zGLOB
162 ** as parameter returns a TH list in pzList,pnList with filenames matching
163 ** glob pattern with the checking
164 */
dir_cmd_rev(Th_Interp * interp,char ** pzList,int * pnList,const char * zRev,const char * zGlob,int bDetails)165 static void dir_cmd_rev(
166   Th_Interp *interp,
167   char **pzList,
168   int *pnList,
169   const char *zRev,  /* Revision string given */
170   const char *zGlob, /* Glob pattern given */
171   int bDetails
172 ){
173   Stmt q;
174   char *zOrderBy = "pathname COLLATE nocase";
175   int rid;
176 
177   rid = th1_name_to_typed_rid(interp, zRev, "ci");
178   compute_fileage(rid, zGlob);
179   db_prepare(&q,
180     "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
181     "       blob.size\n"
182     "  FROM fileage, blob\n"
183     " WHERE blob.rid=fileage.fid \n"
184     " ORDER BY %s;", zOrderBy /*safe-for-%s*/
185   );
186   while( db_step(&q)==SQLITE_ROW ){
187     const char *zFile = db_column_text(&q, 1);
188     if( bDetails ){
189       const char *zTime = db_column_text(&q, 0);
190       int size = db_column_int(&q, 2);
191       char zSize[50];
192       char *zSubList = 0;
193       int nSubList = 0;
194       sqlite3_snprintf(sizeof(zSize), zSize, "%d", size);
195       Th_ListAppend(interp, &zSubList, &nSubList, zFile, -1);
196       Th_ListAppend(interp, &zSubList, &nSubList, zSize, -1);
197       Th_ListAppend(interp, &zSubList, &nSubList, zTime, -1);
198       Th_ListAppend(interp, pzList, pnList, zSubList, -1);
199       Th_Free(interp, zSubList);
200     }else{
201       Th_ListAppend(interp, pzList, pnList, zFile, -1);
202     }
203   }
204   db_finalize(&q);
205 }
206 
207 /*
208 ** TH1 command: dir CHECKIN ?GLOB? ?DETAILS?
209 **
210 ** Returns a list containing all files in CHECKIN. If GLOB is given only
211 ** the files matching the pattern GLOB within CHECKIN will be returned.
212 ** If DETAILS is non-zero, the result will be a list-of-lists, with each
213 ** element containing at least three elements: the file name, the file
214 ** size (in bytes), and the file last modification time (relative to the
215 ** time zone configured for the repository).
216 */
dirCmd(Th_Interp * interp,void * ctx,int argc,const char ** argv,int * argl)217 static int dirCmd(
218   Th_Interp *interp,
219   void *ctx,
220   int argc,
221   const char **argv,
222   int *argl
223 ){
224   const char *zGlob = 0;
225   int bDetails = 0;
226 
227   if( argc<2 || argc>4 ){
228     return Th_WrongNumArgs(interp, "dir CHECKIN ?GLOB? ?DETAILS?");
229   }
230   if( argc>=3 ){
231     zGlob = argv[2];
232   }
233   if( argc>=4 && Th_ToInt(interp, argv[3], argl[3], &bDetails) ){
234     return TH_ERROR;
235   }
236   if( Th_IsRepositoryOpen() ){
237     char *zList = 0;
238     int nList = 0;
239     dir_cmd_rev(interp, &zList, &nList, argv[1], zGlob, bDetails);
240     Th_SetResult(interp, zList, nList);
241     Th_Free(interp, zList);
242     return TH_OK;
243   }else{
244     Th_SetResult(interp, "repository unavailable", -1);
245     return TH_ERROR;
246   }
247 }
248 
249 /*
250 ** TH1 command: httpize STRING
251 **
252 ** Escape all characters of STRING which have special meaning in URI
253 ** components. Return a new string result.
254 */
httpizeCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)255 static int httpizeCmd(
256   Th_Interp *interp,
257   void *p,
258   int argc,
259   const char **argv,
260   int *argl
261 ){
262   char *zOut;
263   if( argc!=2 ){
264     return Th_WrongNumArgs(interp, "httpize STRING");
265   }
266   zOut = httpize((char*)argv[1], argl[1]);
267   Th_SetResult(interp, zOut, -1);
268   free(zOut);
269   return TH_OK;
270 }
271 
272 /*
273 ** True if output is enabled.  False if disabled.
274 */
275 static int enableOutput = 1;
276 
277 /*
278 ** TH1 command: enable_output BOOLEAN
279 **
280 ** Enable or disable the puts, wiki, combobox and copybtn commands.
281 */
enableOutputCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)282 static int enableOutputCmd(
283   Th_Interp *interp,
284   void *p,
285   int argc,
286   const char **argv,
287   int *argl
288 ){
289   int rc;
290   if( argc<2 || argc>3 ){
291     return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
292   }
293   rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
294   if( g.thTrace ){
295     Th_Trace("enable_output {%.*s} -> %d<br />\n", argl[1],argv[1],enableOutput);
296   }
297   return rc;
298 }
299 
300 /*
301 ** TH1 command: enable_htmlify ?BOOLEAN?
302 **
303 ** Enable or disable the HTML escaping done by all output which
304 ** originates from TH1 (via sendText()).
305 **
306 ** If passed no arguments it instead returns 0 or 1 to indicate the
307 ** current state.
308 */
enableHtmlifyCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)309 static int enableHtmlifyCmd(
310   Th_Interp *interp,
311   void *p,
312   int argc,
313   const char **argv,
314   int *argl
315 ){
316   int rc = 0, buul;
317   if( argc>3 ){
318     return Th_WrongNumArgs(interp,
319                            "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
320   }
321   buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
322   Th_SetResultInt(g.interp, buul);
323   if(argc>1){
324     if( g.thTrace ){
325       Th_Trace("enable_htmlify {%.*s} -> %d<br />\n",
326                argl[1],argv[1],buul);
327     }
328     rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
329     if(!rc){
330       if(buul){
331         g.th1Flags &= ~TH_INIT_NO_ENCODE;
332       }else{
333         g.th1Flags |= TH_INIT_NO_ENCODE;
334       }
335     }
336   }
337   return rc;
338 }
339 
340 /*
341 ** Returns a name for a TH1 return code.
342 */
Th_ReturnCodeName(int rc,int nullIfOk)343 const char *Th_ReturnCodeName(int rc, int nullIfOk){
344   static char zRc[32];
345 
346   switch( rc ){
347     case TH_OK:       return nullIfOk ? 0 : "TH_OK";
348     case TH_ERROR:    return "TH_ERROR";
349     case TH_BREAK:    return "TH_BREAK";
350     case TH_RETURN:   return "TH_RETURN";
351     case TH_CONTINUE: return "TH_CONTINUE";
352     case TH_RETURN2:  return "TH_RETURN2";
353     default: {
354       sqlite3_snprintf(sizeof(zRc), zRc, "TH1 return code %d", rc);
355     }
356   }
357   return zRc;
358 }
359 
360 /* See Th_SetOutputBlob() */
361 static Blob * pThOut = 0;
362 /*
363 ** Sets the th1-internal output-redirection blob and returns the
364 ** previous value. That blob is used by certain output-generation
365 ** routines to emit its output. It returns the previous value so that
366 ** a routine can temporarily replace the buffer with its own and
367 ** restore it when it's done.
368 */
Th_SetOutputBlob(Blob * pOut)369 Blob * Th_SetOutputBlob(Blob * pOut){
370   Blob * tmp = pThOut;
371   pThOut = pOut;
372   return tmp;
373 }
374 
375 /*
376 ** Send text to the appropriate output: If pOut is not NULL, it is
377 ** appended there, else to the console or to the CGI reply buffer.
378 ** Escape all characters with special meaning to HTML if the encode
379 ** parameter is true, with the exception that that flag is ignored if
380 ** g.th1Flags has the TH_INIT_NO_ENCODE flag.
381 **
382 ** If pOut is NULL and the global pThOut is not then that blob
383 ** is used for output.
384 */
sendText(Blob * pOut,const char * z,int n,int encode)385 static void sendText(Blob * pOut, const char *z, int n, int encode){
386   if(0==pOut && pThOut!=0){
387     pOut = pThOut;
388   }
389   if(TH_INIT_NO_ENCODE & g.th1Flags){
390     encode = 0;
391   }
392   if( enableOutput && n ){
393     if( n<0 ) n = strlen(z);
394     if( encode ){
395       z = htmlize(z, n);
396       n = strlen(z);
397     }
398     if(pOut!=0){
399       blob_append(pOut, z, n);
400     }else if( g.cgiOutput ){
401       cgi_append_content(z, n);
402     }else{
403       fwrite(z, 1, n, stdout);
404       fflush(stdout);
405     }
406     if( encode ) free((char*)z);
407   }
408 }
409 
410 /*
411 ** error-reporting counterpart of sendText().
412 */
sendError(Blob * pOut,const char * z,int n,int forceCgi)413 static void sendError(Blob * pOut, const char *z, int n, int forceCgi){
414   int savedEnable = enableOutput;
415   enableOutput = 1;
416   if( forceCgi || g.cgiOutput ){
417     sendText(pOut, "<hr /><p class=\"thmainError\">", -1, 0);
418   }
419   sendText(pOut,"ERROR: ", -1, 0);
420   sendText(pOut,(char*)z, n, 1);
421   sendText(pOut,forceCgi || g.cgiOutput ? "</p>" : "\n", -1, 0);
422   enableOutput = savedEnable;
423 }
424 
425 /*
426 ** Convert name to an rid.  This function was copied from name_to_typed_rid()
427 ** in name.c; however, it has been modified to report TH1 script errors instead
428 ** of "fatal errors".
429 */
th1_name_to_typed_rid(Th_Interp * interp,const char * zName,const char * zType)430 int th1_name_to_typed_rid(
431   Th_Interp *interp,
432   const char *zName,
433   const char *zType
434 ){
435   int rid;
436 
437   if( zName==0 || zName[0]==0 ) return 0;
438   rid = symbolic_name_to_rid(zName, zType);
439   if( rid<0 ){
440     Th_SetResult(interp, "ambiguous name", -1);
441   }else if( rid==0 ){
442     Th_SetResult(interp, "name not found", -1);
443   }
444   return rid;
445 }
446 
447 /*
448 ** Attempt to lookup the specified check-in and file name into an rid.
449 ** This function was copied from artifact_from_ci_and_filename() in
450 ** info.c; however, it has been modified to report TH1 script errors
451 ** instead of "fatal errors".
452 */
th1_artifact_from_ci_and_filename(Th_Interp * interp,const char * zCI,const char * zFilename)453 int th1_artifact_from_ci_and_filename(
454   Th_Interp *interp,
455   const char *zCI,
456   const char *zFilename
457 ){
458   int cirid;
459   Blob err;
460   Manifest *pManifest;
461   ManifestFile *pFile;
462 
463   if( zCI==0 ){
464     Th_SetResult(interp, "invalid check-in", -1);
465     return 0;
466   }
467   if( zFilename==0 ){
468     Th_SetResult(interp, "invalid file name", -1);
469     return 0;
470   }
471   cirid = th1_name_to_typed_rid(interp, zCI, "*");
472   blob_zero(&err);
473   pManifest = manifest_get(cirid, CFTYPE_MANIFEST, &err);
474   if( pManifest==0 ){
475     if( blob_size(&err)>0 ){
476       Th_SetResult(interp, blob_str(&err), blob_size(&err));
477     }else{
478       Th_SetResult(interp, "manifest not found", -1);
479     }
480     blob_reset(&err);
481     return 0;
482   }
483   blob_reset(&err);
484   manifest_file_rewind(pManifest);
485   while( (pFile = manifest_file_next(pManifest,0))!=0 ){
486     if( fossil_strcmp(zFilename, pFile->zName)==0 ){
487       int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
488       manifest_destroy(pManifest);
489       return rid;
490     }
491   }
492   Th_SetResult(interp, "file name not found in manifest", -1);
493   return 0;
494 }
495 
496 /*
497 ** TH1 command: nonce
498 **
499 ** Returns the value of the cryptographic nonce for the request being
500 ** processed.
501 */
nonceCmd(Th_Interp * interp,void * pConvert,int argc,const char ** argv,int * argl)502 static int nonceCmd(
503   Th_Interp *interp,
504   void *pConvert,
505   int argc,
506   const char **argv,
507   int *argl
508 ){
509   if( argc!=1 ){
510     return Th_WrongNumArgs(interp, "nonce");
511   }
512   Th_SetResult(interp, style_nonce(), -1);
513   return TH_OK;
514 }
515 
516 /*
517 ** TH1 command: puts STRING
518 ** TH1 command: html STRING
519 **
520 ** Output STRING escaped for HTML (puts) or unchanged (html).
521 */
putsCmd(Th_Interp * interp,void * pConvert,int argc,const char ** argv,int * argl)522 static int putsCmd(
523   Th_Interp *interp,
524   void *pConvert,
525   int argc,
526   const char **argv,
527   int *argl
528 ){
529   if( argc!=2 ){
530     return Th_WrongNumArgs(interp, "puts STRING");
531   }
532   sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
533   return TH_OK;
534 }
535 
536 /*
537 ** TH1 command: redirect URL ?withMethod?
538 **
539 ** Issues an HTTP redirect to the specified URL and then exits the process.
540 ** By default, an HTTP status code of 302 is used.  If the optional withMethod
541 ** argument is present and non-zero, an HTTP status code of 307 is used, which
542 ** should force the user agent to preserve the original method for the request
543 ** (e.g. GET, POST) instead of (possibly) forcing the user agent to change the
544 ** method to GET.
545 */
redirectCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)546 static int redirectCmd(
547   Th_Interp *interp,
548   void *p,
549   int argc,
550   const char **argv,
551   int *argl
552 ){
553   int withMethod = 0;
554   if( argc!=2 && argc!=3 ){
555     return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
556   }
557   if( argc==3 ){
558     if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
559       return TH_ERROR;
560     }
561   }
562   if( withMethod ){
563     cgi_redirect_with_method(argv[1]);
564   }else{
565     cgi_redirect(argv[1]);
566   }
567   Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */
568   return TH_OK;
569 }
570 
571 /*
572 ** TH1 command: insertCsrf
573 **
574 ** While rendering a form, call this command to add the Anti-CSRF token
575 ** as a hidden element of the form.
576 */
insertCsrfCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)577 static int insertCsrfCmd(
578   Th_Interp *interp,
579   void *p,
580   int argc,
581   const char **argv,
582   int *argl
583 ){
584   if( argc!=1 ){
585     return Th_WrongNumArgs(interp, "insertCsrf");
586   }
587   login_insert_csrf_secret();
588   return TH_OK;
589 }
590 
591 /*
592 ** TH1 command: verifyCsrf
593 **
594 ** Before using the results of a form, first call this command to verify
595 ** that this Anti-CSRF token is present and is valid.  If the Anti-CSRF token
596 ** is missing or is incorrect, that indicates a cross-site scripting attack.
597 ** If the event of an attack is detected, an error message is generated and
598 ** all further processing is aborted.
599 */
verifyCsrfCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)600 static int verifyCsrfCmd(
601   Th_Interp *interp,
602   void *p,
603   int argc,
604   const char **argv,
605   int *argl
606 ){
607   if( argc!=1 ){
608     return Th_WrongNumArgs(interp, "verifyCsrf");
609   }
610   login_verify_csrf_secret();
611   return TH_OK;
612 }
613 
614 /*
615 ** TH1 command: verifyLogin
616 **
617 ** Returns non-zero if the specified user name and password represent a
618 ** valid login for the repository.
619 */
verifyLoginCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)620 static int verifyLoginCmd(
621   Th_Interp *interp,
622   void *p,
623   int argc,
624   const char **argv,
625   int *argl
626 ){
627   const char *zUser;
628   const char *zPass;
629   int uid;
630   if( argc!=3 ){
631     return Th_WrongNumArgs(interp, "verifyLogin userName password");
632   }
633   zUser = argv[1];
634   zPass = argv[2];
635   uid = login_search_uid(&zUser, zPass);
636   Th_SetResultInt(interp, uid!=0);
637   if( uid==0 ) sqlite3_sleep(100);
638   return TH_OK;
639 }
640 
641 /*
642 ** TH1 command: markdown STRING
643 **
644 ** Renders the input string as markdown.  The result is a two-element list.
645 ** The first element is the text-only title string.  The second element
646 ** contains the body, rendered as HTML.
647 */
markdownCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)648 static int markdownCmd(
649   Th_Interp *interp,
650   void *p,
651   int argc,
652   const char **argv,
653   int *argl
654 ){
655   Blob src, title, body;
656   char *zValue = 0;
657   int nValue = 0;
658   if( argc!=2 ){
659     return Th_WrongNumArgs(interp, "markdown STRING");
660   }
661   blob_zero(&src);
662   blob_init(&src, (char*)argv[1], argl[1]);
663   blob_zero(&title); blob_zero(&body);
664   markdown_to_html(&src, &title, &body);
665   Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
666   Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
667   Th_SetResult(interp, zValue, nValue);
668   Th_Free(interp, zValue);
669   return TH_OK;
670 }
671 
672 /*
673 ** TH1 command: decorate STRING
674 ** TH1 command: wiki STRING
675 **
676 ** Render the input string as wiki.  For the decorate command, only links
677 ** are handled.
678 */
wikiCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)679 static int wikiCmd(
680   Th_Interp *interp,
681   void *p,
682   int argc,
683   const char **argv,
684   int *argl
685 ){
686   int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
687   if( argc!=2 ){
688     return Th_WrongNumArgs(interp, "wiki STRING");
689   }
690   if( enableOutput ){
691     Blob src;
692     blob_init(&src, (char*)argv[1], argl[1]);
693     wiki_convert(&src, 0, flags);
694     blob_reset(&src);
695   }
696   return TH_OK;
697 }
698 
699 /*
700 ** TH1 command: htmlize STRING
701 **
702 ** Escape all characters of STRING which have special meaning in HTML.
703 ** Return a new string result.
704 */
htmlizeCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)705 static int htmlizeCmd(
706   Th_Interp *interp,
707   void *p,
708   int argc,
709   const char **argv,
710   int *argl
711 ){
712   char *zOut;
713   if( argc!=2 ){
714     return Th_WrongNumArgs(interp, "htmlize STRING");
715   }
716   zOut = htmlize((char*)argv[1], argl[1]);
717   Th_SetResult(interp, zOut, -1);
718   free(zOut);
719   return TH_OK;
720 }
721 
722 /*
723 ** TH1 command: encode64 STRING
724 **
725 ** Encode the specified string using Base64 and return the result.
726 */
encode64Cmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)727 static int encode64Cmd(
728   Th_Interp *interp,
729   void *p,
730   int argc,
731   const char **argv,
732   int *argl
733 ){
734   char *zOut;
735   if( argc!=2 ){
736     return Th_WrongNumArgs(interp, "encode64 STRING");
737   }
738   zOut = encode64((char*)argv[1], argl[1]);
739   Th_SetResult(interp, zOut, -1);
740   free(zOut);
741   return TH_OK;
742 }
743 
744 /*
745 ** TH1 command: date
746 **
747 ** Return a string which is the current time and date.  If the
748 ** -local option is used, the date appears using localtime instead
749 ** of UTC.
750 */
dateCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)751 static int dateCmd(
752   Th_Interp *interp,
753   void *p,
754   int argc,
755   const char **argv,
756   int *argl
757 ){
758   char *zOut;
759   if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
760     zOut = db_text("??", "SELECT datetime('now',toLocal())");
761   }else{
762     zOut = db_text("??", "SELECT datetime('now')");
763   }
764   Th_SetResult(interp, zOut, -1);
765   free(zOut);
766   return TH_OK;
767 }
768 
769 /*
770 ** TH1 command: hascap STRING...
771 ** TH1 command: anoncap STRING...
772 **
773 ** Return true if the current user (hascap) or if the anonymous user
774 ** (anoncap) has all of the capabilities listed in STRING.
775 */
hascapCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)776 static int hascapCmd(
777   Th_Interp *interp,
778   void *p,
779   int argc,
780   const char **argv,
781   int *argl
782 ){
783   int rc = 1, i;
784   char *zCapList = 0;
785   int nCapList = 0;
786   if( argc<2 ){
787     return Th_WrongNumArgs(interp, "hascap STRING ...");
788   }
789   for(i=1; rc==1 && i<argc; i++){
790     if( g.thTrace ){
791       Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
792     }
793     rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
794   }
795   if( g.thTrace ){
796     Th_Trace("[%s %#h] => %d<br />\n", argv[0], nCapList, zCapList, rc);
797     Th_Free(interp, zCapList);
798   }
799   Th_SetResultInt(interp, rc);
800   return TH_OK;
801 }
802 
803 /*
804 ** TH1 command:   capexpr CAPABILITY-EXPR
805 **
806 ** Nmemonic:  "CAPability EXPRression"
807 **
808 ** The capability expression is a list.  Each term of the list is a cluster
809 ** of capability letters.  The overall expression is true if any one term
810 ** is true.  A single term is true if all letters within that term are true.
811 ** Or, if the term begins with "!", then the term is true if none of the
812 ** terms or true.  Or, if the term begins with "@" then the term is true
813 ** if all of the capability letters in that term are available to the
814 ** "anonymous" user.  Or, if the term is "*" then it is always true.
815 **
816 ** Examples:
817 **
818 **   capexpr {j o r}               True if any one of j, o, or r are available
819 **   capexpr {oh}                  True if both o and h are available
820 **   capexpr {@2 @3 4 5 6}         2 or 3 available for anonymous or one of
821 **                                   4, 5 or 6 is available for the user
822 */
capexprCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)823 int capexprCmd(
824   Th_Interp *interp,
825   void *p,
826   int argc,
827   const char **argv,
828   int *argl
829 ){
830   char **azCap;
831   int *anCap;
832   int nCap;
833   int rc;
834   int i;
835 
836   if( argc!=2 ){
837     return Th_WrongNumArgs(interp, "capexpr EXPR");
838   }
839   rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
840   if( rc ) return rc;
841   rc = 0;
842   for(i=0; i<nCap; i++){
843     if( azCap[i][0]=='!' ){
844       rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
845     }else if( azCap[i][0]=='@' ){
846       rc = login_has_capability(azCap[i]+1, anCap[i]-1, LOGIN_ANON);
847     }else if( azCap[i][0]=='*' ){
848       rc = 1;
849     }else{
850       rc = login_has_capability(azCap[i], anCap[i], 0);
851     }
852     break;
853   }
854   Th_Free(interp, azCap);
855   Th_SetResultInt(interp, rc);
856   return TH_OK;
857 }
858 
859 
860 /*
861 ** TH1 command: searchable STRING...
862 **
863 ** Return true if searching in any of the document classes identified
864 ** by STRING is enabled for the repository and user has the necessary
865 ** capabilities to perform the search.
866 **
867 ** Document classes:
868 **
869 **      c     Check-in comments
870 **      d     Embedded documentation
871 **      t     Tickets
872 **      w     Wiki
873 **
874 ** To be clear, only one of the document classes identified by each STRING
875 ** needs to be searchable in order for that argument to be true.  But
876 ** all arguments must be true for this routine to return true.  Hence, to
877 ** see if ALL document classes are searchable:
878 **
879 **      if {[searchable c d t w]} {...}
880 **
881 ** But to see if ANY document class is searchable:
882 **
883 **      if {[searchable cdtw]} {...}
884 **
885 ** This command is useful for enabling or disabling a "Search" entry
886 ** on the menu bar.
887 */
searchableCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)888 static int searchableCmd(
889   Th_Interp *interp,
890   void *p,
891   int argc,
892   const char **argv,
893   int *argl
894 ){
895   int rc = 1, i, j;
896   unsigned int searchCap = search_restrict(SRCH_ALL);
897   if( argc<2 ){
898     return Th_WrongNumArgs(interp, "hascap STRING ...");
899   }
900   for(i=1; i<argc && rc; i++){
901     int match = 0;
902     for(j=0; j<argl[i]; j++){
903       switch( argv[i][j] ){
904         case 'c':  match |= searchCap & SRCH_CKIN;  break;
905         case 'd':  match |= searchCap & SRCH_DOC;   break;
906         case 't':  match |= searchCap & SRCH_TKT;   break;
907         case 'w':  match |= searchCap & SRCH_WIKI;  break;
908       }
909     }
910     if( !match ) rc = 0;
911   }
912   if( g.thTrace ){
913     Th_Trace("[searchable %#h] => %d<br />\n", argl[1], argv[1], rc);
914   }
915   Th_SetResultInt(interp, rc);
916   return TH_OK;
917 }
918 
919 /*
920 ** TH1 command: hasfeature STRING
921 **
922 ** Return true if the fossil binary has the given compile-time feature
923 ** enabled. The set of features includes:
924 **
925 ** "ssl"             = FOSSIL_ENABLE_SSL
926 ** "legacyMvRm"      = FOSSIL_ENABLE_LEGACY_MV_RM
927 ** "execRelPaths"    = FOSSIL_ENABLE_EXEC_REL_PATHS
928 ** "th1Docs"         = FOSSIL_ENABLE_TH1_DOCS
929 ** "th1Hooks"        = FOSSIL_ENABLE_TH1_HOOKS
930 ** "tcl"             = FOSSIL_ENABLE_TCL
931 ** "useTclStubs"     = USE_TCL_STUBS
932 ** "tclStubs"        = FOSSIL_ENABLE_TCL_STUBS
933 ** "tclPrivateStubs" = FOSSIL_ENABLE_TCL_PRIVATE_STUBS
934 ** "json"            = FOSSIL_ENABLE_JSON
935 ** "markdown"        = FOSSIL_ENABLE_MARKDOWN
936 ** "unicodeCmdLine"  = !BROKEN_MINGW_CMDLINE
937 ** "dynamicBuild"    = FOSSIL_DYNAMIC_BUILD
938 ** "mman"            = USE_MMAN_H
939 ** "see"             = USE_SEE
940 **
941 ** Specifying an unknown feature will return a value of false, it will not
942 ** raise a script error.
943 */
hasfeatureCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)944 static int hasfeatureCmd(
945   Th_Interp *interp,
946   void *p,
947   int argc,
948   const char **argv,
949   int *argl
950 ){
951   int rc = 0;
952   const char *zArg;
953   if( argc!=2 ){
954     return Th_WrongNumArgs(interp, "hasfeature STRING");
955   }
956   zArg = (const char *)argv[1];
957   if(NULL==zArg){
958     /* placeholder for following ifdefs... */
959   }
960 #if defined(FOSSIL_ENABLE_SSL)
961   else if( 0 == fossil_strnicmp( zArg, "ssl\0", 4 ) ){
962     rc = 1;
963   }
964 #endif
965   else if( 0 == fossil_strnicmp( zArg, "legacyMvRm\0", 11 ) ){
966     rc = 1;
967   }
968 #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
969   else if( 0 == fossil_strnicmp( zArg, "execRelPaths\0", 13 ) ){
970     rc = 1;
971   }
972 #endif
973 #if defined(FOSSIL_ENABLE_TH1_DOCS)
974   else if( 0 == fossil_strnicmp( zArg, "th1Docs\0", 8 ) ){
975     rc = 1;
976   }
977 #endif
978 #if defined(FOSSIL_ENABLE_TH1_HOOKS)
979   else if( 0 == fossil_strnicmp( zArg, "th1Hooks\0", 9 ) ){
980     rc = 1;
981   }
982 #endif
983 #if defined(FOSSIL_ENABLE_TCL)
984   else if( 0 == fossil_strnicmp( zArg, "tcl\0", 4 ) ){
985     rc = 1;
986   }
987 #endif
988 #if defined(USE_TCL_STUBS)
989   else if( 0 == fossil_strnicmp( zArg, "useTclStubs\0", 12 ) ){
990     rc = 1;
991   }
992 #endif
993 #if defined(FOSSIL_ENABLE_TCL_STUBS)
994   else if( 0 == fossil_strnicmp( zArg, "tclStubs\0", 9 ) ){
995     rc = 1;
996   }
997 #endif
998 #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
999   else if( 0 == fossil_strnicmp( zArg, "tclPrivateStubs\0", 16 ) ){
1000     rc = 1;
1001   }
1002 #endif
1003 #if defined(FOSSIL_ENABLE_JSON)
1004   else if( 0 == fossil_strnicmp( zArg, "json\0", 5 ) ){
1005     rc = 1;
1006   }
1007 #endif
1008 #if !defined(BROKEN_MINGW_CMDLINE)
1009   else if( 0 == fossil_strnicmp( zArg, "unicodeCmdLine\0", 15 ) ){
1010     rc = 1;
1011   }
1012 #endif
1013 #if defined(FOSSIL_DYNAMIC_BUILD)
1014   else if( 0 == fossil_strnicmp( zArg, "dynamicBuild\0", 13 ) ){
1015     rc = 1;
1016   }
1017 #endif
1018 #if defined(USE_MMAN_H)
1019   else if( 0 == fossil_strnicmp( zArg, "mman\0", 5 ) ){
1020     rc = 1;
1021   }
1022 #endif
1023 #if defined(USE_SEE)
1024   else if( 0 == fossil_strnicmp( zArg, "see\0", 4 ) ){
1025     rc = 1;
1026   }
1027 #endif
1028   else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1029     rc = 1;
1030   }
1031   if( g.thTrace ){
1032     Th_Trace("[hasfeature %#h] => %d<br />\n", argl[1], zArg, rc);
1033   }
1034   Th_SetResultInt(interp, rc);
1035   return TH_OK;
1036 }
1037 
1038 
1039 /*
1040 ** TH1 command: tclReady
1041 **
1042 ** Return true if the fossil binary has the Tcl integration feature
1043 ** enabled and it is currently available for use by TH1 scripts.
1044 **
1045 */
tclReadyCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1046 static int tclReadyCmd(
1047   Th_Interp *interp,
1048   void *p,
1049   int argc,
1050   const char **argv,
1051   int *argl
1052 ){
1053   int rc = 0;
1054   if( argc!=1 ){
1055     return Th_WrongNumArgs(interp, "tclReady");
1056   }
1057 #if defined(FOSSIL_ENABLE_TCL)
1058   if( g.tcl.interp ){
1059     rc = 1;
1060   }
1061 #endif
1062   if( g.thTrace ){
1063     Th_Trace("[tclReady] => %d<br />\n", rc);
1064   }
1065   Th_SetResultInt(interp, rc);
1066   return TH_OK;
1067 }
1068 
1069 
1070 /*
1071 ** TH1 command: anycap STRING
1072 **
1073 ** Return true if the current user user
1074 ** has any one of the capabilities listed in STRING.
1075 */
anycapCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1076 static int anycapCmd(
1077   Th_Interp *interp,
1078   void *p,
1079   int argc,
1080   const char **argv,
1081   int *argl
1082 ){
1083   int rc = 0;
1084   int i;
1085   if( argc!=2 ){
1086     return Th_WrongNumArgs(interp, "anycap STRING");
1087   }
1088   for(i=0; rc==0 && i<argl[1]; i++){
1089     rc = login_has_capability((char*)&argv[1][i],1,0);
1090   }
1091   if( g.thTrace ){
1092     Th_Trace("[anycap %#h] => %d<br />\n", argl[1], argv[1], rc);
1093   }
1094   Th_SetResultInt(interp, rc);
1095   return TH_OK;
1096 }
1097 
1098 /*
1099 ** TH1 command: combobox NAME TEXT-LIST NUMLINES
1100 **
1101 ** Generate an HTML combobox.  NAME is both the name of the
1102 ** CGI parameter and the name of a variable that contains the
1103 ** currently selected value.  TEXT-LIST is a list of possible
1104 ** values for the combobox.  NUMLINES is 1 for a true combobox.
1105 ** If NUMLINES is greater than one then the display is a listbox
1106 ** with the number of lines given.
1107 */
comboboxCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1108 static int comboboxCmd(
1109   Th_Interp *interp,
1110   void *p,
1111   int argc,
1112   const char **argv,
1113   int *argl
1114 ){
1115   if( argc!=4 ){
1116     return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1117   }
1118   if( enableOutput ){
1119     int height;
1120     Blob name;
1121     int nValue;
1122     const char *zValue;
1123     char *z, *zH;
1124     int nElem;
1125     int *aszElem;
1126     char **azElem;
1127     int i;
1128 
1129     if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1130     Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
1131     blob_init(&name, (char*)argv[1], argl[1]);
1132     zValue = Th_Fetch(blob_str(&name), &nValue);
1133     zH = htmlize(blob_buffer(&name), blob_size(&name));
1134     z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1135     free(zH);
1136     sendText(0,z, -1, 0);
1137     free(z);
1138     blob_reset(&name);
1139     for(i=0; i<nElem; i++){
1140       zH = htmlize((char*)azElem[i], aszElem[i]);
1141       if( zValue && aszElem[i]==nValue
1142              && memcmp(zValue, azElem[i], nValue)==0 ){
1143         z = mprintf("<option value=\"%s\" selected=\"selected\">%s</option>",
1144                      zH, zH);
1145       }else{
1146         z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
1147       }
1148       free(zH);
1149       sendText(0,z, -1, 0);
1150       free(z);
1151     }
1152     sendText(0,"</select>", -1, 0);
1153     Th_Free(interp, azElem);
1154   }
1155   return TH_OK;
1156 }
1157 
1158 /*
1159 ** TH1 command: copybtn TARGETID FLIPPED TEXT ?COPYLENGTH?
1160 **
1161 ** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
1162 ** Javascript module, and generates HTML elements with the following IDs:
1163 **
1164 **    TARGETID:       The <span> wrapper around TEXT.
1165 **    copy-TARGETID:  The <span> for the copy button.
1166 **
1167 ** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
1168 **
1169 ** The optional COPYLENGTH argument defines the length of the substring of TEXT
1170 ** copied to clipboard:
1171 **
1172 **    <= 0:   No limit (default if the argument is omitted).
1173 **    >= 3:   Truncate TEXT after COPYLENGTH (single-byte) characters.
1174 **       1:   Use the "hash-digits" setting as the limit.
1175 **       2:   Use the length appropriate for URLs as the limit (defined at
1176 **            compile-time by FOSSIL_HASH_DIGITS_URL, defaults to 16).
1177 */
copybtnCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1178 static int copybtnCmd(
1179   Th_Interp *interp,
1180   void *p,
1181   int argc,
1182   const char **argv,
1183   int *argl
1184 ){
1185   if( argc!=4 && argc!=5 ){
1186     return Th_WrongNumArgs(interp,
1187                            "copybtn TARGETID FLIPPED TEXT ?COPYLENGTH?");
1188   }
1189   if( enableOutput ){
1190     int flipped = 0;
1191     int copylength = 0;
1192     char *zResult;
1193     if( Th_ToInt(interp, argv[2], argl[2], &flipped) ) return TH_ERROR;
1194     if( argc==5 ){
1195       if( Th_ToInt(interp, argv[4], argl[4], &copylength) ) return TH_ERROR;
1196     }
1197     zResult = style_copy_button(
1198                 /*bOutputCGI==*/0, /*TARGETID==*/(char*)argv[1],
1199                 flipped, copylength, "%h", /*TEXT==*/(char*)argv[3]);
1200     sendText(0,zResult, -1, 0);
1201     free(zResult);
1202   }
1203   return TH_OK;
1204 }
1205 
1206 /*
1207 ** TH1 command: linecount STRING MAX MIN
1208 **
1209 ** Return one more than the number of \n characters in STRING.  But
1210 ** never return less than MIN or more than MAX.
1211 */
linecntCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1212 static int linecntCmd(
1213   Th_Interp *interp,
1214   void *p,
1215   int argc,
1216   const char **argv,
1217   int *argl
1218 ){
1219   const char *z;
1220   int size, n, i;
1221   int iMin, iMax;
1222   if( argc!=4 ){
1223     return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1224   }
1225   if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1226   if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1227   z = argv[1];
1228   size = argl[1];
1229   for(n=1, i=0; i<size; i++){
1230     if( z[i]=='\n' ){
1231       n++;
1232       if( n>=iMax ) break;
1233     }
1234   }
1235   if( n<iMin ) n = iMin;
1236   if( n>iMax ) n = iMax;
1237   Th_SetResultInt(interp, n);
1238   return TH_OK;
1239 }
1240 
1241 /*
1242 ** TH1 command: repository ?BOOLEAN?
1243 **
1244 ** Return the fully qualified file name of the open repository or an empty
1245 ** string if one is not currently open.  Optionally, it will attempt to open
1246 ** the repository if the boolean argument is non-zero.
1247 */
repositoryCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1248 static int repositoryCmd(
1249   Th_Interp *interp,
1250   void *p,
1251   int argc,
1252   const char **argv,
1253   int *argl
1254 ){
1255   if( argc!=1 && argc!=2 ){
1256     return Th_WrongNumArgs(interp, "repository ?BOOLEAN?");
1257   }
1258   if( argc==2 ){
1259     int openRepository = 0;
1260     if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){
1261       return TH_ERROR;
1262     }
1263     if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0);
1264   }
1265   Th_SetResult(interp, g.zRepositoryName, -1);
1266   return TH_OK;
1267 }
1268 
1269 /*
1270 ** TH1 command: checkout ?BOOLEAN?
1271 **
1272 ** Return the fully qualified directory name of the current checkout or an
1273 ** empty string if it is not available.  Optionally, it will attempt to find
1274 ** the current checkout, opening the configuration ("user") database and the
1275 ** repository as necessary, if the boolean argument is non-zero.
1276 */
checkoutCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1277 static int checkoutCmd(
1278   Th_Interp *interp,
1279   void *p,
1280   int argc,
1281   const char **argv,
1282   int *argl
1283 ){
1284   if( argc!=1 && argc!=2 ){
1285     return Th_WrongNumArgs(interp, "checkout ?BOOLEAN?");
1286   }
1287   if( argc==2 ){
1288     int openCheckout = 0;
1289     if( Th_ToInt(interp, argv[1], argl[1], &openCheckout) ){
1290       return TH_ERROR;
1291     }
1292     if( openCheckout ) db_open_local(0);
1293   }
1294   Th_SetResult(interp, g.zLocalRoot, -1);
1295   return TH_OK;
1296 }
1297 
1298 /*
1299 ** TH1 command: trace STRING
1300 **
1301 ** Generate a TH1 trace message if debugging is enabled.
1302 */
traceCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1303 static int traceCmd(
1304   Th_Interp *interp,
1305   void *p,
1306   int argc,
1307   const char **argv,
1308   int *argl
1309 ){
1310   if( argc!=2 ){
1311     return Th_WrongNumArgs(interp, "trace STRING");
1312   }
1313   if( g.thTrace ){
1314     Th_Trace("%s", argv[1]);
1315   }
1316   Th_SetResult(interp, 0, 0);
1317   return TH_OK;
1318 }
1319 
1320 /*
1321 ** TH1 command: globalState NAME ?DEFAULT?
1322 **
1323 ** Returns a string containing the value of the specified global state
1324 ** variable -OR- the specified default value.  Currently, the supported
1325 ** items are:
1326 **
1327 ** "checkout"        = The active local checkout directory, if any.
1328 ** "configuration"   = The active configuration database file name,
1329 **                     if any.
1330 ** "executable"      = The fully qualified executable file name.
1331 ** "flags"           = The TH1 initialization flags.
1332 ** "log"             = The error log file name, if any.
1333 ** "repository"      = The active local repository file name, if
1334 **                     any.
1335 ** "top"             = The base path for the active server instance,
1336 **                     if applicable.
1337 ** "user"            = The active user name, if any.
1338 ** "vfs"             = The SQLite VFS in use, if overridden.
1339 **
1340 ** Attempts to query for unsupported global state variables will result
1341 ** in a script error.  Additional global state variables may be exposed
1342 ** in the future.
1343 **
1344 ** See also: checkout, repository, setting
1345 */
globalStateCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1346 static int globalStateCmd(
1347   Th_Interp *interp,
1348   void *p,
1349   int argc,
1350   const char **argv,
1351   int *argl
1352 ){
1353   const char *zDefault = 0;
1354   if( argc!=2 && argc!=3 ){
1355     return Th_WrongNumArgs(interp, "globalState NAME ?DEFAULT?");
1356   }
1357   if( argc==3 ){
1358     zDefault = argv[2];
1359   }
1360   if( fossil_strnicmp(argv[1], "checkout\0", 9)==0 ){
1361     Th_SetResult(interp, g.zLocalRoot ? g.zLocalRoot : zDefault, -1);
1362     return TH_OK;
1363   }else if( fossil_strnicmp(argv[1], "configuration\0", 14)==0 ){
1364     Th_SetResult(interp, g.zConfigDbName ? g.zConfigDbName : zDefault, -1);
1365     return TH_OK;
1366   }else if( fossil_strnicmp(argv[1], "executable\0", 11)==0 ){
1367     Th_SetResult(interp, g.nameOfExe ? g.nameOfExe : zDefault, -1);
1368     return TH_OK;
1369   }else if( fossil_strnicmp(argv[1], "flags\0", 6)==0 ){
1370     Th_SetResultInt(interp, g.th1Flags);
1371     return TH_OK;
1372   }else if( fossil_strnicmp(argv[1], "log\0", 4)==0 ){
1373     Th_SetResult(interp, g.zErrlog ? g.zErrlog : zDefault, -1);
1374     return TH_OK;
1375   }else if( fossil_strnicmp(argv[1], "repository\0", 11)==0 ){
1376     Th_SetResult(interp, g.zRepositoryName ? g.zRepositoryName : zDefault, -1);
1377     return TH_OK;
1378   }else if( fossil_strnicmp(argv[1], "top\0", 4)==0 ){
1379     Th_SetResult(interp, g.zTop ? g.zTop : zDefault, -1);
1380     return TH_OK;
1381   }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
1382     Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
1383     return TH_OK;
1384   }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1385     Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1386     return TH_OK;
1387   }else{
1388     Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
1389     return TH_ERROR;
1390   }
1391 }
1392 
1393 /*
1394 ** TH1 command: getParameter NAME ?DEFAULT?
1395 **
1396 ** Return the value of the specified query parameter or the specified default
1397 ** value when there is no matching query parameter.
1398 */
getParameterCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1399 static int getParameterCmd(
1400   Th_Interp *interp,
1401   void *p,
1402   int argc,
1403   const char **argv,
1404   int *argl
1405 ){
1406   const char *zDefault = 0;
1407   if( argc!=2 && argc!=3 ){
1408     return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1409   }
1410   if( argc==3 ){
1411     zDefault = argv[2];
1412   }
1413   Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
1414   return TH_OK;
1415 }
1416 
1417 /*
1418 ** TH1 command: setParameter NAME VALUE
1419 **
1420 ** Sets the value of the specified query parameter.
1421 */
setParameterCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1422 static int setParameterCmd(
1423   Th_Interp *interp,
1424   void *p,
1425   int argc,
1426   const char **argv,
1427   int *argl
1428 ){
1429   if( argc!=3 ){
1430     return Th_WrongNumArgs(interp, "setParameter NAME VALUE");
1431   }
1432   cgi_replace_parameter(mprintf("%s", argv[1]), mprintf("%s", argv[2]));
1433   return TH_OK;
1434 }
1435 
1436 /*
1437 ** TH1 command: reinitialize ?FLAGS?
1438 **
1439 ** Reinitializes the TH1 interpreter using the specified flags.
1440 */
reinitializeCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1441 static int reinitializeCmd(
1442   Th_Interp *interp,
1443   void *p,
1444   int argc,
1445   const char **argv,
1446   int *argl
1447 ){
1448   u32 flags = TH_INIT_DEFAULT;
1449   if( argc!=1 && argc!=2 ){
1450     return Th_WrongNumArgs(interp, "reinitialize ?FLAGS?");
1451   }
1452   if( argc==2 ){
1453     int iFlags;
1454     if( Th_ToInt(interp, argv[1], argl[1], &iFlags) ){
1455       return TH_ERROR;
1456     }else{
1457       flags = (u32)iFlags;
1458     }
1459   }
1460   Th_FossilInit(flags & ~TH_INIT_FORBID_MASK);
1461   Th_SetResult(interp, 0, 0);
1462   return TH_OK;
1463 }
1464 
1465 /*
1466 ** TH1 command: render STRING
1467 **
1468 ** Renders the template and writes the results.
1469 */
renderCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1470 static int renderCmd(
1471   Th_Interp *interp,
1472   void *p,
1473   int argc,
1474   const char **argv,
1475   int *argl
1476 ){
1477   int rc;
1478   if( argc!=2 ){
1479     return Th_WrongNumArgs(interp, "render STRING");
1480   }
1481   rc = Th_Render(argv[1]);
1482   Th_SetResult(interp, 0, 0);
1483   return rc;
1484 }
1485 
1486 /*
1487 ** TH1 command: defHeader TITLE
1488 **
1489 ** Returns the default page header.
1490 */
defHeaderCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1491 static int defHeaderCmd(
1492   Th_Interp *interp,
1493   void *p,
1494   int argc,
1495   const char **argv,
1496   int *argl
1497 ){
1498   if( argc!=1 ){
1499     return Th_WrongNumArgs(interp, "defHeader");
1500   }
1501   Th_SetResult(interp, get_default_header(), -1);
1502   return TH_OK;
1503 }
1504 
1505 /*
1506 ** TH1 command: styleHeader TITLE
1507 **
1508 ** Render the configured style header for the selected skin.
1509 */
styleHeaderCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1510 static int styleHeaderCmd(
1511   Th_Interp *interp,
1512   void *p,
1513   int argc,
1514   const char **argv,
1515   int *argl
1516 ){
1517   if( argc!=2 ){
1518     return Th_WrongNumArgs(interp, "styleHeader TITLE");
1519   }
1520   if( Th_IsRepositoryOpen() ){
1521     style_header("%s", argv[1]);
1522     Th_SetResult(interp, 0, 0);
1523     return TH_OK;
1524   }else{
1525     Th_SetResult(interp, "repository unavailable", -1);
1526     return TH_ERROR;
1527   }
1528 }
1529 
1530 /*
1531 ** TH1 command: styleFooter
1532 **
1533 ** Render the configured style footer for the selected skin.
1534 */
styleFooterCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1535 static int styleFooterCmd(
1536   Th_Interp *interp,
1537   void *p,
1538   int argc,
1539   const char **argv,
1540   int *argl
1541 ){
1542   if( argc!=1 ){
1543     return Th_WrongNumArgs(interp, "styleFooter");
1544   }
1545   if( Th_IsRepositoryOpen() ){
1546     style_finish_page();
1547     Th_SetResult(interp, 0, 0);
1548     return TH_OK;
1549   }else{
1550     Th_SetResult(interp, "repository unavailable", -1);
1551     return TH_ERROR;
1552   }
1553 }
1554 
1555 /*
1556 ** TH1 command: styleScript ?BUILTIN-FILENAME?
1557 **
1558 ** Render the js.txt file from the current skin.  Or, if an argument
1559 ** is supplied, render the built-in filename given.
1560 **
1561 ** By "rendering" we mean that the script is loaded and run through
1562 ** TH1 to expand variables and process <th1>...</th1> script.  Contrast
1563 ** with the "builtin_request_js BUILTIN-FILENAME" command which just
1564 ** loads the file as-is without interpretation.
1565 */
styleScriptCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1566 static int styleScriptCmd(
1567   Th_Interp *interp,
1568   void *p,
1569   int argc,
1570   const char **argv,
1571   int *argl
1572 ){
1573   if( argc!=1 && argc!=2 ){
1574     return Th_WrongNumArgs(interp, "styleScript ?BUILTIN_NAME?");
1575   }
1576   if( Th_IsRepositoryOpen() ){
1577     const char *zScript;
1578     if( argc==2 ){
1579       zScript = (const char*)builtin_file(argv[1], 0);
1580     }else{
1581       zScript = skin_get("js");
1582     }
1583     if( zScript==0 ) zScript = "";
1584     Th_Render(zScript);
1585     Th_SetResult(interp, 0, 0);
1586     return TH_OK;
1587   }else{
1588     Th_SetResult(interp, "repository unavailable", -1);
1589     return TH_ERROR;
1590   }
1591 }
1592 
1593 /*
1594 ** TH1 command: builtin_request_js NAME
1595 **
1596 ** Request that the built-in javascript file called NAME be added to the
1597 ** end of the generated page.
1598 **
1599 ** See also:  styleScript
1600 */
builtinRequestJsCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1601 static int builtinRequestJsCmd(
1602   Th_Interp *interp,
1603   void *p,
1604   int argc,
1605   const char **argv,
1606   int *argl
1607 ){
1608   if( argc!=2 ){
1609     return Th_WrongNumArgs(interp, "builtin_request_js NAME");
1610   }
1611   builtin_request_js(argv[1]);
1612   return TH_OK;
1613 }
1614 
1615 /*
1616 ** TH1 command: artifact ID ?FILENAME?
1617 **
1618 ** Attempts to locate the specified artifact and return its contents.  An
1619 ** error is generated if the repository is not open or the artifact cannot
1620 ** be found.
1621 */
artifactCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1622 static int artifactCmd(
1623   Th_Interp *interp,
1624   void *p,
1625   int argc,
1626   const char **argv,
1627   int *argl
1628 ){
1629   if( argc!=2 && argc!=3 ){
1630     return Th_WrongNumArgs(interp, "artifact ID ?FILENAME?");
1631   }
1632   if( Th_IsRepositoryOpen() ){
1633     int rid;
1634     Blob content;
1635     if( argc==3 ){
1636       rid = th1_artifact_from_ci_and_filename(interp, argv[1], argv[2]);
1637     }else{
1638       rid = th1_name_to_typed_rid(interp, argv[1], "*");
1639     }
1640     if( rid!=0 && content_get(rid, &content) ){
1641       Th_SetResult(interp, blob_str(&content), blob_size(&content));
1642       blob_reset(&content);
1643       return TH_OK;
1644     }else{
1645       return TH_ERROR;
1646     }
1647   }else{
1648     Th_SetResult(interp, "repository unavailable", -1);
1649     return TH_ERROR;
1650   }
1651 }
1652 
1653 /*
1654 ** TH1 command: cgiHeaderLine line
1655 **
1656 ** Adds the specified line to the CGI header.
1657 */
cgiHeaderLineCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1658 static int cgiHeaderLineCmd(
1659   Th_Interp *interp,
1660   void *p,
1661   int argc,
1662   const char **argv,
1663   int *argl
1664 ){
1665   if( argc!=2 ){
1666     return Th_WrongNumArgs(interp, "cgiHeaderLine line");
1667   }
1668   cgi_append_header(argv[1]);
1669   return TH_OK;
1670 }
1671 
1672 /*
1673 ** TH1 command: unversioned content FILENAME
1674 **
1675 ** Attempts to locate the specified unversioned file and return its contents.
1676 ** An error is generated if the repository is not open or the unversioned file
1677 ** cannot be found.
1678 */
unversionedContentCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1679 static int unversionedContentCmd(
1680   Th_Interp *interp,
1681   void *p,
1682   int argc,
1683   const char **argv,
1684   int *argl
1685 ){
1686   if( argc!=3 ){
1687     return Th_WrongNumArgs(interp, "unversioned content FILENAME");
1688   }
1689   if( Th_IsRepositoryOpen() ){
1690     Blob content;
1691     if( unversioned_content(argv[2], &content)!=0 ){
1692       Th_SetResult(interp, blob_str(&content), blob_size(&content));
1693       blob_reset(&content);
1694       return TH_OK;
1695     }else{
1696       return TH_ERROR;
1697     }
1698   }else{
1699     Th_SetResult(interp, "repository unavailable", -1);
1700     return TH_ERROR;
1701   }
1702 }
1703 
1704 /*
1705 ** TH1 command: unversioned list
1706 **
1707 ** Returns a list of the names of all unversioned files held in the local
1708 ** repository.  An error is generated if the repository is not open.
1709 */
unversionedListCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1710 static int unversionedListCmd(
1711   Th_Interp *interp,
1712   void *p,
1713   int argc,
1714   const char **argv,
1715   int *argl
1716 ){
1717   if( argc!=2 ){
1718     return Th_WrongNumArgs(interp, "unversioned list");
1719   }
1720   if( Th_IsRepositoryOpen() ){
1721     Stmt q;
1722     char *zList = 0;
1723     int nList = 0;
1724     db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
1725                    " ORDER BY name");
1726     while( db_step(&q)==SQLITE_ROW ){
1727       Th_ListAppend(interp, &zList, &nList, db_column_text(&q,0), -1);
1728     }
1729     db_finalize(&q);
1730     Th_SetResult(interp, zList, nList);
1731     Th_Free(interp, zList);
1732     return TH_OK;
1733   }else{
1734     Th_SetResult(interp, "repository unavailable", -1);
1735     return TH_ERROR;
1736   }
1737 }
1738 
unversionedCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1739 static int unversionedCmd(
1740   Th_Interp *interp,
1741   void *p,
1742   int argc,
1743   const char **argv,
1744   int *argl
1745 ){
1746   static const Th_SubCommand aSub[] = {
1747     { "content", unversionedContentCmd },
1748     { "list",    unversionedListCmd    },
1749     { 0, 0 }
1750   };
1751   return Th_CallSubCommand(interp, p, argc, argv, argl, aSub);
1752 }
1753 
1754 
1755 /*
1756 ** TH1 command: utime
1757 **
1758 ** Return the number of microseconds of CPU time consumed by the current
1759 ** process in user space.
1760 */
utimeCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1761 static int utimeCmd(
1762   Th_Interp *interp,
1763   void *p,
1764   int argc,
1765   const char **argv,
1766   int *argl
1767 ){
1768   sqlite3_uint64 x;
1769   char zUTime[50];
1770   fossil_cpu_times(&x, 0);
1771   sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1772   Th_SetResult(interp, zUTime, -1);
1773   return TH_OK;
1774 }
1775 
1776 /*
1777 ** TH1 command: stime
1778 **
1779 ** Return the number of microseconds of CPU time consumed by the current
1780 ** process in system space.
1781 */
stimeCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1782 static int stimeCmd(
1783   Th_Interp *interp,
1784   void *p,
1785   int argc,
1786   const char **argv,
1787   int *argl
1788 ){
1789   sqlite3_uint64 x;
1790   char zUTime[50];
1791   fossil_cpu_times(0, &x);
1792   sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1793   Th_SetResult(interp, zUTime, -1);
1794   return TH_OK;
1795 }
1796 
1797 
1798 /*
1799 ** TH1 command: randhex  N
1800 **
1801 ** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
1802 ** use a value of 10.
1803 */
randhexCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1804 static int randhexCmd(
1805   Th_Interp *interp,
1806   void *p,
1807   int argc,
1808   const char **argv,
1809   int *argl
1810 ){
1811   int n;
1812   unsigned char aRand[50];
1813   unsigned char zOut[100];
1814   if( argc!=1 && argc!=2 ){
1815     return Th_WrongNumArgs(interp, "repository ?BOOLEAN?");
1816   }
1817   if( argc==2 ){
1818     if( Th_ToInt(interp, argv[1], argl[1], &n) ){
1819       return TH_ERROR;
1820     }
1821     if( n<1 ) n = 1;
1822     if( n>sizeof(aRand) ) n = sizeof(aRand);
1823   }else{
1824     n = 10;
1825   }
1826   sqlite3_randomness(n, aRand);
1827   encode16(aRand, zOut, n);
1828   Th_SetResult(interp, (const char *)zOut, -1);
1829   return TH_OK;
1830 }
1831 
1832 /*
1833 ** Run sqlite3_step() while suppressing error messages sent to the
1834 ** rendered webpage or to the console.
1835 */
ignore_errors_step(sqlite3_stmt * pStmt)1836 static int ignore_errors_step(sqlite3_stmt *pStmt){
1837   int rc;
1838   g.dbIgnoreErrors++;
1839   rc = sqlite3_step(pStmt);
1840   g.dbIgnoreErrors--;
1841   return rc;
1842 }
1843 
1844 /*
1845 ** TH1 command: query [-nocomplain] SQL CODE
1846 **
1847 ** Run the SQL query given by the SQL argument.  For each row in the result
1848 ** set, run CODE.
1849 **
1850 ** In SQL, parameters such as $var are filled in using the value of variable
1851 ** "var".  Result values are stored in variables with the column name prior
1852 ** to each invocation of CODE.
1853 */
queryCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1854 static int queryCmd(
1855   Th_Interp *interp,
1856   void *p,
1857   int argc,
1858   const char **argv,
1859   int *argl
1860 ){
1861   sqlite3_stmt *pStmt;
1862   int rc;
1863   const char *zSql;
1864   int nSql;
1865   const char *zTail;
1866   int n, i;
1867   int res = TH_OK;
1868   int nVar;
1869   char *zErr = 0;
1870   int noComplain = 0;
1871 
1872   if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
1873     argc--;
1874     argv++;
1875     argl++;
1876     noComplain = 1;
1877   }
1878   if( argc!=3 ){
1879     return Th_WrongNumArgs(interp, "query SQL CODE");
1880   }
1881   if( g.db==0 ){
1882     if( noComplain ) return TH_OK;
1883     Th_ErrorMessage(interp, "database is not open", 0, 0);
1884     return TH_ERROR;
1885   }
1886   zSql = argv[1];
1887   nSql = argl[1];
1888   while( res==TH_OK && nSql>0 ){
1889     zErr = 0;
1890     report_restrict_sql(&zErr);
1891     g.dbIgnoreErrors++;
1892     rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1893     g.dbIgnoreErrors--;
1894     report_unrestrict_sql();
1895     if( rc!=0 || zErr!=0 ){
1896       if( noComplain ) return TH_OK;
1897       Th_ErrorMessage(interp, "SQL error: ",
1898                       zErr ? zErr : sqlite3_errmsg(g.db), -1);
1899       return TH_ERROR;
1900     }
1901     n = (int)(zTail - zSql);
1902     zSql += n;
1903     nSql -= n;
1904     if( pStmt==0 ) continue;
1905     nVar = sqlite3_bind_parameter_count(pStmt);
1906     for(i=1; i<=nVar; i++){
1907       const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
1908       int szVar = zVar ? th_strlen(zVar) : 0;
1909       if( szVar>1 && zVar[0]=='$'
1910        && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1911         int nVal;
1912         const char *zVal = Th_GetResult(interp, &nVal);
1913         sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1914       }
1915     }
1916     while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
1917       int nCol = sqlite3_column_count(pStmt);
1918       for(i=0; i<nCol; i++){
1919         const char *zCol = sqlite3_column_name(pStmt, i);
1920         int szCol = th_strlen(zCol);
1921         const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
1922         int szVal = sqlite3_column_bytes(pStmt, i);
1923         Th_SetVar(interp, zCol, szCol, zVal, szVal);
1924       }
1925       if( g.thTrace ){
1926         Th_Trace("query_eval {<pre>%#h</pre>}<br />\n", argl[2], argv[2]);
1927       }
1928       res = Th_Eval(interp, 0, argv[2], argl[2]);
1929       if( g.thTrace ){
1930         int nTrRes;
1931         char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
1932         Th_Trace("[query_eval] => %h {%#h}<br />\n",
1933                  Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
1934       }
1935       if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
1936     }
1937     rc = sqlite3_finalize(pStmt);
1938     if( rc!=SQLITE_OK ){
1939       if( noComplain ) return TH_OK;
1940       Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
1941       return TH_ERROR;
1942     }
1943   }
1944   return res;
1945 }
1946 
1947 /*
1948 ** TH1 command: setting name
1949 **
1950 ** Gets and returns the value of the specified Fossil setting.
1951 */
1952 #define SETTING_WRONGNUMARGS "setting ?-strict? ?--? name"
settingCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1953 static int settingCmd(
1954   Th_Interp *interp,
1955   void *p,
1956   int argc,
1957   const char **argv,
1958   int *argl
1959 ){
1960   int rc;
1961   int strict = 0;
1962   int nArg = 1;
1963   char *zValue;
1964   if( argc<2 || argc>4 ){
1965     return Th_WrongNumArgs(interp, SETTING_WRONGNUMARGS);
1966   }
1967   if( fossil_strcmp(argv[nArg], "-strict")==0 ){
1968     strict = 1; nArg++;
1969   }
1970   if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
1971   if( nArg+1!=argc ){
1972     return Th_WrongNumArgs(interp, SETTING_WRONGNUMARGS);
1973   }
1974   zValue = db_get(argv[nArg], 0);
1975   if( zValue!=0 ){
1976     Th_SetResult(interp, zValue, -1);
1977     rc = TH_OK;
1978   }else if( strict ){
1979     Th_ErrorMessage(interp, "no value for setting \"", argv[nArg], -1);
1980     rc = TH_ERROR;
1981   }else{
1982     Th_SetResult(interp, 0, 0);
1983     rc = TH_OK;
1984   }
1985   if( g.thTrace ){
1986     Th_Trace("[setting %s%#h] => %d<br />\n", strict ? "strict " : "",
1987              argl[nArg], argv[nArg], rc);
1988   }
1989   return rc;
1990 }
1991 
1992 /*
1993 ** TH1 command: glob_match ?-one? ?--? patternList string
1994 **
1995 ** Checks the string against the specified glob pattern -OR- list of glob
1996 ** patterns and returns non-zero if there is a match.
1997 */
1998 #define GLOB_MATCH_WRONGNUMARGS "glob_match ?-one? ?--? patternList string"
globMatchCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)1999 static int globMatchCmd(
2000   Th_Interp *interp,
2001   void *p,
2002   int argc,
2003   const char **argv,
2004   int *argl
2005 ){
2006   int rc;
2007   int one = 0;
2008   int nArg = 1;
2009   Glob *pGlob = 0;
2010   if( argc<3 || argc>5 ){
2011     return Th_WrongNumArgs(interp, GLOB_MATCH_WRONGNUMARGS);
2012   }
2013   if( fossil_strcmp(argv[nArg], "-one")==0 ){
2014     one = 1; nArg++;
2015   }
2016   if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2017   if( nArg+2!=argc ){
2018     return Th_WrongNumArgs(interp, GLOB_MATCH_WRONGNUMARGS);
2019   }
2020   if( one ){
2021     Th_SetResultInt(interp, sqlite3_strglob(argv[nArg], argv[nArg+1])==0);
2022     rc = TH_OK;
2023   }else{
2024     pGlob = glob_create(argv[nArg]);
2025     if( pGlob ){
2026       Th_SetResultInt(interp, glob_match(pGlob, argv[nArg+1]));
2027       rc = TH_OK;
2028     }else{
2029       Th_SetResult(interp, "unable to create glob from pattern list", -1);
2030       rc = TH_ERROR;
2031     }
2032     glob_free(pGlob);
2033   }
2034   return rc;
2035 }
2036 
2037 /*
2038 ** TH1 command: regexp ?-nocase? ?--? exp string
2039 **
2040 ** Checks the string against the specified regular expression and returns
2041 ** non-zero if it matches.  If the regular expression is invalid or cannot
2042 ** be compiled, an error will be generated.
2043 */
2044 #define REGEXP_WRONGNUMARGS "regexp ?-nocase? ?--? exp string"
regexpCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)2045 static int regexpCmd(
2046   Th_Interp *interp,
2047   void *p,
2048   int argc,
2049   const char **argv,
2050   int *argl
2051 ){
2052   int rc;
2053   int noCase = 0;
2054   int nArg = 1;
2055   ReCompiled *pRe = 0;
2056   const char *zErr;
2057   if( argc<3 || argc>5 ){
2058     return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2059   }
2060   if( fossil_strcmp(argv[nArg], "-nocase")==0 ){
2061     noCase = 1; nArg++;
2062   }
2063   if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2064   if( nArg+2!=argc ){
2065     return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2066   }
2067   zErr = re_compile(&pRe, argv[nArg], noCase);
2068   if( !zErr ){
2069     Th_SetResultInt(interp, re_match(pRe,
2070         (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2071     rc = TH_OK;
2072   }else{
2073     Th_SetResult(interp, zErr, -1);
2074     rc = TH_ERROR;
2075   }
2076   re_free(pRe);
2077   return rc;
2078 }
2079 
2080 /*
2081 ** TH1 command: http ?-asynchronous? ?--? url ?payload?
2082 **
2083 ** Perform an HTTP or HTTPS request for the specified URL.  If a
2084 ** payload is present, it will be interpreted as text/plain and
2085 ** the POST method will be used; otherwise, the GET method will
2086 ** be used.  Upon success, if the -asynchronous option is used, an
2087 ** empty string is returned as the result; otherwise, the response
2088 ** from the server is returned as the result.  Synchronous requests
2089 ** are not currently implemented.
2090 */
2091 #define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?"
httpCmd(Th_Interp * interp,void * p,int argc,const char ** argv,int * argl)2092 static int httpCmd(
2093   Th_Interp *interp,
2094   void *p,
2095   int argc,
2096   const char **argv,
2097   int *argl
2098 ){
2099   int nArg = 1;
2100   int fAsynchronous = 0;
2101   const char *zType, *zRegexp;
2102   Blob payload;
2103   ReCompiled *pRe = 0;
2104   UrlData urlData;
2105 
2106   if( argc<2 || argc>5 ){
2107     return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2108   }
2109   if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2110     fAsynchronous = 1; nArg++;
2111   }
2112   if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2113   if( nArg+1!=argc && nArg+2!=argc ){
2114     return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2115   }
2116   memset(&urlData, '\0', sizeof(urlData));
2117   url_parse_local(argv[nArg], 0, &urlData);
2118   if( urlData.isSsh || urlData.isFile ){
2119     Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0);
2120     return TH_ERROR;
2121   }
2122   zRegexp = db_get("th1-uri-regexp", 0);
2123   if( zRegexp && zRegexp[0] ){
2124     const char *zErr = re_compile(&pRe, zRegexp, 0);
2125     if( zErr ){
2126       Th_SetResult(interp, zErr, -1);
2127       return TH_ERROR;
2128     }
2129   }
2130   if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){
2131     Th_SetResult(interp, "url not allowed", -1);
2132     re_free(pRe);
2133     return TH_ERROR;
2134   }
2135   re_free(pRe);
2136   blob_zero(&payload);
2137   if( nArg+2==argc ){
2138     blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2139     zType = "POST";
2140   }else{
2141     zType = "GET";
2142   }
2143   if( fAsynchronous ){
2144     const char *zSep, *zParams;
2145     Blob hdr;
2146     zParams = strrchr(argv[nArg], '?');
2147     if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){
2148       zSep = "";
2149     }else{
2150       zSep = "/";
2151     }
2152     blob_zero(&hdr);
2153     blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n",
2154                  zType, zSep, urlData.path, zParams ? zParams : "");
2155     if( urlData.proxyAuth ){
2156       blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth);
2157     }
2158     if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){
2159       char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]);
2160       char *zEncoded = encode64(zCredentials, -1);
2161       blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded);
2162       fossil_free(zEncoded);
2163       fossil_free(zCredentials);
2164     }
2165     blob_appendf(&hdr, "Host: %s\r\n"
2166         "User-Agent: %s\r\n", urlData.hostname, get_user_agent());
2167     if( zType[0]=='P' ){
2168       blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n"
2169           "Content-Length: %d\r\n\r\n", blob_size(&payload));
2170     }else{
2171       blob_appendf(&hdr, "\r\n");
2172     }
2173     if( transport_open(&urlData) ){
2174       Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0);
2175       blob_reset(&hdr);
2176       blob_reset(&payload);
2177       return TH_ERROR;
2178     }
2179     transport_send(&urlData, &hdr);
2180     transport_send(&urlData, &payload);
2181     blob_reset(&hdr);
2182     blob_reset(&payload);
2183     transport_close(&urlData);
2184     Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */
2185     return TH_OK;
2186   }else{
2187     Th_ErrorMessage(interp,
2188         "synchronous requests are not yet implemented", 0, 0);
2189     blob_reset(&payload);
2190     return TH_ERROR;
2191   }
2192 }
2193 
2194 /*
2195 ** TH1 command: captureTh1 STRING
2196 **
2197 ** Evaluates the given string as TH1 code and captures any of its
2198 ** TH1-generated output as a string (instead of it being output),
2199 ** which becomes the result of the function.
2200 */
captureTh1Cmd(Th_Interp * interp,void * pConvert,int argc,const char ** argv,int * argl)2201 static int captureTh1Cmd(
2202   Th_Interp *interp,
2203   void *pConvert,
2204   int argc,
2205   const char **argv,
2206   int *argl
2207 ){
2208   Blob out = empty_blob;
2209   Blob * pOrig;
2210   const char * zStr;
2211   int nStr, rc;
2212   if( argc!=2 ){
2213     return Th_WrongNumArgs(interp, "captureTh1 STRING");
2214   }
2215   pOrig = Th_SetOutputBlob(&out);
2216   zStr = argv[1];
2217   nStr = argl[1];
2218   rc = Th_Eval(g.interp, 0, zStr, nStr);
2219   Th_SetOutputBlob(pOrig);
2220   if(0==rc){
2221     Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2222   }
2223   blob_reset(&out);
2224   return rc;
2225 }
2226 
2227 
2228 /*
2229 ** Attempts to open the configuration ("user") database.  Optionally, also
2230 ** attempts to try to find the repository and open it.
2231 */
Th_OpenConfig(int openRepository)2232 void Th_OpenConfig(
2233   int openRepository
2234 ){
2235   if( openRepository && !Th_IsRepositoryOpen() ){
2236     db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0);
2237     if( Th_IsRepositoryOpen() ){
2238       g.th1Flags |= TH_STATE_REPOSITORY;
2239     }else{
2240       g.th1Flags &= ~TH_STATE_REPOSITORY;
2241     }
2242   }
2243   if( !Th_IsConfigOpen() ){
2244     db_open_config(0, 1);
2245     if( Th_IsConfigOpen() ){
2246       g.th1Flags |= TH_STATE_CONFIG;
2247     }else{
2248       g.th1Flags &= ~TH_STATE_CONFIG;
2249     }
2250   }
2251 }
2252 
2253 /*
2254 ** Attempts to close the configuration ("user") database.  Optionally, also
2255 ** attempts to close the repository.
2256 */
Th_CloseConfig(int closeRepository)2257 void Th_CloseConfig(
2258   int closeRepository
2259 ){
2260   if( g.th1Flags & TH_STATE_CONFIG ){
2261     db_close_config();
2262     g.th1Flags &= ~TH_STATE_CONFIG;
2263   }
2264   if( closeRepository && (g.th1Flags & TH_STATE_REPOSITORY) ){
2265     db_close(1);
2266     g.th1Flags &= ~TH_STATE_REPOSITORY;
2267   }
2268 }
2269 
2270 /*
2271 ** Make sure the interpreter has been initialized.  Initialize it if
2272 ** it has not been already.
2273 **
2274 ** The interpreter is stored in the g.interp global variable.
2275 */
Th_FossilInit(u32 flags)2276 void Th_FossilInit(u32 flags){
2277   int wasInit = 0;
2278   int needConfig = flags & TH_INIT_NEED_CONFIG;
2279   int forceReset = flags & TH_INIT_FORCE_RESET;
2280   int forceTcl = flags & TH_INIT_FORCE_TCL;
2281   int forceSetup = flags & TH_INIT_FORCE_SETUP;
2282   int noRepo = flags & TH_INIT_NO_REPO;
2283   static unsigned int aFlags[] = {0, 1, WIKI_LINKSONLY};
2284   static int anonFlag = LOGIN_ANON;
2285   static int zeroInt = 0;
2286   static struct _Command {
2287     const char *zName;
2288     Th_CommandProc xProc;
2289     void *pContext;
2290   } aCommand[] = {
2291     {"anoncap",       hascapCmd,            (void*)&anonFlag},
2292     {"anycap",        anycapCmd,            0},
2293     {"artifact",      artifactCmd,          0},
2294     {"builtin_request_js", builtinRequestJsCmd, 0},
2295     {"capexpr",       capexprCmd,           0},
2296     {"captureTh1",    captureTh1Cmd,        0},
2297     {"cgiHeaderLine", cgiHeaderLineCmd,     0},
2298     {"checkout",      checkoutCmd,          0},
2299     {"combobox",      comboboxCmd,          0},
2300     {"copybtn",       copybtnCmd,           0},
2301     {"date",          dateCmd,              0},
2302     {"decorate",      wikiCmd,              (void*)&aFlags[2]},
2303     {"defHeader",     defHeaderCmd,         0},
2304     {"dir",           dirCmd,               0},
2305     {"enable_htmlify",enableHtmlifyCmd,     0},
2306     {"enable_output", enableOutputCmd,      0},
2307     {"encode64",      encode64Cmd,          0},
2308     {"getParameter",  getParameterCmd,      0},
2309     {"glob_match",    globMatchCmd,         0},
2310     {"globalState",   globalStateCmd,       0},
2311     {"httpize",       httpizeCmd,           0},
2312     {"hascap",        hascapCmd,            (void*)&zeroInt},
2313     {"hasfeature",    hasfeatureCmd,        0},
2314     {"html",          putsCmd,              (void*)&aFlags[0]},
2315     {"htmlize",       htmlizeCmd,           0},
2316     {"http",          httpCmd,              0},
2317     {"insertCsrf",    insertCsrfCmd,        0},
2318     {"linecount",     linecntCmd,           0},
2319     {"markdown",      markdownCmd,          0},
2320     {"nonce",         nonceCmd,             0},
2321     {"puts",          putsCmd,              (void*)&aFlags[1]},
2322     {"query",         queryCmd,             0},
2323     {"randhex",       randhexCmd,           0},
2324     {"redirect",      redirectCmd,          0},
2325     {"regexp",        regexpCmd,            0},
2326     {"reinitialize",  reinitializeCmd,      0},
2327     {"render",        renderCmd,            0},
2328     {"repository",    repositoryCmd,        0},
2329     {"searchable",    searchableCmd,        0},
2330     {"setParameter",  setParameterCmd,      0},
2331     {"setting",       settingCmd,           0},
2332     {"styleFooter",   styleFooterCmd,       0},
2333     {"styleHeader",   styleHeaderCmd,       0},
2334     {"styleScript",   styleScriptCmd,       0},
2335     {"tclReady",      tclReadyCmd,          0},
2336     {"trace",         traceCmd,             0},
2337     {"stime",         stimeCmd,             0},
2338     {"unversioned",   unversionedCmd,       0},
2339     {"utime",         utimeCmd,             0},
2340     {"verifyCsrf",    verifyCsrfCmd,        0},
2341     {"verifyLogin",   verifyLoginCmd,       0},
2342     {"wiki",          wikiCmd,              (void*)&aFlags[0]},
2343     {0, 0, 0}
2344   };
2345   if( g.thTrace ){
2346     Th_Trace("th1-init 0x%x => 0x%x<br />\n", g.th1Flags, flags);
2347   }
2348   if( needConfig ){
2349     /*
2350     ** This function uses several settings which may be defined in the
2351     ** repository and/or the global configuration.  Since the caller
2352     ** passed a non-zero value for the needConfig parameter, make sure
2353     ** the necessary database connections are open prior to continuing.
2354     */
2355     Th_OpenConfig(!noRepo);
2356   }
2357   if( forceReset || forceTcl || g.interp==0 ){
2358     int created = 0;
2359     int i;
2360     if( g.interp==0 ){
2361       Th_Vtab *pVtab = 0;
2362 #if defined(TH_MEMDEBUG)
2363       if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
2364         pVtab = &vtab;
2365         if( g.thTrace ){
2366           Th_Trace("th1-init MEMDEBUG ENABLED<br />\n");
2367         }
2368       }
2369 #endif
2370       g.interp = Th_CreateInterp(pVtab);
2371       created = 1;
2372     }
2373     if( forceReset || created ){
2374       th_register_language(g.interp);     /* Basic scripting commands. */
2375     }
2376 #ifdef FOSSIL_ENABLE_TCL
2377     if( forceTcl || fossil_getenv("TH1_ENABLE_TCL")!=0 ||
2378         db_get_boolean("tcl", 0) ){
2379       if( !g.tcl.setup ){
2380         g.tcl.setup = db_get("tcl-setup", 0); /* Grab Tcl setup script. */
2381       }
2382       th_register_tcl(g.interp, &g.tcl);  /* Tcl integration commands. */
2383     }
2384 #endif
2385     for(i=0; i<count(aCommand); i++){
2386       if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
2387       Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc,
2388                        aCommand[i].pContext, 0);
2389     }
2390   }else{
2391     wasInit = 1;
2392   }
2393   if( forceSetup || !wasInit ){
2394     int rc = TH_OK;
2395     if( !g.th1Setup ){
2396       g.th1Setup = db_get("th1-setup", 0); /* Grab TH1 setup script. */
2397     }
2398     if( g.th1Setup ){
2399       rc = Th_Eval(g.interp, 0, g.th1Setup, -1);
2400       if( rc==TH_ERROR ){
2401         int nResult = 0;
2402         char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2403         sendError(0,zResult, nResult, 0);
2404       }
2405     }
2406     if( g.thTrace ){
2407       Th_Trace("th1-setup {%h} => %h<br />\n", g.th1Setup,
2408                Th_ReturnCodeName(rc, 0));
2409     }
2410   }
2411   g.th1Flags &= ~TH_INIT_MASK;
2412   g.th1Flags |= (flags & TH_INIT_MASK);
2413 }
2414 
2415 /*
2416 ** Store a string value in a variable in the interpreter if the variable
2417 ** does not already exist.
2418 */
Th_MaybeStore(const char * zName,const char * zValue)2419 void Th_MaybeStore(const char *zName, const char *zValue){
2420   Th_FossilInit(TH_INIT_DEFAULT);
2421   if( zValue && !Th_ExistsVar(g.interp, zName, -1) ){
2422     if( g.thTrace ){
2423       Th_Trace("maybe_set %h {%h}<br />\n", zName, zValue);
2424     }
2425     Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2426   }
2427 }
2428 
2429 /*
2430 ** Store a string value in a variable in the interpreter.
2431 */
Th_Store(const char * zName,const char * zValue)2432 void Th_Store(const char *zName, const char *zValue){
2433   Th_FossilInit(TH_INIT_DEFAULT);
2434   if( zValue ){
2435     if( g.thTrace ){
2436       Th_Trace("set %h {%h}<br />\n", zName, zValue);
2437     }
2438     Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2439   }
2440 }
2441 
2442 /*
2443 ** Appends an element to a TH1 list value.  This function is called by the
2444 ** transfer subsystem; therefore, it must be very careful to avoid doing
2445 ** any unnecessary work.  To that end, the TH1 subsystem will not be called
2446 ** or initialized if the list pointer is zero (i.e. which will be the case
2447 ** when TH1 transfer hooks are disabled).
2448 */
Th_AppendToList(char ** pzList,int * pnList,const char * zElem,int nElem)2449 void Th_AppendToList(
2450   char **pzList,
2451   int *pnList,
2452   const char *zElem,
2453   int nElem
2454 ){
2455   if( pzList && zElem ){
2456     Th_FossilInit(TH_INIT_DEFAULT);
2457     Th_ListAppend(g.interp, pzList, pnList, zElem, nElem);
2458   }
2459 }
2460 
2461 /*
2462 ** Stores a list value in the specified TH1 variable using the specified
2463 ** array of strings as the source of the element values.
2464 */
Th_StoreList(const char * zName,char ** pzList,int nList)2465 void Th_StoreList(
2466   const char *zName,
2467   char **pzList,
2468   int nList
2469 ){
2470   Th_FossilInit(TH_INIT_DEFAULT);
2471   if( pzList ){
2472     char *zValue = 0;
2473     int nValue = 0;
2474     int i;
2475     for(i=0; i<nList; i++){
2476       Th_ListAppend(g.interp, &zValue, &nValue, pzList[i], -1);
2477     }
2478     if( g.thTrace ){
2479       Th_Trace("set %h {%h}<br />\n", zName, zValue);
2480     }
2481     Th_SetVar(g.interp, zName, -1, zValue, nValue);
2482     Th_Free(g.interp, zValue);
2483   }
2484 }
2485 
2486 /*
2487 ** Store an integer value in a variable in the interpreter.
2488 */
Th_StoreInt(const char * zName,int iValue)2489 void Th_StoreInt(const char *zName, int iValue){
2490   Blob value;
2491   char *zValue;
2492   Th_FossilInit(TH_INIT_DEFAULT);
2493   blob_zero(&value);
2494   blob_appendf(&value, "%d", iValue);
2495   zValue = blob_str(&value);
2496   if( g.thTrace ){
2497     Th_Trace("set %h {%h}<br />\n", zName, zValue);
2498   }
2499   Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2500   blob_reset(&value);
2501 }
2502 
2503 /*
2504 ** Unset a variable.
2505 */
Th_Unstore(const char * zName)2506 void Th_Unstore(const char *zName){
2507   if( g.interp ){
2508     Th_UnsetVar(g.interp, (char*)zName, -1);
2509   }
2510 }
2511 
2512 /*
2513 ** Retrieve a string value from the interpreter.  If no such
2514 ** variable exists, return NULL.
2515 */
Th_Fetch(const char * zName,int * pSize)2516 char *Th_Fetch(const char *zName, int *pSize){
2517   int rc;
2518   Th_FossilInit(TH_INIT_DEFAULT);
2519   rc = Th_GetVar(g.interp, (char*)zName, -1);
2520   if( rc==TH_OK ){
2521     return (char*)Th_GetResult(g.interp, pSize);
2522   }else{
2523     return 0;
2524   }
2525 }
2526 
2527 /*
2528 ** Return true if the string begins with the TH1 begin-script
2529 ** tag:  <th1>.
2530 */
isBeginScriptTag(const char * z)2531 static int isBeginScriptTag(const char *z){
2532   return z[0]=='<'
2533       && (z[1]=='t' || z[1]=='T')
2534       && (z[2]=='h' || z[2]=='H')
2535       && z[3]=='1'
2536       && z[4]=='>';
2537 }
2538 
2539 /*
2540 ** Return true if the string begins with the TH1 end-script
2541 ** tag:  </th1>.
2542 */
isEndScriptTag(const char * z)2543 static int isEndScriptTag(const char *z){
2544   return z[0]=='<'
2545       && z[1]=='/'
2546       && (z[2]=='t' || z[2]=='T')
2547       && (z[3]=='h' || z[3]=='H')
2548       && z[4]=='1'
2549       && z[5]=='>';
2550 }
2551 
2552 /*
2553 ** If string z[0...] contains a valid variable name, return
2554 ** the number of characters in that name.  Otherwise, return 0.
2555 */
validVarName(const char * z)2556 static int validVarName(const char *z){
2557   int i = 0;
2558   int inBracket = 0;
2559   if( z[0]=='<' ){
2560     inBracket = 1;
2561     z++;
2562   }
2563   if( z[0]==':' && z[1]==':' && fossil_isalpha(z[2]) ){
2564     z += 3;
2565     i += 3;
2566   }else if( fossil_isalpha(z[0]) ){
2567     z ++;
2568     i += 1;
2569   }else{
2570     return 0;
2571   }
2572   while( fossil_isalnum(z[0]) || z[0]=='_' ){
2573     z++;
2574     i++;
2575   }
2576   if( inBracket ){
2577     if( z[0]!='>' ) return 0;
2578     i += 2;
2579   }
2580   return i;
2581 }
2582 
2583 #ifdef FOSSIL_ENABLE_TH1_HOOKS
2584 /*
2585 ** This function determines if TH1 hooks are enabled for the repository.  It
2586 ** may be necessary to open the repository and/or the configuration ("user")
2587 ** database from within this function.  Before this function returns, any
2588 ** database opened will be closed again.  This is very important because some
2589 ** commands do not expect the repository and/or the configuration ("user")
2590 ** database to be open prior to their own code doing so.
2591 */
Th_AreHooksEnabled(void)2592 int Th_AreHooksEnabled(void){
2593   int rc;
2594   if( fossil_getenv("TH1_ENABLE_HOOKS")!=0 ){
2595     return 1;
2596   }
2597   Th_OpenConfig(1);
2598   rc = db_get_boolean("th1-hooks", 0);
2599   Th_CloseConfig(1);
2600   return rc;
2601 }
2602 
2603 /*
2604 ** This function is called by Fossil just prior to dispatching a command.
2605 ** Returning a value other than TH_OK from this function (i.e. via an
2606 ** evaluated script raising an error or calling [break]/[continue]) will
2607 ** cause the actual command execution to be skipped.
2608 */
Th_CommandHook(const char * zName,unsigned int cmdFlags)2609 int Th_CommandHook(
2610   const char *zName,
2611   unsigned int cmdFlags
2612 ){
2613   int rc = TH_OK;
2614   if( !Th_AreHooksEnabled() ) return rc;
2615   Th_FossilInit(TH_INIT_HOOK);
2616   Th_Store("cmd_name", zName);
2617   Th_StoreList("cmd_args", g.argv, g.argc);
2618   Th_StoreInt("cmd_flags", cmdFlags);
2619   rc = Th_Eval(g.interp, 0, "command_hook", -1);
2620   if( rc==TH_ERROR ){
2621     int nResult = 0;
2622     char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2623     /*
2624     ** Make sure that the TH1 script error was not caused by a "missing"
2625     ** command hook handler as that is not actually an error condition.
2626     */
2627     if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2628       sendError(0,zResult, nResult, 0);
2629     }else{
2630       /*
2631       ** There is no command hook handler "installed".  This situation
2632       ** is NOT actually an error.
2633       */
2634       rc = TH_OK;
2635     }
2636   }
2637   /*
2638   ** If the script returned TH_ERROR (e.g. the "command_hook" TH1 command does
2639   ** not exist because commands are not being hooked), return TH_OK because we
2640   ** do not want to skip executing essential commands unless the called command
2641   ** (i.e. "command_hook") explicitly forbids this by successfully returning
2642   ** TH_BREAK or TH_CONTINUE.
2643   */
2644   if( g.thTrace ){
2645     Th_Trace("[command_hook {%h}] => %h<br />\n", zName,
2646              Th_ReturnCodeName(rc, 0));
2647   }
2648   /*
2649   ** Does our call to Th_FossilInit() result in opening a database?  If so,
2650   ** clean it up now.  This is very important because some commands do not
2651   ** expect the repository and/or the configuration ("user") database to be
2652   ** open prior to their own code doing so.
2653   */
2654   if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1);
2655   return rc;
2656 }
2657 
2658 /*
2659 ** This function is called by Fossil just after dispatching a command.
2660 ** Returning a value other than TH_OK from this function (i.e. via an
2661 ** evaluated script raising an error or calling [break]/[continue]) may
2662 ** cause an error message to be displayed to the local interactive user.
2663 ** Currently, TH1 error messages generated by this function are ignored.
2664 */
Th_CommandNotify(const char * zName,unsigned int cmdFlags)2665 int Th_CommandNotify(
2666   const char *zName,
2667   unsigned int cmdFlags
2668 ){
2669   int rc = TH_OK;
2670   if( !Th_AreHooksEnabled() ) return rc;
2671   Th_FossilInit(TH_INIT_HOOK);
2672   Th_Store("cmd_name", zName);
2673   Th_StoreList("cmd_args", g.argv, g.argc);
2674   Th_StoreInt("cmd_flags", cmdFlags);
2675   rc = Th_Eval(g.interp, 0, "command_notify", -1);
2676   if( g.thTrace ){
2677     Th_Trace("[command_notify {%h}] => %h<br />\n", zName,
2678              Th_ReturnCodeName(rc, 0));
2679   }
2680   /*
2681   ** Does our call to Th_FossilInit() result in opening a database?  If so,
2682   ** clean it up now.  This is very important because some commands do not
2683   ** expect the repository and/or the configuration ("user") database to be
2684   ** open prior to their own code doing so.
2685   */
2686   if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1);
2687   return rc;
2688 }
2689 
2690 /*
2691 ** This function is called by Fossil just prior to processing a web page.
2692 ** Returning a value other than TH_OK from this function (i.e. via an
2693 ** evaluated script raising an error or calling [break]/[continue]) will
2694 ** cause the actual web page processing to be skipped.
2695 */
Th_WebpageHook(const char * zName,unsigned int cmdFlags)2696 int Th_WebpageHook(
2697   const char *zName,
2698   unsigned int cmdFlags
2699 ){
2700   int rc = TH_OK;
2701   if( !Th_AreHooksEnabled() ) return rc;
2702   Th_FossilInit(TH_INIT_HOOK);
2703   Th_Store("web_name", zName);
2704   Th_StoreList("web_args", g.argv, g.argc);
2705   Th_StoreInt("web_flags", cmdFlags);
2706   rc = Th_Eval(g.interp, 0, "webpage_hook", -1);
2707   if( rc==TH_ERROR ){
2708     int nResult = 0;
2709     char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2710     /*
2711     ** Make sure that the TH1 script error was not caused by a "missing"
2712     ** webpage hook handler as that is not actually an error condition.
2713     */
2714     if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2715       sendError(0,zResult, nResult, 1);
2716     }else{
2717       /*
2718       ** There is no webpage hook handler "installed".  This situation
2719       ** is NOT actually an error.
2720       */
2721       rc = TH_OK;
2722     }
2723   }
2724   /*
2725   ** If the script returned TH_ERROR (e.g. the "webpage_hook" TH1 command does
2726   ** not exist because commands are not being hooked), return TH_OK because we
2727   ** do not want to skip processing essential web pages unless the called
2728   ** command (i.e. "webpage_hook") explicitly forbids this by successfully
2729   ** returning TH_BREAK or TH_CONTINUE.
2730   */
2731   if( g.thTrace ){
2732     Th_Trace("[webpage_hook {%h}] => %h<br />\n", zName,
2733              Th_ReturnCodeName(rc, 0));
2734   }
2735   /*
2736   ** Does our call to Th_FossilInit() result in opening a database?  If so,
2737   ** clean it up now.  This is very important because some commands do not
2738   ** expect the repository and/or the configuration ("user") database to be
2739   ** open prior to their own code doing so.
2740   */
2741   if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1);
2742   return rc;
2743 }
2744 
2745 /*
2746 ** This function is called by Fossil just after processing a web page.
2747 ** Returning a value other than TH_OK from this function (i.e. via an
2748 ** evaluated script raising an error or calling [break]/[continue]) may
2749 ** cause an error message to be displayed to the remote user.
2750 ** Currently, TH1 error messages generated by this function are ignored.
2751 */
Th_WebpageNotify(const char * zName,unsigned int cmdFlags)2752 int Th_WebpageNotify(
2753   const char *zName,
2754   unsigned int cmdFlags
2755 ){
2756   int rc = TH_OK;
2757   if( !Th_AreHooksEnabled() ) return rc;
2758   Th_FossilInit(TH_INIT_HOOK);
2759   Th_Store("web_name", zName);
2760   Th_StoreList("web_args", g.argv, g.argc);
2761   Th_StoreInt("web_flags", cmdFlags);
2762   rc = Th_Eval(g.interp, 0, "webpage_notify", -1);
2763   if( g.thTrace ){
2764     Th_Trace("[webpage_notify {%h}] => %h<br />\n", zName,
2765              Th_ReturnCodeName(rc, 0));
2766   }
2767   /*
2768   ** Does our call to Th_FossilInit() result in opening a database?  If so,
2769   ** clean it up now.  This is very important because some commands do not
2770   ** expect the repository and/or the configuration ("user") database to be
2771   ** open prior to their own code doing so.
2772   */
2773   if( TH_INIT_HOOK & TH_INIT_NEED_CONFIG ) Th_CloseConfig(1);
2774   return rc;
2775 }
2776 #endif
2777 
2778 
2779 #ifdef FOSSIL_ENABLE_TH1_DOCS
2780 /*
2781 ** This function determines if TH1 docs are enabled for the repository.
2782 */
Th_AreDocsEnabled(void)2783 int Th_AreDocsEnabled(void){
2784   if( fossil_getenv("TH1_ENABLE_DOCS")!=0 ){
2785     return 1;
2786   }
2787   return db_get_boolean("th1-docs", 0);
2788 }
2789 #endif
2790 
2791 
2792 #if INTERFACE
2793 /*
2794 ** Flags for use with Th_RenderToBlob. These must not overlap with
2795 ** TH_INIT_MASK.
2796 */
2797 #define TH_R2B_MASK    ((u32)0x0f000)
2798 #define TH_R2B_NO_VARS ((u32)0x01000) /* Disables eval of $vars and $<vars> */
2799 #endif
2800 
2801 /*
2802 ** If pOut is NULL, this works identically to Th_Render() and sends
2803 ** any TH1-generated output to stdin (in CLI mode) or the CGI buffer
2804 ** (in CGI mode), else it works just like that function but appends
2805 ** any TH1-generated output to the given blob. A bitmask of TH_R2B_xxx
2806 ** and/or TH_INIT_xxx flags may be passed as the 3rd argument, or 0
2807 ** for default options.  Note that this function necessarily calls
2808 ** Th_FossilInit(), which may unset flags used on previous calls
2809 ** unless mFlags is explicitly passed in.
2810 */
Th_RenderToBlob(const char * z,Blob * pOut,u32 mFlags)2811 int Th_RenderToBlob(const char *z, Blob * pOut, u32 mFlags){
2812   int i = 0;
2813   int n;
2814   int rc = TH_OK;
2815   char *zResult;
2816   Blob * const origOut = Th_SetOutputBlob(pOut);
2817 
2818   assert(0==(TH_R2B_MASK & TH_INIT_MASK) && "init/r2b mask conflict");
2819   Th_FossilInit(mFlags & TH_INIT_MASK);
2820   while( z[i] ){
2821     if( 0==(TH_R2B_NO_VARS & mFlags)
2822         && z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
2823       const char *zVar;
2824       int nVar;
2825       int encode = 1;
2826       sendText(pOut,z, i, 0);
2827       if( z[i+1]=='<' ){
2828         /* Variables of the form $<aaa> are html escaped */
2829         zVar = &z[i+2];
2830         nVar = n-2;
2831       }else{
2832         /* Variables of the form $aaa are output raw */
2833         zVar = &z[i+1];
2834         nVar = n;
2835         encode = 0;
2836       }
2837       rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2838       z += i+1+n;
2839       i = 0;
2840       zResult = (char*)Th_GetResult(g.interp, &n);
2841       sendText(pOut,(char*)zResult, n, encode);
2842     }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2843       sendText(pOut,z, i, 0);
2844       z += i+5;
2845       for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2846       if( g.thTrace ){
2847         Th_Trace("render_eval {<pre>%#h</pre>}<br />\n", i, z);
2848       }
2849       rc = Th_Eval(g.interp, 0, (const char*)z, i);
2850       if( g.thTrace ){
2851         int nTrRes;
2852         char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2853         Th_Trace("[render_eval] => %h {%#h}<br />\n",
2854                  Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2855       }
2856       if( rc!=TH_OK ) break;
2857       z += i;
2858       if( z[0] ){ z += 6; }
2859       i = 0;
2860     }else{
2861       i++;
2862     }
2863   }
2864   if( rc==TH_ERROR ){
2865     zResult = (char*)Th_GetResult(g.interp, &n);
2866     sendError(pOut,zResult, n, 1);
2867   }else{
2868     sendText(pOut,z, i, 0);
2869   }
2870   Th_SetOutputBlob(origOut);
2871   return rc;
2872 }
2873 
2874 /*
2875 ** The z[] input contains text mixed with TH1 scripts.
2876 ** The TH1 scripts are contained within <th1>...</th1>.
2877 ** TH1 variables are $aaa or $<aaa>.  The first form of
2878 ** variable is literal.  The second is run through htmlize
2879 ** before being inserted.
2880 **
2881 ** This routine processes the template and writes the results to one
2882 ** of stdout, CGI, or an internal blob which was set up via a prior
2883 ** call to Th_SetOutputBlob().
2884 */
Th_Render(const char * z)2885 int Th_Render(const char *z){
2886   return Th_RenderToBlob(z, pThOut, g.th1Flags)
2887     /* Maintenance reminder: on most calls to Th_Render(), e.g. for
2888     ** outputing the site skin, pThOut will be 0, which means that
2889     ** Th_RenderToBlob() will output directly to the CGI buffer (in
2890     ** CGI mode) or stdout (in CLI mode). Recursive calls, however,
2891     ** e.g. via the "render" script function binding, need to use the
2892     ** pThOut blob in order to avoid out-of-order output if
2893     ** Th_SetOutputBlob() has been called. If it has not been called,
2894     ** pThOut will be 0, which will redirect the output to CGI/stdout,
2895     ** as appropriate. We need to pass on g.th1Flags for the case of
2896     ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
2897     ** inadvertently toggled off by a recursive call.
2898     */;
2899 }
2900 
2901 /*
2902 ** COMMAND: test-th-render
2903 **
2904 ** Usage: %fossil test-th-render FILE
2905 **
2906 ** Read the content of the file named "FILE" as if it were a header or
2907 ** footer or ticket rendering script, evaluate it, and show the results
2908 ** on standard output.
2909 **
2910 ** Options:
2911 **
2912 **     --cgi                Include a CGI response header in the output
2913 **     --http               Include an HTTP response header in the output
2914 **     --open-config        Open the configuration database
2915 **     --set-anon-caps      Set anonymous login capabilities
2916 **     --set-user-caps      Set user login capabilities
2917 **     --th-trace           Trace TH1 execution (for debugging purposes)
2918 */
test_th_render(void)2919 void test_th_render(void){
2920   int forceCgi, fullHttpReply;
2921   Blob in;
2922   Th_InitTraceLog();
2923   forceCgi = find_option("cgi", 0, 0)!=0;
2924   fullHttpReply = find_option("http", 0, 0)!=0;
2925   if( fullHttpReply ) forceCgi = 1;
2926   if( forceCgi ) Th_ForceCgi(fullHttpReply);
2927   if( find_option("open-config", 0, 0)!=0 ){
2928     Th_OpenConfig(1);
2929   }
2930   if( find_option("set-anon-caps", 0, 0)!=0 ){
2931     const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS");
2932     login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON);
2933     g.useLocalauth = 1;
2934   }
2935   if( find_option("set-user-caps", 0, 0)!=0 ){
2936     const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
2937     login_set_capabilities(zCap ? zCap : "sx", 0);
2938     g.useLocalauth = 1;
2939   }
2940   verify_all_options();
2941   if( g.argc<3 ){
2942     usage("FILE");
2943   }
2944   blob_zero(&in);
2945   blob_read_from_file(&in, g.argv[2], ExtFILE);
2946   Th_Render(blob_str(&in));
2947   Th_PrintTraceLog();
2948   if( forceCgi ) cgi_reply();
2949 }
2950 
2951 /*
2952 ** COMMAND: test-th-eval
2953 **
2954 ** Usage: %fossil test-th-eval SCRIPT
2955 **
2956 ** Evaluate SCRIPT as if it were a header or footer or ticket rendering
2957 ** script and show the results on standard output. SCRIPT may be either
2958 ** a filename or a string of th1 script code.
2959 **
2960 ** Options:
2961 **
2962 **     --cgi                Include a CGI response header in the output
2963 **     --http               Include an HTTP response header in the output
2964 **     --open-config        Open the configuration database
2965 **     --set-anon-caps      Set anonymous login capabilities
2966 **     --set-user-caps      Set user login capabilities
2967 **     --th-trace           Trace TH1 execution (for debugging purposes)
2968 */
test_th_eval(void)2969 void test_th_eval(void){
2970   int rc;
2971   const char *zRc;
2972   const char *zCode = 0;
2973   int forceCgi, fullHttpReply;
2974   Blob code = empty_blob;
2975   Th_InitTraceLog();
2976   forceCgi = find_option("cgi", 0, 0)!=0;
2977   fullHttpReply = find_option("http", 0, 0)!=0;
2978   if( fullHttpReply ) forceCgi = 1;
2979   if( forceCgi ) Th_ForceCgi(fullHttpReply);
2980   if( find_option("open-config", 0, 0)!=0 ){
2981     Th_OpenConfig(1);
2982   }
2983   if( find_option("set-anon-caps", 0, 0)!=0 ){
2984     const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS");
2985     login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON);
2986     g.useLocalauth = 1;
2987   }
2988   if( find_option("set-user-caps", 0, 0)!=0 ){
2989     const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
2990     login_set_capabilities(zCap ? zCap : "sx", 0);
2991     g.useLocalauth = 1;
2992   }
2993   verify_all_options();
2994   if( g.argc!=3 ){
2995     usage("script");
2996   }
2997   if(file_isfile(g.argv[2], ExtFILE)){
2998     blob_read_from_file(&code, g.argv[2], ExtFILE);
2999     zCode = blob_str(&code);
3000   }else{
3001     zCode = g.argv[2];
3002   }
3003   Th_FossilInit(TH_INIT_DEFAULT);
3004   rc = Th_Eval(g.interp, 0, zCode, -1);
3005   zRc = Th_ReturnCodeName(rc, 1);
3006   fossil_print("%s%s%s\n", zRc, zRc ? ": " : "", Th_GetResult(g.interp, 0));
3007   Th_PrintTraceLog();
3008   blob_reset(&code);
3009   if( forceCgi ) cgi_reply();
3010 }
3011 
3012 /*
3013 ** COMMAND: test-th-source
3014 **
3015 ** Usage: %fossil test-th-source FILE
3016 **
3017 ** Evaluate the contents of the file named "FILE" as if it were a header
3018 ** or footer or ticket rendering script and show the results on standard
3019 ** output.
3020 **
3021 ** Options:
3022 **
3023 **     --cgi                Include a CGI response header in the output
3024 **     --http               Include an HTTP response header in the output
3025 **     --open-config        Open the configuration database
3026 **     --set-anon-caps      Set anonymous login capabilities
3027 **     --set-user-caps      Set user login capabilities
3028 **     --th-trace           Trace TH1 execution (for debugging purposes)
3029 **     --no-print-result    Do not output the final result. Use if it
3030 **                          interferes with script output.
3031 */
test_th_source(void)3032 void test_th_source(void){
3033   int rc;
3034   const char *zRc;
3035   int forceCgi, fullHttpReply, fNoPrintRc;
3036   Blob in;
3037   Th_InitTraceLog();
3038   forceCgi = find_option("cgi", 0, 0)!=0;
3039   fullHttpReply = find_option("http", 0, 0)!=0;
3040   fNoPrintRc = find_option("no-print-result",0,0)!=0;
3041   if( fullHttpReply ) forceCgi = 1;
3042   if( forceCgi ) Th_ForceCgi(fullHttpReply);
3043   if( find_option("open-config", 0, 0)!=0 ){
3044     Th_OpenConfig(1);
3045   }
3046   if( find_option("set-anon-caps", 0, 0)!=0 ){
3047     const char *zCap = fossil_getenv("TH1_TEST_ANON_CAPS");
3048     login_set_capabilities(zCap ? zCap : "sx", LOGIN_ANON);
3049     g.useLocalauth = 1;
3050   }
3051   if( find_option("set-user-caps", 0, 0)!=0 ){
3052     const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3053     login_set_capabilities(zCap ? zCap : "sx", 0);
3054     g.useLocalauth = 1;
3055   }
3056   verify_all_options();
3057   if( g.argc!=3 ){
3058     usage("file");
3059   }
3060   blob_zero(&in);
3061   blob_read_from_file(&in, g.argv[2], ExtFILE);
3062   Th_FossilInit(TH_INIT_DEFAULT);
3063   rc = Th_Eval(g.interp, 0, blob_str(&in), -1);
3064   zRc = Th_ReturnCodeName(rc, 1);
3065   if(0==fNoPrintRc){
3066     fossil_print("%s%s%s\n", zRc, zRc ? ": " : "",
3067                  Th_GetResult(g.interp, 0));
3068   }
3069   Th_PrintTraceLog();
3070   if( forceCgi ) cgi_reply();
3071 }
3072 
3073 #ifdef FOSSIL_ENABLE_TH1_HOOKS
3074 /*
3075 ** COMMAND: test-th-hook
3076 **
3077 ** Usage: %fossil test-th-hook TYPE NAME FLAGS
3078 **
3079 ** Evaluates the TH1 script configured for the pre-operation (i.e. a command
3080 ** or web page) "hook" or post-operation "notification".  The results of the
3081 ** script evaluation, if any, will be printed to the standard output channel.
3082 ** The NAME argument must be the name of a command or web page; however, it
3083 ** does not necessarily have to be a command or web page that is normally
3084 ** recognized by Fossil.  The FLAGS argument will be used to set the value
3085 ** of the "cmd_flags" and/or "web_flags" TH1 variables, if applicable.  The
3086 ** TYPE argument must be one of the following:
3087 **
3088 **     cmdhook              Executes the TH1 procedure [command_hook], after
3089 **                          setting the TH1 variables "cmd_name", "cmd_args",
3090 **                          and "cmd_flags" to appropriate values.
3091 **
3092 **     cmdnotify            Executes the TH1 procedure [command_notify], after
3093 **                          setting the TH1 variables "cmd_name", "cmd_args",
3094 **                          and "cmd_flags" to appropriate values.
3095 **
3096 **     webhook              Executes the TH1 procedure [webpage_hook], after
3097 **                          setting the TH1 variables "web_name", "web_args",
3098 **                          and "web_flags" to appropriate values.
3099 **
3100 **     webnotify            Executes the TH1 procedure [webpage_notify], after
3101 **                          setting the TH1 variables "web_name", "web_args",
3102 **                          and "web_flags" to appropriate values.
3103 **
3104 ** Options:
3105 **
3106 **     --cgi                Include a CGI response header in the output
3107 **     --http               Include an HTTP response header in the output
3108 **     --th-trace           Trace TH1 execution (for debugging purposes)
3109 */
test_th_hook(void)3110 void test_th_hook(void){
3111   int rc = TH_OK;
3112   int nResult = 0;
3113   char *zResult = 0;
3114   int forceCgi, fullHttpReply;
3115   Th_InitTraceLog();
3116   forceCgi = find_option("cgi", 0, 0)!=0;
3117   fullHttpReply = find_option("http", 0, 0)!=0;
3118   if( fullHttpReply ) forceCgi = 1;
3119   if( forceCgi ) Th_ForceCgi(fullHttpReply);
3120   verify_all_options();
3121   if( g.argc<5 ){
3122     usage("TYPE NAME FLAGS");
3123   }
3124   if( fossil_stricmp(g.argv[2], "cmdhook")==0 ){
3125     rc = Th_CommandHook(g.argv[3], (unsigned int)atoi(g.argv[4]));
3126   }else if( fossil_stricmp(g.argv[2], "cmdnotify")==0 ){
3127     rc = Th_CommandNotify(g.argv[3], (unsigned int)atoi(g.argv[4]));
3128   }else if( fossil_stricmp(g.argv[2], "webhook")==0 ){
3129     rc = Th_WebpageHook(g.argv[3], (unsigned int)atoi(g.argv[4]));
3130   }else if( fossil_stricmp(g.argv[2], "webnotify")==0 ){
3131     rc = Th_WebpageNotify(g.argv[3], (unsigned int)atoi(g.argv[4]));
3132   }else{
3133     fossil_fatal("Unknown TH1 hook %s", g.argv[2]);
3134   }
3135   if( g.interp ){
3136     zResult = (char*)Th_GetResult(g.interp, &nResult);
3137   }
3138   sendText(0,"RESULT (", -1, 0);
3139   sendText(0,Th_ReturnCodeName(rc, 0), -1, 0);
3140   sendText(0,")", -1, 0);
3141   if( zResult && nResult>0 ){
3142     sendText(0,": ", -1, 0);
3143     sendText(0,zResult, nResult, 0);
3144   }
3145   sendText(0,"\n", -1, 0);
3146   Th_PrintTraceLog();
3147   if( forceCgi ) cgi_reply();
3148 }
3149 #endif
3150