Fossil SCM

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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button