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], ©length) ) 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