Fossil SCM

Add "taint mode" to TH1. Attempts to output values that are derived from user input as unescaped HTML, or to use such values unescaped in SQL, raises errors. The resolution of these errors depends on the value of the new "vuln-report" setting.

drh 2025-04-20 16:54 trunk merge
Commit 2116238e80cc3dcb1c012c60df9915c525a3ed3e15f908570d1bb8ef939f1b36
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
2828
return $logourl
2929
}
3030
set logourl [getLogoUrl $baseurl]
3131
</th1>
3232
<a href="$logourl">
33
- <img src="$logo_image_url" border="0" alt="$project_name">
33
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
3434
</a>
3535
</div>
3636
<div class="title">
3737
<h1>$<project_name></h1>
3838
<span class="page-title">$<title></span>
3939
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
28 return $logourl
29 }
30 set logourl [getLogoUrl $baseurl]
31 </th1>
32 <a href="$logourl">
33 <img src="$logo_image_url" border="0" alt="$project_name">
34 </a>
35 </div>
36 <div class="title">
37 <h1>$<project_name></h1>
38 <span class="page-title">$<title></span>
39
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
28 return $logourl
29 }
30 set logourl [getLogoUrl $baseurl]
31 </th1>
32 <a href="$logourl">
33 <img src="$logo_image_url" border="0" alt="$<project_name>">
34 </a>
35 </div>
36 <div class="title">
37 <h1>$<project_name></h1>
38 <span class="page-title">$<title></span>
39
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
6565
# Link logo to the top of the current repo
6666
set logourl $baseurl
6767
}
6868
</th1>
6969
<a href="$logourl">
70
- <img src="$logo_image_url" border="0" alt="$project_name">
70
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
7171
</a>
7272
</div>
7373
<div class="title">$<title></div>
7474
<div class="status"><nobr><th1>
7575
if {[info exists login]} {
7676
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$project_name">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr><th1>
75 if {[info exists login]} {
76
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$<project_name>">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr><th1>
75 if {[info exists login]} {
76
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
5959
return $logourl
6060
}
6161
set logourl [getLogoUrl $baseurl]
6262
</th1>
6363
<a href="$logourl">
64
- <img src="$logo_image_url" border="0" alt="$project_name">
64
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
6565
</a>
6666
</div>
6767
<div class="title">$<title></div>
6868
<div class="status"><nobr><th1>
6969
if {[info exists login]} {
7070
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
59 return $logourl
60 }
61 set logourl [getLogoUrl $baseurl]
62 </th1>
63 <a href="$logourl">
64 <img src="$logo_image_url" border="0" alt="$project_name">
65 </a>
66 </div>
67 <div class="title">$<title></div>
68 <div class="status"><nobr><th1>
69 if {[info exists login]} {
70
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
59 return $logourl
60 }
61 set logourl [getLogoUrl $baseurl]
62 </th1>
63 <a href="$logourl">
64 <img src="$logo_image_url" border="0" alt="$<project_name>">
65 </a>
66 </div>
67 <div class="title">$<title></div>
68 <div class="status"><nobr><th1>
69 if {[info exists login]} {
70
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
6565
# Link logo to the top of the current repo
6666
set logourl $baseurl
6767
}
6868
</th1>
6969
<a href="$logourl">
70
- <img src="$logo_image_url" border="0" alt="$project_name">
70
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
7171
</a>
7272
</div>
7373
<div class="title">$<title></div>
7474
<div class="status"><nobr>
7575
<th1>
7676
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$project_name">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr>
75 <th1>
76
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$<project_name>">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr>
75 <th1>
76
+2 -2
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205205
linkTip = rid != symbolic_name_to_rid("tip", "ci");
206206
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207207
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208208
isBranchCI = branch_includes_uuid(zCI, zUuid);
209209
if( bDocDir ) zCI = mprintf("%S", zUuid);
210
- Th_Store("current_checkin", zCI);
210
+ Th_StoreUnsafe("current_checkin", zCI);
211211
}else{
212212
zCI = 0;
213213
}
214214
}
215215
@@ -771,11 +771,11 @@
771771
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772772
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773773
" FROM event WHERE objid=%d", rid);
774774
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775775
isBranchCI = branch_includes_uuid(zCI, zUuid);
776
- Th_Store("current_checkin", zCI);
776
+ Th_StoreUnsafe("current_checkin", zCI);
777777
}else{
778778
zCI = 0;
779779
}
780780
}
781781
if( zCI==0 ){
782782
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205 linkTip = rid != symbolic_name_to_rid("tip", "ci");
206 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208 isBranchCI = branch_includes_uuid(zCI, zUuid);
209 if( bDocDir ) zCI = mprintf("%S", zUuid);
210 Th_Store("current_checkin", zCI);
211 }else{
212 zCI = 0;
213 }
214 }
215
@@ -771,11 +771,11 @@
771 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772 zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773 " FROM event WHERE objid=%d", rid);
774 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775 isBranchCI = branch_includes_uuid(zCI, zUuid);
776 Th_Store("current_checkin", zCI);
777 }else{
778 zCI = 0;
779 }
780 }
781 if( zCI==0 ){
782
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205 linkTip = rid != symbolic_name_to_rid("tip", "ci");
206 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208 isBranchCI = branch_includes_uuid(zCI, zUuid);
209 if( bDocDir ) zCI = mprintf("%S", zUuid);
210 Th_StoreUnsafe("current_checkin", zCI);
211 }else{
212 zCI = 0;
213 }
214 }
215
@@ -771,11 +771,11 @@
771 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772 zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773 " FROM event WHERE objid=%d", rid);
774 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775 isBranchCI = branch_includes_uuid(zCI, zUuid);
776 Th_StoreUnsafe("current_checkin", zCI);
777 }else{
778 zCI = 0;
779 }
780 }
781 if( zCI==0 ){
782
+1 -1
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
10521052
*/
10531053
zMime = nMiss==0 ? P("mimetype") : 0;
10541054
if( zMime==0 ){
10551055
zMime = mimetype_from_name(zName);
10561056
}
1057
- Th_Store("doc_name", zName);
1057
+ Th_StoreUnsafe("doc_name", zName);
10581058
if( vid ){
10591059
Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
10601060
" FROM blob WHERE rid=%d", vid));
10611061
Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
10621062
" WHERE objid=%d AND type='ci'", vid));
10631063
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
1052 */
1053 zMime = nMiss==0 ? P("mimetype") : 0;
1054 if( zMime==0 ){
1055 zMime = mimetype_from_name(zName);
1056 }
1057 Th_Store("doc_name", zName);
1058 if( vid ){
1059 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
1060 " FROM blob WHERE rid=%d", vid));
1061 Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
1062 " WHERE objid=%d AND type='ci'", vid));
1063
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
1052 */
1053 zMime = nMiss==0 ? P("mimetype") : 0;
1054 if( zMime==0 ){
1055 zMime = mimetype_from_name(zName);
1056 }
1057 Th_StoreUnsafe("doc_name", zName);
1058 if( vid ){
1059 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
1060 " FROM blob WHERE rid=%d", vid));
1061 Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
1062 " WHERE objid=%d AND type='ci'", vid));
1063
+1 -1
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951951
const char *zOrigDate;
952952
int okWiki = 0;
953953
Blob wiki_read_links = BLOB_INITIALIZER;
954954
Blob wiki_add_links = BLOB_INITIALIZER;
955955
956
- Th_Store("current_checkin", zName);
956
+ Th_StoreUnsafe("current_checkin", zName);
957957
style_header("Check-in [%S]", zUuid);
958958
login_anonymous_available();
959959
zEUser = db_text(0,
960960
"SELECT value FROM tagxref"
961961
" WHERE tagid=%d AND rid=%d AND tagtype>0",
962962
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951 const char *zOrigDate;
952 int okWiki = 0;
953 Blob wiki_read_links = BLOB_INITIALIZER;
954 Blob wiki_add_links = BLOB_INITIALIZER;
955
956 Th_Store("current_checkin", zName);
957 style_header("Check-in [%S]", zUuid);
958 login_anonymous_available();
959 zEUser = db_text(0,
960 "SELECT value FROM tagxref"
961 " WHERE tagid=%d AND rid=%d AND tagtype>0",
962
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951 const char *zOrigDate;
952 int okWiki = 0;
953 Blob wiki_read_links = BLOB_INITIALIZER;
954 Blob wiki_add_links = BLOB_INITIALIZER;
955
956 Th_StoreUnsafe("current_checkin", zName);
957 style_header("Check-in [%S]", zUuid);
958 login_anonymous_available();
959 zEUser = db_text(0,
960 "SELECT value FROM tagxref"
961 " WHERE tagid=%d AND rid=%d AND tagtype>0",
962
+29 -9
--- src/main.c
+++ src/main.c
@@ -3718,10 +3718,13 @@
37183718
** case=3 Extra db_end_transaction()
37193719
** case=4 Error during SQL processing
37203720
** case=5 Call the segfault handler
37213721
** case=6 Call webpage_assert()
37223722
** case=7 Call webpage_error()
3723
+** case=8 Simulate a timeout
3724
+** case=9 Simulate a TH1 XSS vulnerability
3725
+** case=10 Simulate a TH1 SQL-injection vulnerability
37233726
*/
37243727
void test_warning_page(void){
37253728
int iCase = atoi(PD("case","0"));
37263729
int i;
37273730
login_check_credentials();
@@ -3730,17 +3733,15 @@
37303733
return;
37313734
}
37323735
style_set_current_feature("test");
37333736
style_header("Warning Test Page");
37343737
style_submenu_element("Error Log","%R/errorlog");
3735
- if( iCase<1 || iCase>4 ){
3736
- @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3737
- @ by clicking on one of the following cases:
3738
- }else{
3739
- @ <p>This is the test page for case=%d(iCase). All possible cases:
3740
- }
3741
- for(i=1; i<=8; i++){
3738
+ @ <p>This page will generate various kinds of errors to test Fossil's
3739
+ @ reaction. Depending on settings, a message might be written
3740
+ @ into the <a href="%R/errorlog">error log</a>. Click on
3741
+ @ one of the following hyperlinks to generate a simulated error:
3742
+ for(i=1; i<=10; i++){
37423743
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
37433744
}
37443745
@ </p>
37453746
@ <p><ol>
37463747
@ <li value='1'> Call fossil_warning()
@@ -3769,20 +3770,39 @@
37693770
}
37703771
@ <li value='6'> call webpage_assert(0)
37713772
if( iCase==6 ){
37723773
webpage_assert( 5==7 );
37733774
}
3774
- @ <li value='7'> call webpage_error()"
3775
+ @ <li value='7'> call webpage_error()
37753776
if( iCase==7 ){
37763777
cgi_reset_content();
37773778
webpage_error("Case 7 from /test-warning");
37783779
}
3779
- @ <li value='8'> simulated timeout"
3780
+ @ <li value='8'> simulated timeout
37803781
if( iCase==8 ){
37813782
fossil_set_timeout(1);
37823783
cgi_reset_content();
37833784
sqlite3_sleep(1100);
3785
+ }
3786
+ @ <li value='9'> simulated TH1 XSS vulnerability
3787
+ @ <li value='10'> simulated TH1 SQL-injection vulnerability
3788
+ if( iCase==9 || iCase==10 ){
3789
+ const char *zR;
3790
+ int n, rc;
3791
+ static const char *zTH1[] = {
3792
+ /* case 9 */ "html [taint {<b>XSS</b>}]",
3793
+ /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3794
+ " html \"<b>[htmlize $msg]</b>\"\n"
3795
+ "}"
3796
+ };
3797
+ rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3798
+ zR = Th_GetResult(g.interp, &n);
3799
+ if( rc==TH_OK ){
3800
+ @ <pre class="th1result">%h(zR)</pre>
3801
+ }else{
3802
+ @ <pre class="th1error">%h(zR)</pre>
3803
+ }
37843804
}
37853805
@ </ol>
37863806
@ <p>End of test</p>
37873807
style_finish_page();
37883808
}
37893809
--- src/main.c
+++ src/main.c
@@ -3718,10 +3718,13 @@
3718 ** case=3 Extra db_end_transaction()
3719 ** case=4 Error during SQL processing
3720 ** case=5 Call the segfault handler
3721 ** case=6 Call webpage_assert()
3722 ** case=7 Call webpage_error()
 
 
 
3723 */
3724 void test_warning_page(void){
3725 int iCase = atoi(PD("case","0"));
3726 int i;
3727 login_check_credentials();
@@ -3730,17 +3733,15 @@
3730 return;
3731 }
3732 style_set_current_feature("test");
3733 style_header("Warning Test Page");
3734 style_submenu_element("Error Log","%R/errorlog");
3735 if( iCase<1 || iCase>4 ){
3736 @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3737 @ by clicking on one of the following cases:
3738 }else{
3739 @ <p>This is the test page for case=%d(iCase). All possible cases:
3740 }
3741 for(i=1; i<=8; i++){
3742 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3743 }
3744 @ </p>
3745 @ <p><ol>
3746 @ <li value='1'> Call fossil_warning()
@@ -3769,20 +3770,39 @@
3769 }
3770 @ <li value='6'> call webpage_assert(0)
3771 if( iCase==6 ){
3772 webpage_assert( 5==7 );
3773 }
3774 @ <li value='7'> call webpage_error()"
3775 if( iCase==7 ){
3776 cgi_reset_content();
3777 webpage_error("Case 7 from /test-warning");
3778 }
3779 @ <li value='8'> simulated timeout"
3780 if( iCase==8 ){
3781 fossil_set_timeout(1);
3782 cgi_reset_content();
3783 sqlite3_sleep(1100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3784 }
3785 @ </ol>
3786 @ <p>End of test</p>
3787 style_finish_page();
3788 }
3789
--- src/main.c
+++ src/main.c
@@ -3718,10 +3718,13 @@
3718 ** case=3 Extra db_end_transaction()
3719 ** case=4 Error during SQL processing
3720 ** case=5 Call the segfault handler
3721 ** case=6 Call webpage_assert()
3722 ** case=7 Call webpage_error()
3723 ** case=8 Simulate a timeout
3724 ** case=9 Simulate a TH1 XSS vulnerability
3725 ** case=10 Simulate a TH1 SQL-injection vulnerability
3726 */
3727 void test_warning_page(void){
3728 int iCase = atoi(PD("case","0"));
3729 int i;
3730 login_check_credentials();
@@ -3730,17 +3733,15 @@
3733 return;
3734 }
3735 style_set_current_feature("test");
3736 style_header("Warning Test Page");
3737 style_submenu_element("Error Log","%R/errorlog");
3738 @ <p>This page will generate various kinds of errors to test Fossil's
3739 @ reaction. Depending on settings, a message might be written
3740 @ into the <a href="%R/errorlog">error log</a>. Click on
3741 @ one of the following hyperlinks to generate a simulated error:
3742 for(i=1; i<=10; i++){
 
 
3743 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3744 }
3745 @ </p>
3746 @ <p><ol>
3747 @ <li value='1'> Call fossil_warning()
@@ -3769,20 +3770,39 @@
3770 }
3771 @ <li value='6'> call webpage_assert(0)
3772 if( iCase==6 ){
3773 webpage_assert( 5==7 );
3774 }
3775 @ <li value='7'> call webpage_error()
3776 if( iCase==7 ){
3777 cgi_reset_content();
3778 webpage_error("Case 7 from /test-warning");
3779 }
3780 @ <li value='8'> simulated timeout
3781 if( iCase==8 ){
3782 fossil_set_timeout(1);
3783 cgi_reset_content();
3784 sqlite3_sleep(1100);
3785 }
3786 @ <li value='9'> simulated TH1 XSS vulnerability
3787 @ <li value='10'> simulated TH1 SQL-injection vulnerability
3788 if( iCase==9 || iCase==10 ){
3789 const char *zR;
3790 int n, rc;
3791 static const char *zTH1[] = {
3792 /* case 9 */ "html [taint {<b>XSS</b>}]",
3793 /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3794 " html \"<b>[htmlize $msg]</b>\"\n"
3795 "}"
3796 };
3797 rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3798 zR = Th_GetResult(g.interp, &n);
3799 if( rc==TH_OK ){
3800 @ <pre class="th1result">%h(zR)</pre>
3801 }else{
3802 @ <pre class="th1error">%h(zR)</pre>
3803 }
3804 }
3805 @ </ol>
3806 @ <p>End of test</p>
3807 style_finish_page();
3808 }
3809
+1 -1
--- src/printf.c
+++ src/printf.c
@@ -1121,11 +1121,11 @@
11211121
}else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
11221122
fprintf(out, "%s=%s\n", azEnv[i], z);
11231123
}
11241124
}
11251125
}
1126
- fclose(out);
1126
+ if( out!=stderr ) fclose(out);
11271127
}
11281128
11291129
/*
11301130
** The following variable becomes true while processing a fatal error
11311131
** or a panic. If additional "recursive-fatal" errors occur while
11321132
--- src/printf.c
+++ src/printf.c
@@ -1121,11 +1121,11 @@
1121 }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
1122 fprintf(out, "%s=%s\n", azEnv[i], z);
1123 }
1124 }
1125 }
1126 fclose(out);
1127 }
1128
1129 /*
1130 ** The following variable becomes true while processing a fatal error
1131 ** or a panic. If additional "recursive-fatal" errors occur while
1132
--- src/printf.c
+++ src/printf.c
@@ -1121,11 +1121,11 @@
1121 }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
1122 fprintf(out, "%s=%s\n", azEnv[i], z);
1123 }
1124 }
1125 }
1126 if( out!=stderr ) fclose(out);
1127 }
1128
1129 /*
1130 ** The following variable becomes true while processing a fatal error
1131 ** or a panic. If additional "recursive-fatal" errors occur while
1132
--- src/security_audit.c
+++ src/security_audit.c
@@ -810,27 +810,28 @@
810810
** WEBPAGE: errorlog
811811
**
812812
** Show the content of the error log. Only the administrator can view
813813
** this page.
814814
**
815
-** y=0x01 Show only hack attempts
816
-** y=0x02 Show only panics and assertion faults
817
-** y=0x04 Show hung backoffice processes
818
-** y=0x08 Show POST requests from a different origin
819
-** y=0x10 Show SQLITE_AUTH and similar
820
-** y=0x20 Show SMTP error reports
821
-** y=0x40 Show other uncategorized messages
815
+** y=0x001 Show only hack attempts
816
+** y=0x002 Show only panics and assertion faults
817
+** y=0x004 Show hung backoffice processes
818
+** y=0x008 Show POST requests from a different origin
819
+** y=0x010 Show SQLITE_AUTH and similar
820
+** y=0x020 Show SMTP error reports
821
+** y=0x040 Show TH1 vulnerability reports
822
+** y=0x800 Show other uncategorized messages
822823
**
823824
** If y is omitted or is zero, a count of the various message types is
824825
** shown.
825826
*/
826827
void errorlog_page(void){
827828
i64 szFile;
828829
FILE *in;
829830
char *zLog;
830831
const char *zType = P("y");
831
- static const int eAllTypes = 0x7f;
832
+ static const int eAllTypes = 0x87f;
832833
long eType = 0;
833834
int bOutput = 0;
834835
int prevWasTime = 0;
835836
int nHack = 0;
836837
int nPanic = 0;
@@ -837,10 +838,11 @@
837838
int nOther = 0;
838839
int nHang = 0;
839840
int nXPost = 0;
840841
int nAuth = 0;
841842
int nSmtp = 0;
843
+ int nVuln = 0;
842844
char z[10000];
843845
char zTime[10000];
844846
845847
login_check_credentials();
846848
if( !g.perm.Admin ){
@@ -917,10 +919,13 @@
917919
}
918920
if( eType & 0x20 ){
919921
@ <li>SMTP malfunctions
920922
}
921923
if( eType & 0x40 ){
924
+ @ <li>TH1 vulnerabilities
925
+ }
926
+ if( eType & 0x800 ){
922927
@ <li>Other uncategorized messages
923928
}
924929
@ </ul>
925930
}
926931
@ <hr>
@@ -953,12 +958,16 @@
953958
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
954959
){
955960
bOutput = (eType & 0x10)!=0;
956961
nAuth++;
957962
}else
958
- {
963
+ if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
959964
bOutput = (eType & 0x40)!=0;
965
+ nVuln++;
966
+ }else
967
+ {
968
+ bOutput = (eType & 0x800)!=0;
960969
nOther++;
961970
}
962971
if( bOutput ){
963972
@ %h(zTime)\
964973
}
@@ -978,17 +987,21 @@
978987
fclose(in);
979988
if( eType ){
980989
@ </pre>
981990
}
982991
if( eType==0 ){
983
- int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther;
992
+ int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
984993
int nTotal = nNonHack + nHack + nXPost;
985994
@ <p><table border="a" cellspacing="0" cellpadding="5">
986995
if( nPanic>0 ){
987996
@ <tr><td align="right">%d(nPanic)</td>
988997
@ <td><a href="./errorlog?y=2">Panics</a></td>
989998
}
999
+ if( nVuln>0 ){
1000
+ @ <tr><td align="right">%d(nVuln)</td>
1001
+ @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1002
+ }
9901003
if( nHack>0 ){
9911004
@ <tr><td align="right">%d(nHack)</td>
9921005
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
9931006
}
9941007
if( nHang>0 ){
@@ -1007,17 +1020,17 @@
10071020
@ <tr><td align="right">%d(nSmtp)</td>
10081021
@ <td><a href="./errorlog?y=32">SMTP faults</a></td>
10091022
}
10101023
if( nOther>0 ){
10111024
@ <tr><td align="right">%d(nOther)</td>
1012
- @ <td><a href="./errorlog?y=64">Other</a></td>
1025
+ @ <td><a href="./errorlog?y=2048">Other</a></td>
10131026
}
10141027
@ <tr><td align="right">%d(nTotal)</td>
10151028
if( nTotal>0 ){
1016
- @ <td><a href="./errorlog?y=255">All Messages</a></td>
1029
+ @ <td><a href="./errorlog?y=4095">All Messages</a></td>
10171030
}else{
10181031
@ <td>All Messages</td>
10191032
}
10201033
@ </table>
10211034
}
10221035
style_finish_page();
10231036
}
10241037
--- src/security_audit.c
+++ src/security_audit.c
@@ -810,27 +810,28 @@
810 ** WEBPAGE: errorlog
811 **
812 ** Show the content of the error log. Only the administrator can view
813 ** this page.
814 **
815 ** y=0x01 Show only hack attempts
816 ** y=0x02 Show only panics and assertion faults
817 ** y=0x04 Show hung backoffice processes
818 ** y=0x08 Show POST requests from a different origin
819 ** y=0x10 Show SQLITE_AUTH and similar
820 ** y=0x20 Show SMTP error reports
821 ** y=0x40 Show other uncategorized messages
 
822 **
823 ** If y is omitted or is zero, a count of the various message types is
824 ** shown.
825 */
826 void errorlog_page(void){
827 i64 szFile;
828 FILE *in;
829 char *zLog;
830 const char *zType = P("y");
831 static const int eAllTypes = 0x7f;
832 long eType = 0;
833 int bOutput = 0;
834 int prevWasTime = 0;
835 int nHack = 0;
836 int nPanic = 0;
@@ -837,10 +838,11 @@
837 int nOther = 0;
838 int nHang = 0;
839 int nXPost = 0;
840 int nAuth = 0;
841 int nSmtp = 0;
 
842 char z[10000];
843 char zTime[10000];
844
845 login_check_credentials();
846 if( !g.perm.Admin ){
@@ -917,10 +919,13 @@
917 }
918 if( eType & 0x20 ){
919 @ <li>SMTP malfunctions
920 }
921 if( eType & 0x40 ){
 
 
 
922 @ <li>Other uncategorized messages
923 }
924 @ </ul>
925 }
926 @ <hr>
@@ -953,12 +958,16 @@
953 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
954 ){
955 bOutput = (eType & 0x10)!=0;
956 nAuth++;
957 }else
958 {
959 bOutput = (eType & 0x40)!=0;
 
 
 
 
960 nOther++;
961 }
962 if( bOutput ){
963 @ %h(zTime)\
964 }
@@ -978,17 +987,21 @@
978 fclose(in);
979 if( eType ){
980 @ </pre>
981 }
982 if( eType==0 ){
983 int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther;
984 int nTotal = nNonHack + nHack + nXPost;
985 @ <p><table border="a" cellspacing="0" cellpadding="5">
986 if( nPanic>0 ){
987 @ <tr><td align="right">%d(nPanic)</td>
988 @ <td><a href="./errorlog?y=2">Panics</a></td>
989 }
 
 
 
 
990 if( nHack>0 ){
991 @ <tr><td align="right">%d(nHack)</td>
992 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
993 }
994 if( nHang>0 ){
@@ -1007,17 +1020,17 @@
1007 @ <tr><td align="right">%d(nSmtp)</td>
1008 @ <td><a href="./errorlog?y=32">SMTP faults</a></td>
1009 }
1010 if( nOther>0 ){
1011 @ <tr><td align="right">%d(nOther)</td>
1012 @ <td><a href="./errorlog?y=64">Other</a></td>
1013 }
1014 @ <tr><td align="right">%d(nTotal)</td>
1015 if( nTotal>0 ){
1016 @ <td><a href="./errorlog?y=255">All Messages</a></td>
1017 }else{
1018 @ <td>All Messages</td>
1019 }
1020 @ </table>
1021 }
1022 style_finish_page();
1023 }
1024
--- src/security_audit.c
+++ src/security_audit.c
@@ -810,27 +810,28 @@
810 ** WEBPAGE: errorlog
811 **
812 ** Show the content of the error log. Only the administrator can view
813 ** this page.
814 **
815 ** y=0x001 Show only hack attempts
816 ** y=0x002 Show only panics and assertion faults
817 ** y=0x004 Show hung backoffice processes
818 ** y=0x008 Show POST requests from a different origin
819 ** y=0x010 Show SQLITE_AUTH and similar
820 ** y=0x020 Show SMTP error reports
821 ** y=0x040 Show TH1 vulnerability reports
822 ** y=0x800 Show other uncategorized messages
823 **
824 ** If y is omitted or is zero, a count of the various message types is
825 ** shown.
826 */
827 void errorlog_page(void){
828 i64 szFile;
829 FILE *in;
830 char *zLog;
831 const char *zType = P("y");
832 static const int eAllTypes = 0x87f;
833 long eType = 0;
834 int bOutput = 0;
835 int prevWasTime = 0;
836 int nHack = 0;
837 int nPanic = 0;
@@ -837,10 +838,11 @@
838 int nOther = 0;
839 int nHang = 0;
840 int nXPost = 0;
841 int nAuth = 0;
842 int nSmtp = 0;
843 int nVuln = 0;
844 char z[10000];
845 char zTime[10000];
846
847 login_check_credentials();
848 if( !g.perm.Admin ){
@@ -917,10 +919,13 @@
919 }
920 if( eType & 0x20 ){
921 @ <li>SMTP malfunctions
922 }
923 if( eType & 0x40 ){
924 @ <li>TH1 vulnerabilities
925 }
926 if( eType & 0x800 ){
927 @ <li>Other uncategorized messages
928 }
929 @ </ul>
930 }
931 @ <hr>
@@ -953,12 +958,16 @@
958 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
959 ){
960 bOutput = (eType & 0x10)!=0;
961 nAuth++;
962 }else
963 if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
964 bOutput = (eType & 0x40)!=0;
965 nVuln++;
966 }else
967 {
968 bOutput = (eType & 0x800)!=0;
969 nOther++;
970 }
971 if( bOutput ){
972 @ %h(zTime)\
973 }
@@ -978,17 +987,21 @@
987 fclose(in);
988 if( eType ){
989 @ </pre>
990 }
991 if( eType==0 ){
992 int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
993 int nTotal = nNonHack + nHack + nXPost;
994 @ <p><table border="a" cellspacing="0" cellpadding="5">
995 if( nPanic>0 ){
996 @ <tr><td align="right">%d(nPanic)</td>
997 @ <td><a href="./errorlog?y=2">Panics</a></td>
998 }
999 if( nVuln>0 ){
1000 @ <tr><td align="right">%d(nVuln)</td>
1001 @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1002 }
1003 if( nHack>0 ){
1004 @ <tr><td align="right">%d(nHack)</td>
1005 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
1006 }
1007 if( nHang>0 ){
@@ -1007,17 +1020,17 @@
1020 @ <tr><td align="right">%d(nSmtp)</td>
1021 @ <td><a href="./errorlog?y=32">SMTP faults</a></td>
1022 }
1023 if( nOther>0 ){
1024 @ <tr><td align="right">%d(nOther)</td>
1025 @ <td><a href="./errorlog?y=2048">Other</a></td>
1026 }
1027 @ <tr><td align="right">%d(nTotal)</td>
1028 if( nTotal>0 ){
1029 @ <td><a href="./errorlog?y=4095">All Messages</a></td>
1030 }else{
1031 @ <td>All Messages</td>
1032 }
1033 @ </table>
1034 }
1035 style_finish_page();
1036 }
1037
+3 -2
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744744
** is evaluated before the header is rendered).
745745
*/
746746
Th_MaybeStore("default_csp", zDfltCsp);
747747
fossil_free(zDfltCsp);
748748
Th_Store("nonce", zNonce);
749
- Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750
- Th_Store("project_description", db_get("project-description",""));
749
+ Th_StoreUnsafe("project_name",
750
+ db_get("project-name","Unnamed Fossil Project"));
751
+ Th_StoreUnsafe("project_description", db_get("project-description",""));
751752
if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752753
Th_Store("baseurl", g.zBaseURL);
753754
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754755
Th_Store("home", g.zTop);
755756
Th_Store("index_page", db_get("index-page","/home"));
756757
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744 ** is evaluated before the header is rendered).
745 */
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750 Th_Store("project_description", db_get("project-description",""));
 
751 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752 Th_Store("baseurl", g.zBaseURL);
753 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754 Th_Store("home", g.zTop);
755 Th_Store("index_page", db_get("index-page","/home"));
756
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744 ** is evaluated before the header is rendered).
745 */
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_StoreUnsafe("project_name",
750 db_get("project-name","Unnamed Fossil Project"));
751 Th_StoreUnsafe("project_description", db_get("project-description",""));
752 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
753 Th_Store("baseurl", g.zBaseURL);
754 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
755 Th_Store("home", g.zTop);
756 Th_Store("index_page", db_get("index-page","/home"));
757
+80 -53
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
77
#include "config.h"
88
#include "th.h"
99
#include <string.h>
1010
#include <assert.h>
1111
12
+/*
13
+** External routines
14
+*/
15
+void fossil_panic(const char*,...);
16
+void fossil_errorlog(const char*,...);
17
+
1218
/*
1319
** Values used for element values in the tcl_platform array.
1420
*/
1521
1622
#if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
197203
*/
198204
struct Buffer {
199205
char *zBuf;
200206
int nBuf;
201207
int nBufAlloc;
208
+ int bTaint;
202209
};
203210
typedef struct Buffer Buffer;
204211
static void thBufferInit(Buffer *);
205212
static void thBufferFree(Th_Interp *interp, Buffer *);
206213
@@ -209,10 +216,18 @@
209216
** be NULL as long as the number of bytes to copy is zero.
210217
*/
211218
static void th_memcpy(void *dest, const void *src, size_t n){
212219
if( n>0 ) memcpy(dest,src,n);
213220
}
221
+
222
+/*
223
+** An oversized string has been encountered. Do not try to recover.
224
+** Panic the process.
225
+*/
226
+void Th_OversizeString(void){
227
+ fossil_panic("string too large. maximum size 286MB.");
228
+}
214229
215230
/*
216231
** Append nAdd bytes of content copied from zAdd to the end of buffer
217232
** pBuffer. If there is not enough space currently allocated, resize
218233
** the allocation to make space.
@@ -219,40 +234,46 @@
219234
*/
220235
static void thBufferWriteResize(
221236
Th_Interp *interp,
222237
Buffer *pBuffer,
223238
const char *zAdd,
224
- int nAdd
239
+ int nAddX
225240
){
241
+ int nAdd = TH1_LEN(nAddX);
226242
int nNew = (pBuffer->nBuf+nAdd)*2+32;
227243
#if defined(TH_MEMDEBUG)
228244
char *zNew = (char *)Th_Malloc(interp, nNew);
245
+ TH1_SIZECHECK(nNew);
229246
th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
230247
Th_Free(interp, pBuffer->zBuf);
231248
pBuffer->zBuf = zNew;
232249
#else
233250
int nOld = pBuffer->nBufAlloc;
251
+ TH1_SIZECHECK(nNew);
234252
pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
235253
memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
236254
#endif
237255
pBuffer->nBufAlloc = nNew;
238256
th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
239257
pBuffer->nBuf += nAdd;
258
+ TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
240259
}
241260
static void thBufferWriteFast(
242261
Th_Interp *interp,
243262
Buffer *pBuffer,
244263
const char *zAdd,
245
- int nAdd
264
+ int nAddX
246265
){
266
+ int nAdd = TH1_LEN(nAddX);
247267
if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
248
- thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
268
+ thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
249269
}else{
250270
if( pBuffer->zBuf ){
251271
memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
252272
}
253273
pBuffer->nBuf += nAdd;
274
+ TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
254275
}
255276
}
256277
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
257278
258279
/*
@@ -704,24 +725,25 @@
704725
int nWord
705726
){
706727
int rc = TH_OK;
707728
Buffer output;
708729
int i;
730
+ int nn = TH1_LEN(nWord);
709731
710732
thBufferInit(&output);
711733
712
- if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
713
- thBufferWrite(interp, &output, &zWord[1], nWord-2);
734
+ if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
735
+ thBufferWrite(interp, &output, &zWord[1], nn-2);
714736
}else{
715737
716738
/* If the word is surrounded by double-quotes strip these away. */
717
- if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
739
+ if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
718740
zWord++;
719
- nWord -= 2;
741
+ nn -= 2;
720742
}
721743
722
- for(i=0; rc==TH_OK && i<nWord; i++){
744
+ for(i=0; rc==TH_OK && i<nn; i++){
723745
int nGet;
724746
725747
int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
726748
int (*xSubst)(Th_Interp *, const char*, int) = 0;
727749
@@ -743,11 +765,11 @@
743765
thBufferAddChar(interp, &output, zWord[i]);
744766
continue; /* Go to the next iteration of the for(...) loop */
745767
}
746768
}
747769
748
- rc = xGet(interp, &zWord[i], nWord-i, &nGet);
770
+ rc = xGet(interp, &zWord[i], nn-i, &nGet);
749771
if( rc==TH_OK ){
750772
rc = xSubst(interp, &zWord[i], nGet);
751773
}
752774
if( rc==TH_OK ){
753775
const char *zRes;
@@ -758,11 +780,11 @@
758780
}
759781
}
760782
}
761783
762784
if( rc==TH_OK ){
763
- Th_SetResult(interp, output.zBuf, output.nBuf);
785
+ Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
764786
}
765787
thBufferFree(interp, &output);
766788
return rc;
767789
}
768790
@@ -826,11 +848,11 @@
826848
Buffer strbuf;
827849
Buffer lenbuf;
828850
int nCount = 0;
829851
830852
const char *zInput = zList;
831
- int nInput = nList;
853
+ int nInput = TH1_LEN(nList);
832854
833855
thBufferInit(&strbuf);
834856
thBufferInit(&lenbuf);
835857
836858
while( nInput>0 ){
@@ -837,19 +859,19 @@
837859
const char *zWord;
838860
int nWord;
839861
840862
thNextSpace(interp, zInput, nInput, &nWord);
841863
zInput += nWord;
842
- nInput = nList-(zInput-zList);
864
+ nInput = TH1_LEN(nList)-(zInput-zList);
843865
844866
if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
845867
|| TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
846868
){
847869
goto finish;
848870
}
849
- zInput = &zInput[nWord];
850
- nInput = nList-(zInput-zList);
871
+ zInput = &zInput[TH1_LEN(nWord)];
872
+ nInput = TH1_LEN(nList)-(zInput-zList);
851873
if( nWord>0 ){
852874
zWord = Th_GetResult(interp, &nWord);
853875
thBufferWrite(interp, &strbuf, zWord, nWord);
854876
thBufferAddChar(interp, &strbuf, 0);
855877
thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
872894
zElem = (char *)&anElem[nCount];
873895
th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
874896
th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
875897
for(i=0; i<nCount;i++){
876898
azElem[i] = zElem;
877
- zElem += (anElem[i] + 1);
899
+ zElem += (TH1_LEN(anElem[i]) + 1);
878900
}
879901
*pazElem = azElem;
880902
*panElem = anElem;
881903
}
882904
if( pnCount ){
@@ -894,12 +916,17 @@
894916
** in the current stack frame.
895917
*/
896918
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
897919
int rc = TH_OK;
898920
const char *zInput = zProgram;
899
- int nInput = nProgram;
921
+ int nInput = TH1_LEN(nProgram);
900922
923
+ if( TH1_TAINTED(nProgram)
924
+ && Th_ReportTaint(interp, "script", zProgram, nProgram)
925
+ ){
926
+ return TH_ERROR;
927
+ }
901928
while( rc==TH_OK && nInput ){
902929
Th_HashEntry *pEntry;
903930
int nSpace;
904931
const char *zFirst;
905932
@@ -949,13 +976,13 @@
949976
if( rc!=TH_OK ) continue;
950977
951978
if( argc>0 ){
952979
953980
/* Look up the command name in the command hash-table. */
954
- pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
981
+ pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
955982
if( !pEntry ){
956
- Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
983
+ Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
957984
rc = TH_ERROR;
958985
}
959986
960987
/* Call the command procedure. */
961988
if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
10531080
}else{
10541081
int nInput = nProgram;
10551082
10561083
if( nInput<0 ){
10571084
nInput = th_strlen(zProgram);
1085
+ }else{
1086
+ nInput = TH1_LEN(nInput);
10581087
}
10591088
rc = thEvalLocal(interp, zProgram, nInput);
10601089
}
10611090
10621091
interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
10951124
int isGlobal = 0;
10961125
int i;
10971126
10981127
if( nVarname<0 ){
10991128
nVarname = th_strlen(zVarname);
1129
+ }else{
1130
+ nVarname = TH1_LEN(nVarname);
11001131
}
11011132
nOuter = nVarname;
11021133
11031134
/* If the variable name starts with "::", then do the lookup is in the
11041135
** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
12711302
}
12721303
12731304
return Th_SetResult(interp, pValue->zData, pValue->nData);
12741305
}
12751306
1276
-/*
1277
-** If interp has a variable with the given name, its value is returned
1278
-** and its length is returned via *nOut if nOut is not NULL. If
1279
-** interp has no such var then NULL is returned without setting any
1280
-** error state and *nOut, if not NULL, is set to -1. The returned value
1281
-** is owned by the interpreter and may be invalidated the next time
1282
-** the interpreter is modified.
1283
-*/
1284
-const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
1285
- int *nOut){
1286
- Th_Variable *pValue;
1287
-
1288
- pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
1289
- if( !pValue || !pValue->zData ){
1290
- if( nOut!=0 ) *nOut = -1;
1291
- return NULL;
1292
- }
1293
- if( nOut!=0 ) *nOut = pValue->nData;
1294
- return pValue->zData;
1295
-}
1296
-
12971307
/*
12981308
** Return true if variable (zVar, nVar) exists.
12991309
*/
13001310
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
13011311
Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
13241334
int nVar,
13251335
const char *zValue,
13261336
int nValue
13271337
){
13281338
Th_Variable *pValue;
1339
+ int nn;
13291340
1341
+ nVar = TH1_LEN(nVar);
13301342
pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
13311343
if( !pValue ){
13321344
return TH_ERROR;
13331345
}
13341346
13351347
if( nValue<0 ){
1336
- nValue = th_strlen(zValue);
1348
+ nn = th_strlen(zValue);
1349
+ }else{
1350
+ nn = TH1_LEN(nValue);
13371351
}
13381352
if( pValue->zData ){
13391353
Th_Free(interp, pValue->zData);
13401354
pValue->zData = 0;
13411355
}
13421356
1343
- assert(zValue || nValue==0);
1344
- pValue->zData = Th_Malloc(interp, nValue+1);
1345
- pValue->zData[nValue] = '\0';
1346
- th_memcpy(pValue->zData, zValue, nValue);
1357
+ assert(zValue || nn==0);
1358
+ pValue->zData = Th_Malloc(interp, nn+1);
1359
+ pValue->zData[nn] = '\0';
1360
+ th_memcpy(pValue->zData, zValue, nn);
13471361
pValue->nData = nValue;
13481362
13491363
return TH_OK;
13501364
}
13511365
@@ -1458,10 +1472,12 @@
14581472
*/
14591473
char *th_strdup(Th_Interp *interp, const char *z, int n){
14601474
char *zRes;
14611475
if( n<0 ){
14621476
n = th_strlen(z);
1477
+ }else{
1478
+ n = TH1_LEN(n);
14631479
}
14641480
zRes = Th_Malloc(interp, n+1);
14651481
th_memcpy(zRes, z, n);
14661482
zRes[n] = '\0';
14671483
return zRes;
@@ -1519,13 +1535,14 @@
15191535
n = th_strlen(z);
15201536
}
15211537
15221538
if( z && n>0 ){
15231539
char *zResult;
1524
- zResult = Th_Malloc(pInterp, n+1);
1525
- th_memcpy(zResult, z, n);
1526
- zResult[n] = '\0';
1540
+ int nn = TH1_LEN(n);
1541
+ zResult = Th_Malloc(pInterp, nn+1);
1542
+ th_memcpy(zResult, z, nn);
1543
+ zResult[nn] = '\0';
15271544
pInterp->zResult = zResult;
15281545
pInterp->nResult = n;
15291546
}
15301547
15311548
return TH_OK;
@@ -1834,24 +1851,28 @@
18341851
int *pnStr, /* IN/OUT: Current length of *pzStr */
18351852
const char *zElem, /* Data to append */
18361853
int nElem /* Length of nElem */
18371854
){
18381855
char *zNew;
1839
- int nNew;
1856
+ long long int nNew;
1857
+ int nn;
18401858
18411859
if( nElem<0 ){
1842
- nElem = th_strlen(zElem);
1860
+ nn = th_strlen(zElem);
1861
+ }else{
1862
+ nn = TH1_LEN(nElem);
18431863
}
18441864
1845
- nNew = *pnStr + nElem;
1865
+ nNew = TH1_LEN(*pnStr) + nn;
1866
+ TH1_SIZECHECK(nNew);
18461867
zNew = Th_Malloc(interp, nNew);
18471868
th_memcpy(zNew, *pzStr, *pnStr);
1848
- th_memcpy(&zNew[*pnStr], zElem, nElem);
1869
+ th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
18491870
18501871
Th_Free(interp, *pzStr);
18511872
*pzStr = zNew;
1852
- *pnStr = nNew;
1873
+ *pnStr = (int)nNew;
18531874
18541875
return TH_OK;
18551876
}
18561877
18571878
/*
@@ -2456,10 +2477,12 @@
24562477
int nToken = 0;
24572478
Expr **apToken = 0;
24582479
24592480
if( nExpr<0 ){
24602481
nExpr = th_strlen(zExpr);
2482
+ }else{
2483
+ nExpr = TH1_LEN(nExpr);
24612484
}
24622485
24632486
/* Parse the expression to a list of tokens. */
24642487
rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
24652488
@@ -2567,10 +2590,12 @@
25672590
Th_HashEntry *pRet;
25682591
Th_HashEntry **ppRet;
25692592
25702593
if( nKey<0 ){
25712594
nKey = th_strlen(zKey);
2595
+ }else{
2596
+ nKey = TH1_LEN(nKey);
25722597
}
25732598
25742599
for(i=0; i<nKey; i++){
25752600
iKey = (iKey<<3) ^ iKey ^ zKey[i];
25762601
}
@@ -2800,10 +2825,12 @@
28002825
int base = 10;
28012826
int (*isdigit)(char) = th_isdigit;
28022827
28032828
if( n<0 ){
28042829
n = th_strlen(z);
2830
+ }else{
2831
+ n = TH1_LEN(n);
28052832
}
28062833
28072834
if( n>1 && (z[0]=='-' || z[0]=='+') ){
28082835
i = 1;
28092836
}
@@ -2859,11 +2886,11 @@
28592886
const char *z,
28602887
int n,
28612888
double *pfOut
28622889
){
28632890
if( !sqlite3IsNumber((const char *)z, 0) ){
2864
- Th_ErrorMessage(interp, "expected number, got: \"", z, n);
2891
+ Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
28652892
return TH_ERROR;
28662893
}
28672894
28682895
sqlite3AtoF((const char *)z, pfOut);
28692896
return TH_OK;
28702897
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
 
 
 
 
 
 
12 /*
13 ** Values used for element values in the tcl_platform array.
14 */
15
16 #if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
197 */
198 struct Buffer {
199 char *zBuf;
200 int nBuf;
201 int nBufAlloc;
 
202 };
203 typedef struct Buffer Buffer;
204 static void thBufferInit(Buffer *);
205 static void thBufferFree(Th_Interp *interp, Buffer *);
206
@@ -209,10 +216,18 @@
209 ** be NULL as long as the number of bytes to copy is zero.
210 */
211 static void th_memcpy(void *dest, const void *src, size_t n){
212 if( n>0 ) memcpy(dest,src,n);
213 }
 
 
 
 
 
 
 
 
214
215 /*
216 ** Append nAdd bytes of content copied from zAdd to the end of buffer
217 ** pBuffer. If there is not enough space currently allocated, resize
218 ** the allocation to make space.
@@ -219,40 +234,46 @@
219 */
220 static void thBufferWriteResize(
221 Th_Interp *interp,
222 Buffer *pBuffer,
223 const char *zAdd,
224 int nAdd
225 ){
 
226 int nNew = (pBuffer->nBuf+nAdd)*2+32;
227 #if defined(TH_MEMDEBUG)
228 char *zNew = (char *)Th_Malloc(interp, nNew);
 
229 th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
230 Th_Free(interp, pBuffer->zBuf);
231 pBuffer->zBuf = zNew;
232 #else
233 int nOld = pBuffer->nBufAlloc;
 
234 pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
235 memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
236 #endif
237 pBuffer->nBufAlloc = nNew;
238 th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
239 pBuffer->nBuf += nAdd;
 
240 }
241 static void thBufferWriteFast(
242 Th_Interp *interp,
243 Buffer *pBuffer,
244 const char *zAdd,
245 int nAdd
246 ){
 
247 if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
248 thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
249 }else{
250 if( pBuffer->zBuf ){
251 memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
252 }
253 pBuffer->nBuf += nAdd;
 
254 }
255 }
256 #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
257
258 /*
@@ -704,24 +725,25 @@
704 int nWord
705 ){
706 int rc = TH_OK;
707 Buffer output;
708 int i;
 
709
710 thBufferInit(&output);
711
712 if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
713 thBufferWrite(interp, &output, &zWord[1], nWord-2);
714 }else{
715
716 /* If the word is surrounded by double-quotes strip these away. */
717 if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
718 zWord++;
719 nWord -= 2;
720 }
721
722 for(i=0; rc==TH_OK && i<nWord; i++){
723 int nGet;
724
725 int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
726 int (*xSubst)(Th_Interp *, const char*, int) = 0;
727
@@ -743,11 +765,11 @@
743 thBufferAddChar(interp, &output, zWord[i]);
744 continue; /* Go to the next iteration of the for(...) loop */
745 }
746 }
747
748 rc = xGet(interp, &zWord[i], nWord-i, &nGet);
749 if( rc==TH_OK ){
750 rc = xSubst(interp, &zWord[i], nGet);
751 }
752 if( rc==TH_OK ){
753 const char *zRes;
@@ -758,11 +780,11 @@
758 }
759 }
760 }
761
762 if( rc==TH_OK ){
763 Th_SetResult(interp, output.zBuf, output.nBuf);
764 }
765 thBufferFree(interp, &output);
766 return rc;
767 }
768
@@ -826,11 +848,11 @@
826 Buffer strbuf;
827 Buffer lenbuf;
828 int nCount = 0;
829
830 const char *zInput = zList;
831 int nInput = nList;
832
833 thBufferInit(&strbuf);
834 thBufferInit(&lenbuf);
835
836 while( nInput>0 ){
@@ -837,19 +859,19 @@
837 const char *zWord;
838 int nWord;
839
840 thNextSpace(interp, zInput, nInput, &nWord);
841 zInput += nWord;
842 nInput = nList-(zInput-zList);
843
844 if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
845 || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
846 ){
847 goto finish;
848 }
849 zInput = &zInput[nWord];
850 nInput = nList-(zInput-zList);
851 if( nWord>0 ){
852 zWord = Th_GetResult(interp, &nWord);
853 thBufferWrite(interp, &strbuf, zWord, nWord);
854 thBufferAddChar(interp, &strbuf, 0);
855 thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
872 zElem = (char *)&anElem[nCount];
873 th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
874 th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
875 for(i=0; i<nCount;i++){
876 azElem[i] = zElem;
877 zElem += (anElem[i] + 1);
878 }
879 *pazElem = azElem;
880 *panElem = anElem;
881 }
882 if( pnCount ){
@@ -894,12 +916,17 @@
894 ** in the current stack frame.
895 */
896 static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
897 int rc = TH_OK;
898 const char *zInput = zProgram;
899 int nInput = nProgram;
900
 
 
 
 
 
901 while( rc==TH_OK && nInput ){
902 Th_HashEntry *pEntry;
903 int nSpace;
904 const char *zFirst;
905
@@ -949,13 +976,13 @@
949 if( rc!=TH_OK ) continue;
950
951 if( argc>0 ){
952
953 /* Look up the command name in the command hash-table. */
954 pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
955 if( !pEntry ){
956 Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
957 rc = TH_ERROR;
958 }
959
960 /* Call the command procedure. */
961 if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
1053 }else{
1054 int nInput = nProgram;
1055
1056 if( nInput<0 ){
1057 nInput = th_strlen(zProgram);
 
 
1058 }
1059 rc = thEvalLocal(interp, zProgram, nInput);
1060 }
1061
1062 interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
1095 int isGlobal = 0;
1096 int i;
1097
1098 if( nVarname<0 ){
1099 nVarname = th_strlen(zVarname);
 
 
1100 }
1101 nOuter = nVarname;
1102
1103 /* If the variable name starts with "::", then do the lookup is in the
1104 ** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
1271 }
1272
1273 return Th_SetResult(interp, pValue->zData, pValue->nData);
1274 }
1275
1276 /*
1277 ** If interp has a variable with the given name, its value is returned
1278 ** and its length is returned via *nOut if nOut is not NULL. If
1279 ** interp has no such var then NULL is returned without setting any
1280 ** error state and *nOut, if not NULL, is set to -1. The returned value
1281 ** is owned by the interpreter and may be invalidated the next time
1282 ** the interpreter is modified.
1283 */
1284 const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
1285 int *nOut){
1286 Th_Variable *pValue;
1287
1288 pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
1289 if( !pValue || !pValue->zData ){
1290 if( nOut!=0 ) *nOut = -1;
1291 return NULL;
1292 }
1293 if( nOut!=0 ) *nOut = pValue->nData;
1294 return pValue->zData;
1295 }
1296
1297 /*
1298 ** Return true if variable (zVar, nVar) exists.
1299 */
1300 int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1301 Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
1324 int nVar,
1325 const char *zValue,
1326 int nValue
1327 ){
1328 Th_Variable *pValue;
 
1329
 
1330 pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
1331 if( !pValue ){
1332 return TH_ERROR;
1333 }
1334
1335 if( nValue<0 ){
1336 nValue = th_strlen(zValue);
 
 
1337 }
1338 if( pValue->zData ){
1339 Th_Free(interp, pValue->zData);
1340 pValue->zData = 0;
1341 }
1342
1343 assert(zValue || nValue==0);
1344 pValue->zData = Th_Malloc(interp, nValue+1);
1345 pValue->zData[nValue] = '\0';
1346 th_memcpy(pValue->zData, zValue, nValue);
1347 pValue->nData = nValue;
1348
1349 return TH_OK;
1350 }
1351
@@ -1458,10 +1472,12 @@
1458 */
1459 char *th_strdup(Th_Interp *interp, const char *z, int n){
1460 char *zRes;
1461 if( n<0 ){
1462 n = th_strlen(z);
 
 
1463 }
1464 zRes = Th_Malloc(interp, n+1);
1465 th_memcpy(zRes, z, n);
1466 zRes[n] = '\0';
1467 return zRes;
@@ -1519,13 +1535,14 @@
1519 n = th_strlen(z);
1520 }
1521
1522 if( z && n>0 ){
1523 char *zResult;
1524 zResult = Th_Malloc(pInterp, n+1);
1525 th_memcpy(zResult, z, n);
1526 zResult[n] = '\0';
 
1527 pInterp->zResult = zResult;
1528 pInterp->nResult = n;
1529 }
1530
1531 return TH_OK;
@@ -1834,24 +1851,28 @@
1834 int *pnStr, /* IN/OUT: Current length of *pzStr */
1835 const char *zElem, /* Data to append */
1836 int nElem /* Length of nElem */
1837 ){
1838 char *zNew;
1839 int nNew;
 
1840
1841 if( nElem<0 ){
1842 nElem = th_strlen(zElem);
 
 
1843 }
1844
1845 nNew = *pnStr + nElem;
 
1846 zNew = Th_Malloc(interp, nNew);
1847 th_memcpy(zNew, *pzStr, *pnStr);
1848 th_memcpy(&zNew[*pnStr], zElem, nElem);
1849
1850 Th_Free(interp, *pzStr);
1851 *pzStr = zNew;
1852 *pnStr = nNew;
1853
1854 return TH_OK;
1855 }
1856
1857 /*
@@ -2456,10 +2477,12 @@
2456 int nToken = 0;
2457 Expr **apToken = 0;
2458
2459 if( nExpr<0 ){
2460 nExpr = th_strlen(zExpr);
 
 
2461 }
2462
2463 /* Parse the expression to a list of tokens. */
2464 rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
2465
@@ -2567,10 +2590,12 @@
2567 Th_HashEntry *pRet;
2568 Th_HashEntry **ppRet;
2569
2570 if( nKey<0 ){
2571 nKey = th_strlen(zKey);
 
 
2572 }
2573
2574 for(i=0; i<nKey; i++){
2575 iKey = (iKey<<3) ^ iKey ^ zKey[i];
2576 }
@@ -2800,10 +2825,12 @@
2800 int base = 10;
2801 int (*isdigit)(char) = th_isdigit;
2802
2803 if( n<0 ){
2804 n = th_strlen(z);
 
 
2805 }
2806
2807 if( n>1 && (z[0]=='-' || z[0]=='+') ){
2808 i = 1;
2809 }
@@ -2859,11 +2886,11 @@
2859 const char *z,
2860 int n,
2861 double *pfOut
2862 ){
2863 if( !sqlite3IsNumber((const char *)z, 0) ){
2864 Th_ErrorMessage(interp, "expected number, got: \"", z, n);
2865 return TH_ERROR;
2866 }
2867
2868 sqlite3AtoF((const char *)z, pfOut);
2869 return TH_OK;
2870
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
12 /*
13 ** External routines
14 */
15 void fossil_panic(const char*,...);
16 void fossil_errorlog(const char*,...);
17
18 /*
19 ** Values used for element values in the tcl_platform array.
20 */
21
22 #if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
203 */
204 struct Buffer {
205 char *zBuf;
206 int nBuf;
207 int nBufAlloc;
208 int bTaint;
209 };
210 typedef struct Buffer Buffer;
211 static void thBufferInit(Buffer *);
212 static void thBufferFree(Th_Interp *interp, Buffer *);
213
@@ -209,10 +216,18 @@
216 ** be NULL as long as the number of bytes to copy is zero.
217 */
218 static void th_memcpy(void *dest, const void *src, size_t n){
219 if( n>0 ) memcpy(dest,src,n);
220 }
221
222 /*
223 ** An oversized string has been encountered. Do not try to recover.
224 ** Panic the process.
225 */
226 void Th_OversizeString(void){
227 fossil_panic("string too large. maximum size 286MB.");
228 }
229
230 /*
231 ** Append nAdd bytes of content copied from zAdd to the end of buffer
232 ** pBuffer. If there is not enough space currently allocated, resize
233 ** the allocation to make space.
@@ -219,40 +234,46 @@
234 */
235 static void thBufferWriteResize(
236 Th_Interp *interp,
237 Buffer *pBuffer,
238 const char *zAdd,
239 int nAddX
240 ){
241 int nAdd = TH1_LEN(nAddX);
242 int nNew = (pBuffer->nBuf+nAdd)*2+32;
243 #if defined(TH_MEMDEBUG)
244 char *zNew = (char *)Th_Malloc(interp, nNew);
245 TH1_SIZECHECK(nNew);
246 th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
247 Th_Free(interp, pBuffer->zBuf);
248 pBuffer->zBuf = zNew;
249 #else
250 int nOld = pBuffer->nBufAlloc;
251 TH1_SIZECHECK(nNew);
252 pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
253 memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
254 #endif
255 pBuffer->nBufAlloc = nNew;
256 th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
257 pBuffer->nBuf += nAdd;
258 TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
259 }
260 static void thBufferWriteFast(
261 Th_Interp *interp,
262 Buffer *pBuffer,
263 const char *zAdd,
264 int nAddX
265 ){
266 int nAdd = TH1_LEN(nAddX);
267 if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
268 thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
269 }else{
270 if( pBuffer->zBuf ){
271 memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
272 }
273 pBuffer->nBuf += nAdd;
274 TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
275 }
276 }
277 #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
278
279 /*
@@ -704,24 +725,25 @@
725 int nWord
726 ){
727 int rc = TH_OK;
728 Buffer output;
729 int i;
730 int nn = TH1_LEN(nWord);
731
732 thBufferInit(&output);
733
734 if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
735 thBufferWrite(interp, &output, &zWord[1], nn-2);
736 }else{
737
738 /* If the word is surrounded by double-quotes strip these away. */
739 if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
740 zWord++;
741 nn -= 2;
742 }
743
744 for(i=0; rc==TH_OK && i<nn; i++){
745 int nGet;
746
747 int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
748 int (*xSubst)(Th_Interp *, const char*, int) = 0;
749
@@ -743,11 +765,11 @@
765 thBufferAddChar(interp, &output, zWord[i]);
766 continue; /* Go to the next iteration of the for(...) loop */
767 }
768 }
769
770 rc = xGet(interp, &zWord[i], nn-i, &nGet);
771 if( rc==TH_OK ){
772 rc = xSubst(interp, &zWord[i], nGet);
773 }
774 if( rc==TH_OK ){
775 const char *zRes;
@@ -758,11 +780,11 @@
780 }
781 }
782 }
783
784 if( rc==TH_OK ){
785 Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
786 }
787 thBufferFree(interp, &output);
788 return rc;
789 }
790
@@ -826,11 +848,11 @@
848 Buffer strbuf;
849 Buffer lenbuf;
850 int nCount = 0;
851
852 const char *zInput = zList;
853 int nInput = TH1_LEN(nList);
854
855 thBufferInit(&strbuf);
856 thBufferInit(&lenbuf);
857
858 while( nInput>0 ){
@@ -837,19 +859,19 @@
859 const char *zWord;
860 int nWord;
861
862 thNextSpace(interp, zInput, nInput, &nWord);
863 zInput += nWord;
864 nInput = TH1_LEN(nList)-(zInput-zList);
865
866 if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
867 || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
868 ){
869 goto finish;
870 }
871 zInput = &zInput[TH1_LEN(nWord)];
872 nInput = TH1_LEN(nList)-(zInput-zList);
873 if( nWord>0 ){
874 zWord = Th_GetResult(interp, &nWord);
875 thBufferWrite(interp, &strbuf, zWord, nWord);
876 thBufferAddChar(interp, &strbuf, 0);
877 thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
894 zElem = (char *)&anElem[nCount];
895 th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
896 th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
897 for(i=0; i<nCount;i++){
898 azElem[i] = zElem;
899 zElem += (TH1_LEN(anElem[i]) + 1);
900 }
901 *pazElem = azElem;
902 *panElem = anElem;
903 }
904 if( pnCount ){
@@ -894,12 +916,17 @@
916 ** in the current stack frame.
917 */
918 static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
919 int rc = TH_OK;
920 const char *zInput = zProgram;
921 int nInput = TH1_LEN(nProgram);
922
923 if( TH1_TAINTED(nProgram)
924 && Th_ReportTaint(interp, "script", zProgram, nProgram)
925 ){
926 return TH_ERROR;
927 }
928 while( rc==TH_OK && nInput ){
929 Th_HashEntry *pEntry;
930 int nSpace;
931 const char *zFirst;
932
@@ -949,13 +976,13 @@
976 if( rc!=TH_OK ) continue;
977
978 if( argc>0 ){
979
980 /* Look up the command name in the command hash-table. */
981 pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
982 if( !pEntry ){
983 Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
984 rc = TH_ERROR;
985 }
986
987 /* Call the command procedure. */
988 if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
1080 }else{
1081 int nInput = nProgram;
1082
1083 if( nInput<0 ){
1084 nInput = th_strlen(zProgram);
1085 }else{
1086 nInput = TH1_LEN(nInput);
1087 }
1088 rc = thEvalLocal(interp, zProgram, nInput);
1089 }
1090
1091 interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
1124 int isGlobal = 0;
1125 int i;
1126
1127 if( nVarname<0 ){
1128 nVarname = th_strlen(zVarname);
1129 }else{
1130 nVarname = TH1_LEN(nVarname);
1131 }
1132 nOuter = nVarname;
1133
1134 /* If the variable name starts with "::", then do the lookup is in the
1135 ** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
1302 }
1303
1304 return Th_SetResult(interp, pValue->zData, pValue->nData);
1305 }
1306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1307 /*
1308 ** Return true if variable (zVar, nVar) exists.
1309 */
1310 int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1311 Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
1334 int nVar,
1335 const char *zValue,
1336 int nValue
1337 ){
1338 Th_Variable *pValue;
1339 int nn;
1340
1341 nVar = TH1_LEN(nVar);
1342 pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
1343 if( !pValue ){
1344 return TH_ERROR;
1345 }
1346
1347 if( nValue<0 ){
1348 nn = th_strlen(zValue);
1349 }else{
1350 nn = TH1_LEN(nValue);
1351 }
1352 if( pValue->zData ){
1353 Th_Free(interp, pValue->zData);
1354 pValue->zData = 0;
1355 }
1356
1357 assert(zValue || nn==0);
1358 pValue->zData = Th_Malloc(interp, nn+1);
1359 pValue->zData[nn] = '\0';
1360 th_memcpy(pValue->zData, zValue, nn);
1361 pValue->nData = nValue;
1362
1363 return TH_OK;
1364 }
1365
@@ -1458,10 +1472,12 @@
1472 */
1473 char *th_strdup(Th_Interp *interp, const char *z, int n){
1474 char *zRes;
1475 if( n<0 ){
1476 n = th_strlen(z);
1477 }else{
1478 n = TH1_LEN(n);
1479 }
1480 zRes = Th_Malloc(interp, n+1);
1481 th_memcpy(zRes, z, n);
1482 zRes[n] = '\0';
1483 return zRes;
@@ -1519,13 +1535,14 @@
1535 n = th_strlen(z);
1536 }
1537
1538 if( z && n>0 ){
1539 char *zResult;
1540 int nn = TH1_LEN(n);
1541 zResult = Th_Malloc(pInterp, nn+1);
1542 th_memcpy(zResult, z, nn);
1543 zResult[nn] = '\0';
1544 pInterp->zResult = zResult;
1545 pInterp->nResult = n;
1546 }
1547
1548 return TH_OK;
@@ -1834,24 +1851,28 @@
1851 int *pnStr, /* IN/OUT: Current length of *pzStr */
1852 const char *zElem, /* Data to append */
1853 int nElem /* Length of nElem */
1854 ){
1855 char *zNew;
1856 long long int nNew;
1857 int nn;
1858
1859 if( nElem<0 ){
1860 nn = th_strlen(zElem);
1861 }else{
1862 nn = TH1_LEN(nElem);
1863 }
1864
1865 nNew = TH1_LEN(*pnStr) + nn;
1866 TH1_SIZECHECK(nNew);
1867 zNew = Th_Malloc(interp, nNew);
1868 th_memcpy(zNew, *pzStr, *pnStr);
1869 th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
1870
1871 Th_Free(interp, *pzStr);
1872 *pzStr = zNew;
1873 *pnStr = (int)nNew;
1874
1875 return TH_OK;
1876 }
1877
1878 /*
@@ -2456,10 +2477,12 @@
2477 int nToken = 0;
2478 Expr **apToken = 0;
2479
2480 if( nExpr<0 ){
2481 nExpr = th_strlen(zExpr);
2482 }else{
2483 nExpr = TH1_LEN(nExpr);
2484 }
2485
2486 /* Parse the expression to a list of tokens. */
2487 rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
2488
@@ -2567,10 +2590,12 @@
2590 Th_HashEntry *pRet;
2591 Th_HashEntry **ppRet;
2592
2593 if( nKey<0 ){
2594 nKey = th_strlen(zKey);
2595 }else{
2596 nKey = TH1_LEN(nKey);
2597 }
2598
2599 for(i=0; i<nKey; i++){
2600 iKey = (iKey<<3) ^ iKey ^ zKey[i];
2601 }
@@ -2800,10 +2825,12 @@
2825 int base = 10;
2826 int (*isdigit)(char) = th_isdigit;
2827
2828 if( n<0 ){
2829 n = th_strlen(z);
2830 }else{
2831 n = TH1_LEN(n);
2832 }
2833
2834 if( n>1 && (z[0]=='-' || z[0]=='+') ){
2835 i = 1;
2836 }
@@ -2859,11 +2886,11 @@
2886 const char *z,
2887 int n,
2888 double *pfOut
2889 ){
2890 if( !sqlite3IsNumber((const char *)z, 0) ){
2891 Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
2892 return TH_ERROR;
2893 }
2894
2895 sqlite3AtoF((const char *)z, pfOut);
2896 return TH_OK;
2897
+53 -14
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
1
-
21
/* This header file defines the external interface to the custom Scripting
32
** Language (TH) interpreter. TH is very similar to Tcl but is not an
43
** exact clone.
4
+**
5
+** TH1 was original developed to run SQLite tests on SymbianOS. This version
6
+** of TH1 was repurposed as a scripted language for Fossil, and was heavily
7
+** modified for that purpose, beginning in early 2008.
8
+**
9
+** More recently, TH1 has been enhanced to distinguish between regular text
10
+** and "tainted" text. "Tainted" text is text that might have originated
11
+** from an outside source and hence might not be trustworthy. To prevent
12
+** cross-site scripting (XSS) and SQL-injections and similar attacks,
13
+** tainted text should not be used for the following purposes:
14
+**
15
+** * executed as TH1 script or expression.
16
+** * output as HTML or Javascript
17
+** * used as part of an SQL query
18
+**
19
+** Tainted text can be converted into a safe form using commands like
20
+** "htmlize". And some commands ("query" and "expr") know how to use
21
+** potentially tainted variable values directly, and thus can bypass
22
+** the restrictions above.
23
+**
24
+** Whether a string is clean or tainted is determined by its length integer.
25
+** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
26
+** (about 268MB - more than sufficient for the purposes of Fossil). The top
27
+** bit of the length integer is the sign bit, of course. The next three bits
28
+** are reserved. One of those, the 0x10000000 bit, marks tainted strings.
529
*/
30
+#define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */
31
+#define TH1_TAINT_BIT 0x10000000 /* The taint bit */
32
+#define TH1_SIGN 0x80000000
33
+
34
+/* Convert an integer into a string length. Negative values remain negative */
35
+#define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X))
36
+
37
+/* Return true if the string is tainted */
38
+#define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0)
39
+
40
+/* Remove taint from a string */
41
+#define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT)
42
+
43
+/* Add taint to a string */
44
+#define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT)
45
+
46
+/* If B is tainted, make A tainted too */
47
+#define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B))
48
+
49
+/* Check to see if a string is too big for TH1 */
50
+#define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();}
51
+void Th_OversizeString(void);
652
753
/*
854
** Before creating an interpreter, the application must allocate and
955
** populate an instance of the following structure. It must remain valid
1056
** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
2470
** Create and delete interpreters.
2571
*/
2672
Th_Interp * Th_CreateInterp(Th_Vtab *);
2773
void Th_DeleteInterp(Th_Interp *);
2874
75
+/*
76
+** Report taint in the string zStr,nStr. That string represents "zTitle"
77
+** If non-zero is returned error out of the caller.
78
+*/
79
+int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);
80
+
2981
/*
3082
** Evaluate an TH program in the stack frame identified by parameter
3183
** iFrame, according to the following rules:
3284
**
3385
** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
56108
int Th_GetVar(Th_Interp *, const char *, int);
57109
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
58110
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
59111
int Th_UnsetVar(Th_Interp *, const char *, int);
60112
61
-/*
62
-** If interp has a variable with the given name, its value is returned
63
-** and its length is returned via *nOut if nOut is not NULL. If
64
-** interp has no such var then NULL is returned without setting any
65
-** error state and *nOut, if not NULL, is set to 0. The returned value
66
-** is owned by the interpreter and may be invalidated the next time
67
-** the interpreter is modified.
68
-**
69
-** zVarName must be NUL-terminated.
70
-*/
71
-const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
72
- int *nOut);
73
-
74113
typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
75114
76115
/*
77116
** Register new commands.
78117
*/
79118
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
1
2 /* This header file defines the external interface to the custom Scripting
3 ** Language (TH) interpreter. TH is very similar to Tcl but is not an
4 ** exact clone.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
7 /*
8 ** Before creating an interpreter, the application must allocate and
9 ** populate an instance of the following structure. It must remain valid
10 ** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
24 ** Create and delete interpreters.
25 */
26 Th_Interp * Th_CreateInterp(Th_Vtab *);
27 void Th_DeleteInterp(Th_Interp *);
28
 
 
 
 
 
 
29 /*
30 ** Evaluate an TH program in the stack frame identified by parameter
31 ** iFrame, according to the following rules:
32 **
33 ** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
56 int Th_GetVar(Th_Interp *, const char *, int);
57 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
58 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
59 int Th_UnsetVar(Th_Interp *, const char *, int);
60
61 /*
62 ** If interp has a variable with the given name, its value is returned
63 ** and its length is returned via *nOut if nOut is not NULL. If
64 ** interp has no such var then NULL is returned without setting any
65 ** error state and *nOut, if not NULL, is set to 0. The returned value
66 ** is owned by the interpreter and may be invalidated the next time
67 ** the interpreter is modified.
68 **
69 ** zVarName must be NUL-terminated.
70 */
71 const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
72 int *nOut);
73
74 typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
75
76 /*
77 ** Register new commands.
78 */
79
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
 
1 /* This header file defines the external interface to the custom Scripting
2 ** Language (TH) interpreter. TH is very similar to Tcl but is not an
3 ** exact clone.
4 **
5 ** TH1 was original developed to run SQLite tests on SymbianOS. This version
6 ** of TH1 was repurposed as a scripted language for Fossil, and was heavily
7 ** modified for that purpose, beginning in early 2008.
8 **
9 ** More recently, TH1 has been enhanced to distinguish between regular text
10 ** and "tainted" text. "Tainted" text is text that might have originated
11 ** from an outside source and hence might not be trustworthy. To prevent
12 ** cross-site scripting (XSS) and SQL-injections and similar attacks,
13 ** tainted text should not be used for the following purposes:
14 **
15 ** * executed as TH1 script or expression.
16 ** * output as HTML or Javascript
17 ** * used as part of an SQL query
18 **
19 ** Tainted text can be converted into a safe form using commands like
20 ** "htmlize". And some commands ("query" and "expr") know how to use
21 ** potentially tainted variable values directly, and thus can bypass
22 ** the restrictions above.
23 **
24 ** Whether a string is clean or tainted is determined by its length integer.
25 ** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
26 ** (about 268MB - more than sufficient for the purposes of Fossil). The top
27 ** bit of the length integer is the sign bit, of course. The next three bits
28 ** are reserved. One of those, the 0x10000000 bit, marks tainted strings.
29 */
30 #define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */
31 #define TH1_TAINT_BIT 0x10000000 /* The taint bit */
32 #define TH1_SIGN 0x80000000
33
34 /* Convert an integer into a string length. Negative values remain negative */
35 #define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X))
36
37 /* Return true if the string is tainted */
38 #define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0)
39
40 /* Remove taint from a string */
41 #define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT)
42
43 /* Add taint to a string */
44 #define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT)
45
46 /* If B is tainted, make A tainted too */
47 #define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B))
48
49 /* Check to see if a string is too big for TH1 */
50 #define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();}
51 void Th_OversizeString(void);
52
53 /*
54 ** Before creating an interpreter, the application must allocate and
55 ** populate an instance of the following structure. It must remain valid
56 ** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
70 ** Create and delete interpreters.
71 */
72 Th_Interp * Th_CreateInterp(Th_Vtab *);
73 void Th_DeleteInterp(Th_Interp *);
74
75 /*
76 ** Report taint in the string zStr,nStr. That string represents "zTitle"
77 ** If non-zero is returned error out of the caller.
78 */
79 int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);
80
81 /*
82 ** Evaluate an TH program in the stack frame identified by parameter
83 ** iFrame, according to the following rules:
84 **
85 ** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
108 int Th_GetVar(Th_Interp *, const char *, int);
109 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
110 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
111 int Th_UnsetVar(Th_Interp *, const char *, int);
112
 
 
 
 
 
 
 
 
 
 
 
 
 
113 typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
114
115 /*
116 ** Register new commands.
117 */
118
+70 -48
--- src/th_lang.c
+++ src/th_lang.c
@@ -39,11 +39,11 @@
3939
4040
rc = Th_Eval(interp, 0, argv[1], -1);
4141
if( argc==3 ){
4242
int nResult;
4343
const char *zResult = Th_GetResult(interp, &nResult);
44
- Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
44
+ Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
4545
}
4646
4747
Th_SetResultInt(interp, rc);
4848
return TH_OK;
4949
}
@@ -215,15 +215,18 @@
215215
int *argl
216216
){
217217
char *zList = 0;
218218
int nList = 0;
219219
int i;
220
+ int bTaint = 0;
220221
221222
for(i=1; i<argc; i++){
223
+ TH1_XFER_TAINT(bTaint,argl[i]);
222224
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
223225
}
224226
227
+ TH1_XFER_TAINT(nList, bTaint);
225228
Th_SetResult(interp, zList, nList);
226229
Th_Free(interp, zList);
227230
228231
return TH_OK;
229232
}
@@ -244,10 +247,11 @@
244247
int *argl
245248
){
246249
char *zList = 0;
247250
int nList = 0;
248251
int i, rc;
252
+ int bTaint = 0;
249253
250254
if( argc<2 ){
251255
return Th_WrongNumArgs(interp, "lappend var ...");
252256
}
253257
rc = Th_GetVar(interp, argv[1], argl[1]);
@@ -254,13 +258,15 @@
254258
if( rc==TH_OK ){
255259
zList = Th_TakeResult(interp, &nList);
256260
}
257261
258262
for(i=2; i<argc; i++){
263
+ TH1_XFER_TAINT(bTaint, argl[i]);
259264
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
260265
}
261266
267
+ TH1_XFER_TAINT(nList, bTaint);
262268
Th_SetVar(interp, argv[1], argl[1], zList, nList);
263269
Th_SetResult(interp, zList, nList);
264270
Th_Free(interp, zList);
265271
266272
return TH_OK;
@@ -356,13 +362,14 @@
356362
return Th_WrongNumArgs(interp, "lsearch list string");
357363
}
358364
359365
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
360366
if( rc==TH_OK ){
367
+ int nn = TH1_LEN(argl[2]);
361368
Th_SetResultInt(interp, -1);
362369
for(i=0; i<nCount; i++){
363
- if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
370
+ if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
364371
Th_SetResultInt(interp, i);
365372
break;
366373
}
367374
}
368375
Th_Free(interp, azElem);
@@ -561,20 +568,21 @@
561568
int nUsage = 0; /* Number of bytes at zUsage */
562569
563570
if( argc!=4 ){
564571
return Th_WrongNumArgs(interp, "proc name arglist code");
565572
}
566
- if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
573
+ if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
574
+ &azParam, &anParam, &nParam) ){
567575
return TH_ERROR;
568576
}
569577
570578
/* Allocate the new ProcDefn structure. */
571579
nByte = sizeof(ProcDefn) + /* ProcDefn structure */
572580
(sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
573581
(sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
574
- argl[3] + /* zProgram */
575
- argl[2]; /* Space for copies of parameter names and default values */
582
+ TH1_LEN(argl[3]) + /* zProgram */
583
+ TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
576584
p = (ProcDefn *)Th_Malloc(interp, nByte);
577585
578586
/* If the last parameter in the parameter list is "args", then set the
579587
** ProcDefn.hasArgs flag. The "args" parameter does not require an
580588
** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
@@ -590,12 +598,12 @@
590598
p->azParam = (char **)&p[1];
591599
p->anParam = (int *)&p->azParam[nParam];
592600
p->azDefault = (char **)&p->anParam[nParam];
593601
p->anDefault = (int *)&p->azDefault[nParam];
594602
p->zProgram = (char *)&p->anDefault[nParam];
595
- memcpy(p->zProgram, argv[3], argl[3]);
596
- p->nProgram = argl[3];
603
+ memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
604
+ p->nProgram = TH1_LEN(argl[3]);
597605
zSpace = &p->zProgram[p->nProgram];
598606
599607
for(i=0; i<nParam; i++){
600608
char **az;
601609
int *an;
@@ -672,11 +680,12 @@
672680
int *argl
673681
){
674682
if( argc!=3 ){
675683
return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
676684
}
677
- return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
685
+ return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
686
+ argv[2], TH1_LEN(argl[2]));
678687
}
679688
680689
/*
681690
** TH Syntax:
682691
**
@@ -746,13 +755,13 @@
746755
if( argc!=4 ){
747756
return Th_WrongNumArgs(interp, "string compare str1 str2");
748757
}
749758
750759
zLeft = argv[2];
751
- nLeft = argl[2];
760
+ nLeft = TH1_LEN(argl[2]);
752761
zRight = argv[3];
753
- nRight = argl[3];
762
+ nRight = TH1_LEN(argl[3]);
754763
755764
for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
756765
iRes = zLeft[i]-zRight[i];
757766
}
758767
if( iRes==0 ){
@@ -779,12 +788,12 @@
779788
780789
if( argc!=4 ){
781790
return Th_WrongNumArgs(interp, "string first needle haystack");
782791
}
783792
784
- nNeedle = argl[2];
785
- nHaystack = argl[3];
793
+ nNeedle = TH1_LEN(argl[2]);
794
+ nHaystack = TH1_LEN(argl[3]);
786795
787796
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
788797
const char *zNeedle = argv[2];
789798
const char *zHaystack = argv[3];
790799
int i;
@@ -812,19 +821,19 @@
812821
813822
if( argc!=4 ){
814823
return Th_WrongNumArgs(interp, "string index string index");
815824
}
816825
817
- if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
818
- iIndex = argl[2]-1;
826
+ if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
827
+ iIndex = TH1_LEN(argl[2])-1;
819828
}else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
820829
Th_ErrorMessage(
821830
interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
822831
return TH_ERROR;
823832
}
824833
825
- if( iIndex>=0 && iIndex<argl[2] ){
834
+ if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
826835
return Th_SetResult(interp, &argv[2][iIndex], 1);
827836
}else{
828837
return Th_SetResult(interp, 0, 0);
829838
}
830839
}
@@ -838,41 +847,44 @@
838847
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
839848
){
840849
if( argc!=4 ){
841850
return Th_WrongNumArgs(interp, "string is class string");
842851
}
843
- if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
852
+ if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
844853
int i;
845854
int iRes = 1;
846855
847
- for(i=0; i<argl[3]; i++){
856
+ for(i=0; i<TH1_LEN(argl[3]); i++){
848857
if( !th_isalnum(argv[3][i]) ){
849858
iRes = 0;
850859
}
851860
}
852861
853862
return Th_SetResultInt(interp, iRes);
854
- }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
863
+ }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
855864
double fVal;
856865
if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
857866
return Th_SetResultInt(interp, 1);
858867
}
859868
return Th_SetResultInt(interp, 0);
860
- }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
869
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
861870
int iVal;
862871
if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
863872
return Th_SetResultInt(interp, 1);
864873
}
865874
return Th_SetResultInt(interp, 0);
866
- }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
875
+ }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
867876
if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
868877
return Th_SetResultInt(interp, 1);
869878
}
870879
return Th_SetResultInt(interp, 0);
880
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
881
+ return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
871882
}else{
872883
Th_ErrorMessage(interp,
873
- "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
884
+ "Expected alnum, double, integer, list, or tainted, got:",
885
+ argv[2], TH1_LEN(argl[2]));
874886
return TH_ERROR;
875887
}
876888
}
877889
878890
/*
@@ -889,12 +901,12 @@
889901
890902
if( argc!=4 ){
891903
return Th_WrongNumArgs(interp, "string last needle haystack");
892904
}
893905
894
- nNeedle = argl[2];
895
- nHaystack = argl[3];
906
+ nNeedle = TH1_LEN(argl[2]);
907
+ nHaystack = TH1_LEN(argl[3]);
896908
897909
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
898910
const char *zNeedle = argv[2];
899911
const char *zHaystack = argv[3];
900912
int i;
@@ -919,11 +931,11 @@
919931
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
920932
){
921933
if( argc!=3 ){
922934
return Th_WrongNumArgs(interp, "string length string");
923935
}
924
- return Th_SetResultInt(interp, argl[2]);
936
+ return Th_SetResultInt(interp, TH1_LEN(argl[2]));
925937
}
926938
927939
/*
928940
** TH Syntax:
929941
**
@@ -938,12 +950,12 @@
938950
char *zPat, *zStr;
939951
int rc;
940952
if( argc!=4 ){
941953
return Th_WrongNumArgs(interp, "string match pattern string");
942954
}
943
- zPat = fossil_strndup(argv[2],argl[2]);
944
- zStr = fossil_strndup(argv[3],argl[3]);
955
+ zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
956
+ zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
945957
rc = sqlite3_strglob(zPat,zStr);
946958
fossil_free(zPat);
947959
fossil_free(zStr);
948960
return Th_SetResultInt(interp, !rc);
949961
}
@@ -961,23 +973,23 @@
961973
962974
if( argc!=5 ){
963975
return Th_WrongNumArgs(interp, "string range string first last");
964976
}
965977
966
- if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
967
- iEnd = argl[2];
978
+ if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
979
+ iEnd = TH1_LEN(argl[2]);
968980
}else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
969981
Th_ErrorMessage(
970
- interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
982
+ interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
971983
return TH_ERROR;
972984
}
973985
if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
974986
return TH_ERROR;
975987
}
976988
977989
if( iStart<0 ) iStart = 0;
978
- if( iEnd>=argl[2] ) iEnd = argl[2]-1;
990
+ if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
979991
if( iStart>iEnd ) iEnd = iStart-1;
980992
981993
return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
982994
}
983995
@@ -989,27 +1001,33 @@
9891001
static int string_repeat_command(
9901002
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
9911003
){
9921004
int n;
9931005
int i;
994
- int nByte;
1006
+ int sz;
1007
+ long long int nByte;
9951008
char *zByte;
9961009
9971010
if( argc!=4 ){
9981011
return Th_WrongNumArgs(interp, "string repeat string n");
9991012
}
10001013
if( Th_ToInt(interp, argv[3], argl[3], &n) ){
10011014
return TH_ERROR;
10021015
}
10031016
1004
- nByte = argl[2] * n;
1017
+ nByte = n;
1018
+ sz = TH1_LEN(argl[2]);
1019
+ nByte *= sz;
1020
+ TH1_SIZECHECK(nByte+1);
10051021
zByte = Th_Malloc(interp, nByte+1);
1006
- for(i=0; i<nByte; i+=argl[2]){
1007
- memcpy(&zByte[i], argv[2], argl[2]);
1022
+ for(i=0; i<nByte; i+=sz){
1023
+ memcpy(&zByte[i], argv[2], sz);
10081024
}
10091025
1010
- Th_SetResult(interp, zByte, nByte);
1026
+ n = nByte;
1027
+ TH1_XFER_TAINT(n, argl[2]);
1028
+ Th_SetResult(interp, zByte, n);
10111029
Th_Free(interp, zByte);
10121030
return TH_OK;
10131031
}
10141032
10151033
/*
@@ -1027,15 +1045,15 @@
10271045
10281046
if( argc!=3 ){
10291047
return Th_WrongNumArgs(interp, "string trim string");
10301048
}
10311049
z = argv[2];
1032
- n = argl[2];
1033
- if( argl[1]<5 || argv[1][4]=='l' ){
1050
+ n = TH1_LEN(argl[2]);
1051
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
10341052
while( n && th_isspace(z[0]) ){ z++; n--; }
10351053
}
1036
- if( argl[1]<5 || argv[1][4]=='r' ){
1054
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
10371055
while( n && th_isspace(z[n-1]) ){ n--; }
10381056
}
10391057
Th_SetResult(interp, z, n);
10401058
return TH_OK;
10411059
}
@@ -1051,11 +1069,11 @@
10511069
int rc;
10521070
10531071
if( argc!=3 ){
10541072
return Th_WrongNumArgs(interp, "info exists var");
10551073
}
1056
- rc = Th_ExistsVar(interp, argv[2], argl[2]);
1074
+ rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
10571075
Th_SetResultInt(interp, rc);
10581076
return TH_OK;
10591077
}
10601078
10611079
/*
@@ -1117,11 +1135,11 @@
11171135
int rc;
11181136
11191137
if( argc!=3 ){
11201138
return Th_WrongNumArgs(interp, "array exists var");
11211139
}
1122
- rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
1140
+ rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
11231141
Th_SetResultInt(interp, rc);
11241142
return TH_OK;
11251143
}
11261144
11271145
/*
@@ -1137,11 +1155,11 @@
11371155
int nElem = 0;
11381156
11391157
if( argc!=3 ){
11401158
return Th_WrongNumArgs(interp, "array names varname");
11411159
}
1142
- rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
1160
+ rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
11431161
if( rc!=TH_OK ){
11441162
return rc;
11451163
}
11461164
Th_SetResult(interp, zElem, nElem);
11471165
if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1179,11 @@
11611179
int *argl
11621180
){
11631181
if( argc!=2 ){
11641182
return Th_WrongNumArgs(interp, "unset var");
11651183
}
1166
- return Th_UnsetVar(interp, argv[1], argl[1]);
1184
+ return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
11671185
}
11681186
11691187
int Th_CallSubCommand(
11701188
Th_Interp *interp,
11711189
void *ctx,
@@ -1176,19 +1194,22 @@
11761194
){
11771195
if( argc>1 ){
11781196
int i;
11791197
for(i=0; aSub[i].zName; i++){
11801198
const char *zName = aSub[i].zName;
1181
- if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
1199
+ if( th_strlen(zName)==TH1_LEN(argl[1])
1200
+ && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
11821201
return aSub[i].xProc(interp, ctx, argc, argv, argl);
11831202
}
11841203
}
11851204
}
11861205
if(argc<2){
1187
- Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
1206
+ Th_ErrorMessage(interp, "Expected sub-command for",
1207
+ argv[0], TH1_LEN(argl[0]));
11881208
}else{
1189
- Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
1209
+ Th_ErrorMessage(interp, "Expected sub-command, got:",
1210
+ argv[1], TH1_LEN(argl[1]));
11901211
}
11911212
return TH_ERROR;
11921213
}
11931214
11941215
/*
@@ -1319,11 +1340,11 @@
13191340
int iFrame = -1;
13201341
13211342
if( argc!=2 && argc!=3 ){
13221343
return Th_WrongNumArgs(interp, "uplevel ?level? script...");
13231344
}
1324
- if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
1345
+ if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13251346
return TH_ERROR;
13261347
}
13271348
return Th_Eval(interp, iFrame, argv[argc-1], -1);
13281349
}
13291350
@@ -1342,19 +1363,20 @@
13421363
int iVar = 1;
13431364
int iFrame = -1;
13441365
int rc = TH_OK;
13451366
int i;
13461367
1347
- if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
1368
+ if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13481369
iVar++;
13491370
}
13501371
if( argc==iVar || (argc-iVar)%2 ){
13511372
return Th_WrongNumArgs(interp,
13521373
"upvar frame othervar myvar ?othervar myvar...?");
13531374
}
13541375
for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1355
- rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]);
1376
+ rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1377
+ iFrame, argv[i], TH1_LEN(argl[i]));
13561378
}
13571379
return rc;
13581380
}
13591381
13601382
/*
13611383
--- src/th_lang.c
+++ src/th_lang.c
@@ -39,11 +39,11 @@
39
40 rc = Th_Eval(interp, 0, argv[1], -1);
41 if( argc==3 ){
42 int nResult;
43 const char *zResult = Th_GetResult(interp, &nResult);
44 Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
45 }
46
47 Th_SetResultInt(interp, rc);
48 return TH_OK;
49 }
@@ -215,15 +215,18 @@
215 int *argl
216 ){
217 char *zList = 0;
218 int nList = 0;
219 int i;
 
220
221 for(i=1; i<argc; i++){
 
222 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
223 }
224
 
225 Th_SetResult(interp, zList, nList);
226 Th_Free(interp, zList);
227
228 return TH_OK;
229 }
@@ -244,10 +247,11 @@
244 int *argl
245 ){
246 char *zList = 0;
247 int nList = 0;
248 int i, rc;
 
249
250 if( argc<2 ){
251 return Th_WrongNumArgs(interp, "lappend var ...");
252 }
253 rc = Th_GetVar(interp, argv[1], argl[1]);
@@ -254,13 +258,15 @@
254 if( rc==TH_OK ){
255 zList = Th_TakeResult(interp, &nList);
256 }
257
258 for(i=2; i<argc; i++){
 
259 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
260 }
261
 
262 Th_SetVar(interp, argv[1], argl[1], zList, nList);
263 Th_SetResult(interp, zList, nList);
264 Th_Free(interp, zList);
265
266 return TH_OK;
@@ -356,13 +362,14 @@
356 return Th_WrongNumArgs(interp, "lsearch list string");
357 }
358
359 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
360 if( rc==TH_OK ){
 
361 Th_SetResultInt(interp, -1);
362 for(i=0; i<nCount; i++){
363 if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
364 Th_SetResultInt(interp, i);
365 break;
366 }
367 }
368 Th_Free(interp, azElem);
@@ -561,20 +568,21 @@
561 int nUsage = 0; /* Number of bytes at zUsage */
562
563 if( argc!=4 ){
564 return Th_WrongNumArgs(interp, "proc name arglist code");
565 }
566 if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
 
567 return TH_ERROR;
568 }
569
570 /* Allocate the new ProcDefn structure. */
571 nByte = sizeof(ProcDefn) + /* ProcDefn structure */
572 (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
573 (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
574 argl[3] + /* zProgram */
575 argl[2]; /* Space for copies of parameter names and default values */
576 p = (ProcDefn *)Th_Malloc(interp, nByte);
577
578 /* If the last parameter in the parameter list is "args", then set the
579 ** ProcDefn.hasArgs flag. The "args" parameter does not require an
580 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
@@ -590,12 +598,12 @@
590 p->azParam = (char **)&p[1];
591 p->anParam = (int *)&p->azParam[nParam];
592 p->azDefault = (char **)&p->anParam[nParam];
593 p->anDefault = (int *)&p->azDefault[nParam];
594 p->zProgram = (char *)&p->anDefault[nParam];
595 memcpy(p->zProgram, argv[3], argl[3]);
596 p->nProgram = argl[3];
597 zSpace = &p->zProgram[p->nProgram];
598
599 for(i=0; i<nParam; i++){
600 char **az;
601 int *an;
@@ -672,11 +680,12 @@
672 int *argl
673 ){
674 if( argc!=3 ){
675 return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
676 }
677 return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
 
678 }
679
680 /*
681 ** TH Syntax:
682 **
@@ -746,13 +755,13 @@
746 if( argc!=4 ){
747 return Th_WrongNumArgs(interp, "string compare str1 str2");
748 }
749
750 zLeft = argv[2];
751 nLeft = argl[2];
752 zRight = argv[3];
753 nRight = argl[3];
754
755 for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
756 iRes = zLeft[i]-zRight[i];
757 }
758 if( iRes==0 ){
@@ -779,12 +788,12 @@
779
780 if( argc!=4 ){
781 return Th_WrongNumArgs(interp, "string first needle haystack");
782 }
783
784 nNeedle = argl[2];
785 nHaystack = argl[3];
786
787 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
788 const char *zNeedle = argv[2];
789 const char *zHaystack = argv[3];
790 int i;
@@ -812,19 +821,19 @@
812
813 if( argc!=4 ){
814 return Th_WrongNumArgs(interp, "string index string index");
815 }
816
817 if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
818 iIndex = argl[2]-1;
819 }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
820 Th_ErrorMessage(
821 interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
822 return TH_ERROR;
823 }
824
825 if( iIndex>=0 && iIndex<argl[2] ){
826 return Th_SetResult(interp, &argv[2][iIndex], 1);
827 }else{
828 return Th_SetResult(interp, 0, 0);
829 }
830 }
@@ -838,41 +847,44 @@
838 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
839 ){
840 if( argc!=4 ){
841 return Th_WrongNumArgs(interp, "string is class string");
842 }
843 if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
844 int i;
845 int iRes = 1;
846
847 for(i=0; i<argl[3]; i++){
848 if( !th_isalnum(argv[3][i]) ){
849 iRes = 0;
850 }
851 }
852
853 return Th_SetResultInt(interp, iRes);
854 }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
855 double fVal;
856 if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
857 return Th_SetResultInt(interp, 1);
858 }
859 return Th_SetResultInt(interp, 0);
860 }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
861 int iVal;
862 if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
863 return Th_SetResultInt(interp, 1);
864 }
865 return Th_SetResultInt(interp, 0);
866 }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
867 if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
868 return Th_SetResultInt(interp, 1);
869 }
870 return Th_SetResultInt(interp, 0);
 
 
871 }else{
872 Th_ErrorMessage(interp,
873 "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
 
874 return TH_ERROR;
875 }
876 }
877
878 /*
@@ -889,12 +901,12 @@
889
890 if( argc!=4 ){
891 return Th_WrongNumArgs(interp, "string last needle haystack");
892 }
893
894 nNeedle = argl[2];
895 nHaystack = argl[3];
896
897 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
898 const char *zNeedle = argv[2];
899 const char *zHaystack = argv[3];
900 int i;
@@ -919,11 +931,11 @@
919 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
920 ){
921 if( argc!=3 ){
922 return Th_WrongNumArgs(interp, "string length string");
923 }
924 return Th_SetResultInt(interp, argl[2]);
925 }
926
927 /*
928 ** TH Syntax:
929 **
@@ -938,12 +950,12 @@
938 char *zPat, *zStr;
939 int rc;
940 if( argc!=4 ){
941 return Th_WrongNumArgs(interp, "string match pattern string");
942 }
943 zPat = fossil_strndup(argv[2],argl[2]);
944 zStr = fossil_strndup(argv[3],argl[3]);
945 rc = sqlite3_strglob(zPat,zStr);
946 fossil_free(zPat);
947 fossil_free(zStr);
948 return Th_SetResultInt(interp, !rc);
949 }
@@ -961,23 +973,23 @@
961
962 if( argc!=5 ){
963 return Th_WrongNumArgs(interp, "string range string first last");
964 }
965
966 if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
967 iEnd = argl[2];
968 }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
969 Th_ErrorMessage(
970 interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
971 return TH_ERROR;
972 }
973 if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
974 return TH_ERROR;
975 }
976
977 if( iStart<0 ) iStart = 0;
978 if( iEnd>=argl[2] ) iEnd = argl[2]-1;
979 if( iStart>iEnd ) iEnd = iStart-1;
980
981 return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
982 }
983
@@ -989,27 +1001,33 @@
989 static int string_repeat_command(
990 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
991 ){
992 int n;
993 int i;
994 int nByte;
 
995 char *zByte;
996
997 if( argc!=4 ){
998 return Th_WrongNumArgs(interp, "string repeat string n");
999 }
1000 if( Th_ToInt(interp, argv[3], argl[3], &n) ){
1001 return TH_ERROR;
1002 }
1003
1004 nByte = argl[2] * n;
 
 
 
1005 zByte = Th_Malloc(interp, nByte+1);
1006 for(i=0; i<nByte; i+=argl[2]){
1007 memcpy(&zByte[i], argv[2], argl[2]);
1008 }
1009
1010 Th_SetResult(interp, zByte, nByte);
 
 
1011 Th_Free(interp, zByte);
1012 return TH_OK;
1013 }
1014
1015 /*
@@ -1027,15 +1045,15 @@
1027
1028 if( argc!=3 ){
1029 return Th_WrongNumArgs(interp, "string trim string");
1030 }
1031 z = argv[2];
1032 n = argl[2];
1033 if( argl[1]<5 || argv[1][4]=='l' ){
1034 while( n && th_isspace(z[0]) ){ z++; n--; }
1035 }
1036 if( argl[1]<5 || argv[1][4]=='r' ){
1037 while( n && th_isspace(z[n-1]) ){ n--; }
1038 }
1039 Th_SetResult(interp, z, n);
1040 return TH_OK;
1041 }
@@ -1051,11 +1069,11 @@
1051 int rc;
1052
1053 if( argc!=3 ){
1054 return Th_WrongNumArgs(interp, "info exists var");
1055 }
1056 rc = Th_ExistsVar(interp, argv[2], argl[2]);
1057 Th_SetResultInt(interp, rc);
1058 return TH_OK;
1059 }
1060
1061 /*
@@ -1117,11 +1135,11 @@
1117 int rc;
1118
1119 if( argc!=3 ){
1120 return Th_WrongNumArgs(interp, "array exists var");
1121 }
1122 rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
1123 Th_SetResultInt(interp, rc);
1124 return TH_OK;
1125 }
1126
1127 /*
@@ -1137,11 +1155,11 @@
1137 int nElem = 0;
1138
1139 if( argc!=3 ){
1140 return Th_WrongNumArgs(interp, "array names varname");
1141 }
1142 rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
1143 if( rc!=TH_OK ){
1144 return rc;
1145 }
1146 Th_SetResult(interp, zElem, nElem);
1147 if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1179,11 @@
1161 int *argl
1162 ){
1163 if( argc!=2 ){
1164 return Th_WrongNumArgs(interp, "unset var");
1165 }
1166 return Th_UnsetVar(interp, argv[1], argl[1]);
1167 }
1168
1169 int Th_CallSubCommand(
1170 Th_Interp *interp,
1171 void *ctx,
@@ -1176,19 +1194,22 @@
1176 ){
1177 if( argc>1 ){
1178 int i;
1179 for(i=0; aSub[i].zName; i++){
1180 const char *zName = aSub[i].zName;
1181 if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
 
1182 return aSub[i].xProc(interp, ctx, argc, argv, argl);
1183 }
1184 }
1185 }
1186 if(argc<2){
1187 Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
 
1188 }else{
1189 Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
 
1190 }
1191 return TH_ERROR;
1192 }
1193
1194 /*
@@ -1319,11 +1340,11 @@
1319 int iFrame = -1;
1320
1321 if( argc!=2 && argc!=3 ){
1322 return Th_WrongNumArgs(interp, "uplevel ?level? script...");
1323 }
1324 if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
1325 return TH_ERROR;
1326 }
1327 return Th_Eval(interp, iFrame, argv[argc-1], -1);
1328 }
1329
@@ -1342,19 +1363,20 @@
1342 int iVar = 1;
1343 int iFrame = -1;
1344 int rc = TH_OK;
1345 int i;
1346
1347 if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
1348 iVar++;
1349 }
1350 if( argc==iVar || (argc-iVar)%2 ){
1351 return Th_WrongNumArgs(interp,
1352 "upvar frame othervar myvar ?othervar myvar...?");
1353 }
1354 for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1355 rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]);
 
1356 }
1357 return rc;
1358 }
1359
1360 /*
1361
--- src/th_lang.c
+++ src/th_lang.c
@@ -39,11 +39,11 @@
39
40 rc = Th_Eval(interp, 0, argv[1], -1);
41 if( argc==3 ){
42 int nResult;
43 const char *zResult = Th_GetResult(interp, &nResult);
44 Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
45 }
46
47 Th_SetResultInt(interp, rc);
48 return TH_OK;
49 }
@@ -215,15 +215,18 @@
215 int *argl
216 ){
217 char *zList = 0;
218 int nList = 0;
219 int i;
220 int bTaint = 0;
221
222 for(i=1; i<argc; i++){
223 TH1_XFER_TAINT(bTaint,argl[i]);
224 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
225 }
226
227 TH1_XFER_TAINT(nList, bTaint);
228 Th_SetResult(interp, zList, nList);
229 Th_Free(interp, zList);
230
231 return TH_OK;
232 }
@@ -244,10 +247,11 @@
247 int *argl
248 ){
249 char *zList = 0;
250 int nList = 0;
251 int i, rc;
252 int bTaint = 0;
253
254 if( argc<2 ){
255 return Th_WrongNumArgs(interp, "lappend var ...");
256 }
257 rc = Th_GetVar(interp, argv[1], argl[1]);
@@ -254,13 +258,15 @@
258 if( rc==TH_OK ){
259 zList = Th_TakeResult(interp, &nList);
260 }
261
262 for(i=2; i<argc; i++){
263 TH1_XFER_TAINT(bTaint, argl[i]);
264 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
265 }
266
267 TH1_XFER_TAINT(nList, bTaint);
268 Th_SetVar(interp, argv[1], argl[1], zList, nList);
269 Th_SetResult(interp, zList, nList);
270 Th_Free(interp, zList);
271
272 return TH_OK;
@@ -356,13 +362,14 @@
362 return Th_WrongNumArgs(interp, "lsearch list string");
363 }
364
365 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
366 if( rc==TH_OK ){
367 int nn = TH1_LEN(argl[2]);
368 Th_SetResultInt(interp, -1);
369 for(i=0; i<nCount; i++){
370 if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
371 Th_SetResultInt(interp, i);
372 break;
373 }
374 }
375 Th_Free(interp, azElem);
@@ -561,20 +568,21 @@
568 int nUsage = 0; /* Number of bytes at zUsage */
569
570 if( argc!=4 ){
571 return Th_WrongNumArgs(interp, "proc name arglist code");
572 }
573 if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
574 &azParam, &anParam, &nParam) ){
575 return TH_ERROR;
576 }
577
578 /* Allocate the new ProcDefn structure. */
579 nByte = sizeof(ProcDefn) + /* ProcDefn structure */
580 (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
581 (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
582 TH1_LEN(argl[3]) + /* zProgram */
583 TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
584 p = (ProcDefn *)Th_Malloc(interp, nByte);
585
586 /* If the last parameter in the parameter list is "args", then set the
587 ** ProcDefn.hasArgs flag. The "args" parameter does not require an
588 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
@@ -590,12 +598,12 @@
598 p->azParam = (char **)&p[1];
599 p->anParam = (int *)&p->azParam[nParam];
600 p->azDefault = (char **)&p->anParam[nParam];
601 p->anDefault = (int *)&p->azDefault[nParam];
602 p->zProgram = (char *)&p->anDefault[nParam];
603 memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
604 p->nProgram = TH1_LEN(argl[3]);
605 zSpace = &p->zProgram[p->nProgram];
606
607 for(i=0; i<nParam; i++){
608 char **az;
609 int *an;
@@ -672,11 +680,12 @@
680 int *argl
681 ){
682 if( argc!=3 ){
683 return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
684 }
685 return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
686 argv[2], TH1_LEN(argl[2]));
687 }
688
689 /*
690 ** TH Syntax:
691 **
@@ -746,13 +755,13 @@
755 if( argc!=4 ){
756 return Th_WrongNumArgs(interp, "string compare str1 str2");
757 }
758
759 zLeft = argv[2];
760 nLeft = TH1_LEN(argl[2]);
761 zRight = argv[3];
762 nRight = TH1_LEN(argl[3]);
763
764 for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
765 iRes = zLeft[i]-zRight[i];
766 }
767 if( iRes==0 ){
@@ -779,12 +788,12 @@
788
789 if( argc!=4 ){
790 return Th_WrongNumArgs(interp, "string first needle haystack");
791 }
792
793 nNeedle = TH1_LEN(argl[2]);
794 nHaystack = TH1_LEN(argl[3]);
795
796 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
797 const char *zNeedle = argv[2];
798 const char *zHaystack = argv[3];
799 int i;
@@ -812,19 +821,19 @@
821
822 if( argc!=4 ){
823 return Th_WrongNumArgs(interp, "string index string index");
824 }
825
826 if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
827 iIndex = TH1_LEN(argl[2])-1;
828 }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
829 Th_ErrorMessage(
830 interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
831 return TH_ERROR;
832 }
833
834 if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
835 return Th_SetResult(interp, &argv[2][iIndex], 1);
836 }else{
837 return Th_SetResult(interp, 0, 0);
838 }
839 }
@@ -838,41 +847,44 @@
847 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
848 ){
849 if( argc!=4 ){
850 return Th_WrongNumArgs(interp, "string is class string");
851 }
852 if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
853 int i;
854 int iRes = 1;
855
856 for(i=0; i<TH1_LEN(argl[3]); i++){
857 if( !th_isalnum(argv[3][i]) ){
858 iRes = 0;
859 }
860 }
861
862 return Th_SetResultInt(interp, iRes);
863 }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
864 double fVal;
865 if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
866 return Th_SetResultInt(interp, 1);
867 }
868 return Th_SetResultInt(interp, 0);
869 }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
870 int iVal;
871 if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
872 return Th_SetResultInt(interp, 1);
873 }
874 return Th_SetResultInt(interp, 0);
875 }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
876 if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
877 return Th_SetResultInt(interp, 1);
878 }
879 return Th_SetResultInt(interp, 0);
880 }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
881 return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
882 }else{
883 Th_ErrorMessage(interp,
884 "Expected alnum, double, integer, list, or tainted, got:",
885 argv[2], TH1_LEN(argl[2]));
886 return TH_ERROR;
887 }
888 }
889
890 /*
@@ -889,12 +901,12 @@
901
902 if( argc!=4 ){
903 return Th_WrongNumArgs(interp, "string last needle haystack");
904 }
905
906 nNeedle = TH1_LEN(argl[2]);
907 nHaystack = TH1_LEN(argl[3]);
908
909 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
910 const char *zNeedle = argv[2];
911 const char *zHaystack = argv[3];
912 int i;
@@ -919,11 +931,11 @@
931 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
932 ){
933 if( argc!=3 ){
934 return Th_WrongNumArgs(interp, "string length string");
935 }
936 return Th_SetResultInt(interp, TH1_LEN(argl[2]));
937 }
938
939 /*
940 ** TH Syntax:
941 **
@@ -938,12 +950,12 @@
950 char *zPat, *zStr;
951 int rc;
952 if( argc!=4 ){
953 return Th_WrongNumArgs(interp, "string match pattern string");
954 }
955 zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
956 zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
957 rc = sqlite3_strglob(zPat,zStr);
958 fossil_free(zPat);
959 fossil_free(zStr);
960 return Th_SetResultInt(interp, !rc);
961 }
@@ -961,23 +973,23 @@
973
974 if( argc!=5 ){
975 return Th_WrongNumArgs(interp, "string range string first last");
976 }
977
978 if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
979 iEnd = TH1_LEN(argl[2]);
980 }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
981 Th_ErrorMessage(
982 interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
983 return TH_ERROR;
984 }
985 if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
986 return TH_ERROR;
987 }
988
989 if( iStart<0 ) iStart = 0;
990 if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
991 if( iStart>iEnd ) iEnd = iStart-1;
992
993 return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
994 }
995
@@ -989,27 +1001,33 @@
1001 static int string_repeat_command(
1002 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
1003 ){
1004 int n;
1005 int i;
1006 int sz;
1007 long long int nByte;
1008 char *zByte;
1009
1010 if( argc!=4 ){
1011 return Th_WrongNumArgs(interp, "string repeat string n");
1012 }
1013 if( Th_ToInt(interp, argv[3], argl[3], &n) ){
1014 return TH_ERROR;
1015 }
1016
1017 nByte = n;
1018 sz = TH1_LEN(argl[2]);
1019 nByte *= sz;
1020 TH1_SIZECHECK(nByte+1);
1021 zByte = Th_Malloc(interp, nByte+1);
1022 for(i=0; i<nByte; i+=sz){
1023 memcpy(&zByte[i], argv[2], sz);
1024 }
1025
1026 n = nByte;
1027 TH1_XFER_TAINT(n, argl[2]);
1028 Th_SetResult(interp, zByte, n);
1029 Th_Free(interp, zByte);
1030 return TH_OK;
1031 }
1032
1033 /*
@@ -1027,15 +1045,15 @@
1045
1046 if( argc!=3 ){
1047 return Th_WrongNumArgs(interp, "string trim string");
1048 }
1049 z = argv[2];
1050 n = TH1_LEN(argl[2]);
1051 if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
1052 while( n && th_isspace(z[0]) ){ z++; n--; }
1053 }
1054 if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
1055 while( n && th_isspace(z[n-1]) ){ n--; }
1056 }
1057 Th_SetResult(interp, z, n);
1058 return TH_OK;
1059 }
@@ -1051,11 +1069,11 @@
1069 int rc;
1070
1071 if( argc!=3 ){
1072 return Th_WrongNumArgs(interp, "info exists var");
1073 }
1074 rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
1075 Th_SetResultInt(interp, rc);
1076 return TH_OK;
1077 }
1078
1079 /*
@@ -1117,11 +1135,11 @@
1135 int rc;
1136
1137 if( argc!=3 ){
1138 return Th_WrongNumArgs(interp, "array exists var");
1139 }
1140 rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
1141 Th_SetResultInt(interp, rc);
1142 return TH_OK;
1143 }
1144
1145 /*
@@ -1137,11 +1155,11 @@
1155 int nElem = 0;
1156
1157 if( argc!=3 ){
1158 return Th_WrongNumArgs(interp, "array names varname");
1159 }
1160 rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
1161 if( rc!=TH_OK ){
1162 return rc;
1163 }
1164 Th_SetResult(interp, zElem, nElem);
1165 if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1179,11 @@
1179 int *argl
1180 ){
1181 if( argc!=2 ){
1182 return Th_WrongNumArgs(interp, "unset var");
1183 }
1184 return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
1185 }
1186
1187 int Th_CallSubCommand(
1188 Th_Interp *interp,
1189 void *ctx,
@@ -1176,19 +1194,22 @@
1194 ){
1195 if( argc>1 ){
1196 int i;
1197 for(i=0; aSub[i].zName; i++){
1198 const char *zName = aSub[i].zName;
1199 if( th_strlen(zName)==TH1_LEN(argl[1])
1200 && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
1201 return aSub[i].xProc(interp, ctx, argc, argv, argl);
1202 }
1203 }
1204 }
1205 if(argc<2){
1206 Th_ErrorMessage(interp, "Expected sub-command for",
1207 argv[0], TH1_LEN(argl[0]));
1208 }else{
1209 Th_ErrorMessage(interp, "Expected sub-command, got:",
1210 argv[1], TH1_LEN(argl[1]));
1211 }
1212 return TH_ERROR;
1213 }
1214
1215 /*
@@ -1319,11 +1340,11 @@
1340 int iFrame = -1;
1341
1342 if( argc!=2 && argc!=3 ){
1343 return Th_WrongNumArgs(interp, "uplevel ?level? script...");
1344 }
1345 if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1346 return TH_ERROR;
1347 }
1348 return Th_Eval(interp, iFrame, argv[argc-1], -1);
1349 }
1350
@@ -1342,19 +1363,20 @@
1363 int iVar = 1;
1364 int iFrame = -1;
1365 int rc = TH_OK;
1366 int i;
1367
1368 if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1369 iVar++;
1370 }
1371 if( argc==iVar || (argc-iVar)%2 ){
1372 return Th_WrongNumArgs(interp,
1373 "upvar frame othervar myvar ?othervar myvar...?");
1374 }
1375 for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1376 rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1377 iFrame, argv[i], TH1_LEN(argl[i]));
1378 }
1379 return rc;
1380 }
1381
1382 /*
1383
+196 -37
--- src/th_main.c
+++ src/th_main.c
@@ -262,11 +262,11 @@
262262
){
263263
char *zOut;
264264
if( argc!=2 ){
265265
return Th_WrongNumArgs(interp, "httpize STRING");
266266
}
267
- zOut = httpize((char*)argv[1], argl[1]);
267
+ zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
268268
Th_SetResult(interp, zOut, -1);
269269
free(zOut);
270270
return TH_OK;
271271
}
272272
@@ -291,11 +291,12 @@
291291
if( argc<2 || argc>3 ){
292292
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293293
}
294294
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295295
if( g.thTrace ){
296
- Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
296
+ Th_Trace("enable_output {%.*s} -> %d<br>\n",
297
+ TH1_LEN(argl[1]),argv[1],enableOutput);
297298
}
298299
return rc;
299300
}
300301
301302
/*
@@ -322,11 +323,11 @@
322323
buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
323324
Th_SetResultInt(g.interp, buul);
324325
if(argc>1){
325326
if( g.thTrace ){
326327
Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
327
- argl[1],argv[1],buul);
328
+ TH1_LEN(argl[1]),argv[1],buul);
328329
}
329330
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
330331
if(!rc){
331332
if(buul){
332333
g.th1Flags &= ~TH_INIT_NO_ENCODE;
@@ -381,19 +382,23 @@
381382
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
382383
**
383384
** If pOut is NULL and the global pThOut is not then that blob
384385
** is used for output.
385386
*/
386
-static void sendText(Blob * pOut, const char *z, int n, int encode){
387
+static void sendText(Blob *pOut, const char *z, int n, int encode){
387388
if(0==pOut && pThOut!=0){
388389
pOut = pThOut;
389390
}
390391
if(TH_INIT_NO_ENCODE & g.th1Flags){
391392
encode = 0;
392393
}
393394
if( enableOutput && n ){
394
- if( n<0 ) n = strlen(z);
395
+ if( n<0 ){
396
+ n = strlen(z);
397
+ }else{
398
+ n = TH1_LEN(n);
399
+ }
395400
if( encode ){
396401
z = htmlize(z, n);
397402
n = strlen(z);
398403
}
399404
if(pOut!=0){
@@ -525,14 +530,23 @@
525530
void *pConvert,
526531
int argc,
527532
const char **argv,
528533
int *argl
529534
){
535
+ int encode = *(unsigned int*)pConvert;
536
+ int n;
530537
if( argc!=2 ){
531538
return Th_WrongNumArgs(interp, "puts STRING");
532539
}
533
- sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
540
+ n = argl[1];
541
+ if( encode==0 && n>0 && TH1_TAINTED(n) ){
542
+ if( Th_ReportTaint(interp, "output string", argv[1], n) ){
543
+ return TH_ERROR;
544
+ }
545
+ n = TH1_LEN(n);
546
+ }
547
+ sendText(0,(char*)argv[1], n, encode);
534548
return TH_OK;
535549
}
536550
537551
/*
538552
** TH1 command: redirect URL ?withMethod?
@@ -557,10 +571,15 @@
557571
}
558572
if( argc==3 ){
559573
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560574
return TH_ERROR;
561575
}
576
+ }
577
+ if( TH1_TAINTED(argl[1])
578
+ && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
579
+ ){
580
+ return TH_ERROR;
562581
}
563582
if( withMethod ){
564583
cgi_redirect_with_method(argv[1]);
565584
}else{
566585
cgi_redirect(argv[1]);
@@ -660,11 +679,11 @@
660679
int nValue = 0;
661680
if( argc!=2 ){
662681
return Th_WrongNumArgs(interp, "markdown STRING");
663682
}
664683
blob_zero(&src);
665
- blob_init(&src, (char*)argv[1], argl[1]);
684
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
666685
blob_zero(&title); blob_zero(&body);
667686
markdown_to_html(&src, &title, &body);
668687
Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669688
Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670689
Th_SetResult(interp, zValue, nValue);
@@ -690,11 +709,11 @@
690709
if( argc!=2 ){
691710
return Th_WrongNumArgs(interp, "wiki STRING");
692711
}
693712
if( enableOutput ){
694713
Blob src;
695
- blob_init(&src, (char*)argv[1], argl[1]);
714
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
696715
wiki_convert(&src, 0, flags);
697716
blob_reset(&src);
698717
}
699718
return TH_OK;
700719
}
@@ -735,11 +754,11 @@
735754
){
736755
char *zOut;
737756
if( argc!=2 ){
738757
return Th_WrongNumArgs(interp, "htmlize STRING");
739758
}
740
- zOut = htmlize((char*)argv[1], argl[1]);
759
+ zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
741760
Th_SetResult(interp, zOut, -1);
742761
free(zOut);
743762
return TH_OK;
744763
}
745764
@@ -757,11 +776,11 @@
757776
){
758777
char *zOut;
759778
if( argc!=2 ){
760779
return Th_WrongNumArgs(interp, "encode64 STRING");
761780
}
762
- zOut = encode64((char*)argv[1], argl[1]);
781
+ zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
763782
Th_SetResult(interp, zOut, -1);
764783
free(zOut);
765784
return TH_OK;
766785
}
767786
@@ -778,11 +797,11 @@
778797
int argc,
779798
const char **argv,
780799
int *argl
781800
){
782801
char *zOut;
783
- if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
802
+ if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
784803
zOut = db_text("??", "SELECT datetime('now',toLocal())");
785804
}else{
786805
zOut = db_text("??", "SELECT datetime('now')");
787806
}
788807
Th_SetResult(interp, zOut, -1);
@@ -810,13 +829,13 @@
810829
if( argc<2 ){
811830
return Th_WrongNumArgs(interp, "hascap STRING ...");
812831
}
813832
for(i=1; rc==1 && i<argc; i++){
814833
if( g.thTrace ){
815
- Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
834
+ Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
816835
}
817
- rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
836
+ rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
818837
}
819838
if( g.thTrace ){
820839
Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821840
Th_Free(interp, zCapList);
822841
}
@@ -858,11 +877,11 @@
858877
int i;
859878
860879
if( argc!=2 ){
861880
return Th_WrongNumArgs(interp, "capexpr EXPR");
862881
}
863
- rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
882
+ rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
864883
if( rc ) return rc;
865884
rc = 0;
866885
for(i=0; i<nCap; i++){
867886
if( azCap[i][0]=='!' ){
868887
rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +940,12 @@
921940
if( argc<2 ){
922941
return Th_WrongNumArgs(interp, "hascap STRING ...");
923942
}
924943
for(i=1; i<argc && rc; i++){
925944
int match = 0;
926
- for(j=0; j<argl[i]; j++){
945
+ int nn = TH1_LEN(argl[i]);
946
+ for(j=0; j<nn; j++){
927947
switch( argv[i][j] ){
928948
case 'c': match |= searchCap & SRCH_CKIN; break;
929949
case 'd': match |= searchCap & SRCH_DOC; break;
930950
case 't': match |= searchCap & SRCH_TKT; break;
931951
case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +952,11 @@
932952
}
933953
}
934954
if( !match ) rc = 0;
935955
}
936956
if( g.thTrace ){
937
- Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
957
+ Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
938958
}
939959
Th_SetResultInt(interp, rc);
940960
return TH_OK;
941961
}
942962
@@ -1051,11 +1071,11 @@
10511071
#endif
10521072
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
10531073
rc = 1;
10541074
}
10551075
if( g.thTrace ){
1056
- Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1076
+ Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
10571077
}
10581078
Th_SetResultInt(interp, rc);
10591079
return TH_OK;
10601080
}
10611081
@@ -1104,18 +1124,20 @@
11041124
const char **argv,
11051125
int *argl
11061126
){
11071127
int rc = 0;
11081128
int i;
1129
+ int nn;
11091130
if( argc!=2 ){
11101131
return Th_WrongNumArgs(interp, "anycap STRING");
11111132
}
1112
- for(i=0; rc==0 && i<argl[1]; i++){
1133
+ nn = TH1_LEN(argl[1]);
1134
+ for(i=0; rc==0 && i<nn; i++){
11131135
rc = login_has_capability((char*)&argv[1][i],1,0);
11141136
}
11151137
if( g.thTrace ){
1116
- Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1138
+ Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
11171139
}
11181140
Th_SetResultInt(interp, rc);
11191141
return TH_OK;
11201142
}
11211143
@@ -1140,22 +1162,23 @@
11401162
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
11411163
}
11421164
if( enableOutput ){
11431165
int height;
11441166
Blob name;
1145
- int nValue;
1167
+ int nValue = 0;
11461168
const char *zValue;
11471169
char *z, *zH;
11481170
int nElem;
11491171
int *aszElem;
11501172
char **azElem;
11511173
int i;
11521174
11531175
if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1154
- Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
1155
- blob_init(&name, (char*)argv[1], argl[1]);
1176
+ Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1177
+ blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
11561178
zValue = Th_Fetch(blob_str(&name), &nValue);
1179
+ nValue = TH1_LEN(nValue);
11571180
zH = htmlize(blob_buffer(&name), blob_size(&name));
11581181
z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
11591182
free(zH);
11601183
sendText(0,z, -1, 0);
11611184
free(z);
@@ -1247,11 +1270,11 @@
12471270
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
12481271
}
12491272
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
12501273
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
12511274
z = argv[1];
1252
- size = argl[1];
1275
+ size = TH1_LEN(argl[1]);
12531276
for(n=1, i=0; i<size; i++){
12541277
if( z[i]=='\n' ){
12551278
n++;
12561279
if( n>=iMax ) break;
12571280
}
@@ -1407,11 +1430,12 @@
14071430
return TH_OK;
14081431
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
14091432
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
14101433
return TH_OK;
14111434
}else{
1412
- Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
1435
+ Th_ErrorMessage(interp, "unsupported global state:",
1436
+ argv[1], TH1_LEN(argl[1]));
14131437
return TH_ERROR;
14141438
}
14151439
}
14161440
14171441
/*
@@ -1848,10 +1872,47 @@
18481872
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
18491873
Th_SetResult(interp, zUTime, -1);
18501874
return TH_OK;
18511875
}
18521876
1877
+/*
1878
+** TH1 command: taint STRING
1879
+**
1880
+** Return a copy of STRING that is marked as tainted.
1881
+*/
1882
+static int taintCmd(
1883
+ Th_Interp *interp,
1884
+ void *p,
1885
+ int argc,
1886
+ const char **argv,
1887
+ int *argl
1888
+){
1889
+ if( argc!=2 ){
1890
+ return Th_WrongNumArgs(interp, "STRING");
1891
+ }
1892
+ Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1893
+ return TH_OK;
1894
+}
1895
+
1896
+/*
1897
+** TH1 command: untaint STRING
1898
+**
1899
+** Return a copy of STRING that is marked as untainted.
1900
+*/
1901
+static int untaintCmd(
1902
+ Th_Interp *interp,
1903
+ void *p,
1904
+ int argc,
1905
+ const char **argv,
1906
+ int *argl
1907
+){
1908
+ if( argc!=2 ){
1909
+ return Th_WrongNumArgs(interp, "STRING");
1910
+ }
1911
+ Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1912
+ return TH_OK;
1913
+}
18531914
18541915
/*
18551916
** TH1 command: randhex N
18561917
**
18571918
** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1984,13 @@
19231984
int res = TH_OK;
19241985
int nVar;
19251986
char *zErr = 0;
19261987
int noComplain = 0;
19271988
1928
- if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
1989
+ if( argc>3 && TH1_LEN(argl[1])==11
1990
+ && strncmp(argv[1], "-nocomplain", 11)==0
1991
+ ){
19291992
argc--;
19301993
argv++;
19311994
argl++;
19321995
noComplain = 1;
19331996
}
@@ -1939,15 +2002,22 @@
19392002
Th_ErrorMessage(interp, "database is not open", 0, 0);
19402003
return TH_ERROR;
19412004
}
19422005
zSql = argv[1];
19432006
nSql = argl[1];
2007
+ if( TH1_TAINTED(nSql) ){
2008
+ if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
2009
+ return TH_ERROR;
2010
+ }
2011
+ nSql = TH1_LEN(nSql);
2012
+ }
2013
+
19442014
while( res==TH_OK && nSql>0 ){
19452015
zErr = 0;
19462016
report_restrict_sql(&zErr);
19472017
g.dbIgnoreErrors++;
1948
- rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
2018
+ rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
19492019
g.dbIgnoreErrors--;
19502020
report_unrestrict_sql();
19512021
if( rc!=0 || zErr!=0 ){
19522022
if( noComplain ) return TH_OK;
19532023
Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +2034,31 @@
19642034
int szVar = zVar ? th_strlen(zVar) : 0;
19652035
if( szVar>1 && zVar[0]=='$'
19662036
&& Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
19672037
int nVal;
19682038
const char *zVal = Th_GetResult(interp, &nVal);
1969
- sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
2039
+ sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
19702040
}
19712041
}
19722042
while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
19732043
int nCol = sqlite3_column_count(pStmt);
19742044
for(i=0; i<nCol; i++){
19752045
const char *zCol = sqlite3_column_name(pStmt, i);
19762046
int szCol = th_strlen(zCol);
19772047
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
19782048
int szVal = sqlite3_column_bytes(pStmt, i);
1979
- Th_SetVar(interp, zCol, szCol, zVal, szVal);
2049
+ Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
19802050
}
19812051
if( g.thTrace ){
1982
- Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
2052
+ Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
19832053
}
1984
- res = Th_Eval(interp, 0, argv[2], argl[2]);
2054
+ res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
19852055
if( g.thTrace ){
19862056
int nTrRes;
19872057
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
19882058
Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989
- Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
2059
+ Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
19902060
}
19912061
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
19922062
}
19932063
rc = sqlite3_finalize(pStmt);
19942064
if( rc!=SQLITE_OK ){
@@ -2038,11 +2108,11 @@
20382108
Th_SetResult(interp, 0, 0);
20392109
rc = TH_OK;
20402110
}
20412111
if( g.thTrace ){
20422112
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043
- argl[nArg], argv[nArg], rc);
2113
+ TH1_LEN(argl[nArg]), argv[nArg], rc);
20442114
}
20452115
return rc;
20462116
}
20472117
20482118
/*
@@ -2121,11 +2191,11 @@
21212191
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
21222192
}
21232193
zErr = re_compile(&pRe, argv[nArg], noCase);
21242194
if( !zErr ){
21252195
Th_SetResultInt(interp, re_match(pRe,
2126
- (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2196
+ (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
21272197
rc = TH_OK;
21282198
}else{
21292199
Th_SetResult(interp, zErr, -1);
21302200
rc = TH_ERROR;
21312201
}
@@ -2160,11 +2230,11 @@
21602230
UrlData urlData;
21612231
21622232
if( argc<2 || argc>5 ){
21632233
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
21642234
}
2165
- if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2235
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
21662236
fAsynchronous = 1; nArg++;
21672237
}
21682238
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
21692239
if( nArg+1!=argc && nArg+2!=argc ){
21702240
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2259,11 @@
21892259
return TH_ERROR;
21902260
}
21912261
re_free(pRe);
21922262
blob_zero(&payload);
21932263
if( nArg+2==argc ){
2194
- blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2264
+ blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
21952265
zType = "POST";
21962266
}else{
21972267
zType = "GET";
21982268
}
21992269
if( fAsynchronous ){
@@ -2268,11 +2338,11 @@
22682338
if( argc!=2 ){
22692339
return Th_WrongNumArgs(interp, "captureTh1 STRING");
22702340
}
22712341
pOrig = Th_SetOutputBlob(&out);
22722342
zStr = argv[1];
2273
- nStr = argl[1];
2343
+ nStr = TH1_LEN(argl[1]);
22742344
rc = Th_Eval(g.interp, 0, zStr, nStr);
22752345
Th_SetOutputBlob(pOrig);
22762346
if(0==rc){
22772347
Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
22782348
}
@@ -2387,13 +2457,15 @@
23872457
{"setting", settingCmd, 0},
23882458
{"styleFooter", styleFooterCmd, 0},
23892459
{"styleHeader", styleHeaderCmd, 0},
23902460
{"styleScript", styleScriptCmd, 0},
23912461
{"submenu", submenuCmd, 0},
2462
+ {"taint", taintCmd, 0},
23922463
{"tclReady", tclReadyCmd, 0},
23932464
{"trace", traceCmd, 0},
23942465
{"stime", stimeCmd, 0},
2466
+ {"untaint", untaintCmd, 0},
23952467
{"unversioned", unversionedCmd, 0},
23962468
{"utime", utimeCmd, 0},
23972469
{"verifyCsrf", verifyCsrfCmd, 0},
23982470
{"verifyLogin", verifyLoginCmd, 0},
23992471
{"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2566,26 @@
24942566
Th_Trace("set %h {%h}<br>\n", zName, zValue);
24952567
}
24962568
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
24972569
}
24982570
}
2571
+
2572
+/*
2573
+** Store a string value in a variable in the interpreter
2574
+** with the "taint" marking, so that TH1 knows that this
2575
+** variable contains content under the control of the remote
2576
+** user and presents a risk of XSS or SQL-injection attacks.
2577
+*/
2578
+void Th_StoreUnsafe(const char *zName, const char *zValue){
2579
+ Th_FossilInit(TH_INIT_DEFAULT);
2580
+ if( zValue ){
2581
+ if( g.thTrace ){
2582
+ Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2583
+ }
2584
+ Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2585
+ }
2586
+}
24992587
25002588
/*
25012589
** Appends an element to a TH1 list value. This function is called by the
25022590
** transfer subsystem; therefore, it must be very careful to avoid doing
25032591
** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2768,11 @@
26802768
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
26812769
/*
26822770
** Make sure that the TH1 script error was not caused by a "missing"
26832771
** command hook handler as that is not actually an error condition.
26842772
*/
2773
+ nResult = TH1_LEN(nResult);
26852774
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
26862775
sendError(0,zResult, nResult, 0);
26872776
}else{
26882777
/*
26892778
** There is no command hook handler "installed". This situation
@@ -2767,10 +2856,11 @@
27672856
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
27682857
/*
27692858
** Make sure that the TH1 script error was not caused by a "missing"
27702859
** webpage hook handler as that is not actually an error condition.
27712860
*/
2861
+ nResult = TH1_LEN(nResult);
27722862
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
27732863
sendError(0,zResult, nResult, 1);
27742864
}else{
27752865
/*
27762866
** There is no webpage hook handler "installed". This situation
@@ -2907,11 +2997,11 @@
29072997
rc = Th_Eval(g.interp, 0, (const char*)z, i);
29082998
if( g.thTrace ){
29092999
int nTrRes;
29103000
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
29113001
Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912
- Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
3002
+ Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
29133003
}
29143004
if( rc!=TH_OK ) break;
29153005
z += i;
29163006
if( z[0] ){ z += 6; }
29173007
i = 0;
@@ -2953,10 +3043,77 @@
29533043
** as appropriate. We need to pass on g.th1Flags for the case of
29543044
** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
29553045
** inadvertently toggled off by a recursive call.
29563046
*/;
29573047
}
3048
+
3049
+/*
3050
+** SETTING: vuln-report width=8 default=log
3051
+**
3052
+** This setting controls Fossil's behavior when it encounters a potential
3053
+** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3054
+** scripts. Choices are:
3055
+**
3056
+** off Do nothing. Ignore the vulnerability.
3057
+**
3058
+** log Write a report of the problem into the error log.
3059
+**
3060
+** block Like "log" but also prevent the offending TH1 command
3061
+** from running.
3062
+**
3063
+** fatal Render an error message page instead of the requested
3064
+** page.
3065
+*/
3066
+
3067
+/*
3068
+** Report misuse of a tainted string in TH1.
3069
+**
3070
+** The behavior depends on the vuln-report setting. If "off", this routine
3071
+** is a no-op. Otherwise, right a message into the error log. If
3072
+** vuln-report is "log", that is all that happens. But for any other
3073
+** value of vuln-report, a fatal error is raised.
3074
+*/
3075
+int Th_ReportTaint(
3076
+ Th_Interp *interp, /* Report error here, if an error is reported */
3077
+ const char *zWhere, /* Where the tainted string appears */
3078
+ const char *zStr, /* The tainted string */
3079
+ int nStr /* Length of the tainted string */
3080
+){
3081
+ char *zDisp; /* Dispensation */
3082
+ const char *zVulnType; /* Type of vulnerability */
3083
+
3084
+ zDisp = db_get("vuln-report","log");
3085
+ if( is_false(zDisp) ) return 0;
3086
+ if( strstr(zWhere,"SQL")!=0 ){
3087
+ zVulnType = "SQL-injection";
3088
+ }else{
3089
+ zVulnType = "XSS";
3090
+ }
3091
+ nStr = TH1_LEN(nStr);
3092
+ fossil_errorlog("possible %s vulnerability due to tainted TH1 %s: \"%.*s\"",
3093
+ zVulnType, zWhere, nStr, zStr);
3094
+ if( strcmp(zDisp,"log")==0 ){
3095
+ return 0;
3096
+ }
3097
+ if( strcmp(zDisp,"block")==0 ){
3098
+ char *z = mprintf("tainted %s: \"", zWhere);
3099
+ Th_ErrorMessage(interp, z, zStr, nStr);
3100
+ fossil_free(z);
3101
+ }else{
3102
+ char *z = mprintf("%#h", nStr, zStr);
3103
+ cgi_reset_content();
3104
+ style_submenu_enable(0);
3105
+ style_set_current_feature("error");
3106
+ style_header("Configuration Error");
3107
+ @ <p>Error in a TH1 configuration script:
3108
+ @ tainted %h(zWhere): "%z(z)"
3109
+ style_finish_page();
3110
+ cgi_reply();
3111
+ fossil_exit(1);
3112
+ }
3113
+ return 1;
3114
+}
29583115
29593116
/*
29603117
** COMMAND: test-th-render
29613118
**
29623119
** Usage: %fossil test-th-render FILE
@@ -2992,10 +3149,11 @@
29923149
if( find_option("set-user-caps", 0, 0)!=0 ){
29933150
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
29943151
login_set_capabilities(zCap ? zCap : "sx", 0);
29953152
g.useLocalauth = 1;
29963153
}
3154
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
29973155
verify_all_options();
29983156
if( g.argc<3 ){
29993157
usage("FILE");
30003158
}
30013159
blob_zero(&in);
@@ -3044,10 +3202,11 @@
30443202
if( find_option("set-user-caps", 0, 0)!=0 ){
30453203
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
30463204
login_set_capabilities(zCap ? zCap : "sx", 0);
30473205
g.useLocalauth = 1;
30483206
}
3207
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
30493208
verify_all_options();
30503209
if( g.argc!=3 ){
30513210
usage("script");
30523211
}
30533212
if(file_isfile(g.argv[2], ExtFILE)){
30543213
--- src/th_main.c
+++ src/th_main.c
@@ -262,11 +262,11 @@
262 ){
263 char *zOut;
264 if( argc!=2 ){
265 return Th_WrongNumArgs(interp, "httpize STRING");
266 }
267 zOut = httpize((char*)argv[1], argl[1]);
268 Th_SetResult(interp, zOut, -1);
269 free(zOut);
270 return TH_OK;
271 }
272
@@ -291,11 +291,12 @@
291 if( argc<2 || argc>3 ){
292 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293 }
294 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295 if( g.thTrace ){
296 Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
 
297 }
298 return rc;
299 }
300
301 /*
@@ -322,11 +323,11 @@
322 buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
323 Th_SetResultInt(g.interp, buul);
324 if(argc>1){
325 if( g.thTrace ){
326 Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
327 argl[1],argv[1],buul);
328 }
329 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
330 if(!rc){
331 if(buul){
332 g.th1Flags &= ~TH_INIT_NO_ENCODE;
@@ -381,19 +382,23 @@
381 ** g.th1Flags has the TH_INIT_NO_ENCODE flag.
382 **
383 ** If pOut is NULL and the global pThOut is not then that blob
384 ** is used for output.
385 */
386 static void sendText(Blob * pOut, const char *z, int n, int encode){
387 if(0==pOut && pThOut!=0){
388 pOut = pThOut;
389 }
390 if(TH_INIT_NO_ENCODE & g.th1Flags){
391 encode = 0;
392 }
393 if( enableOutput && n ){
394 if( n<0 ) n = strlen(z);
 
 
 
 
395 if( encode ){
396 z = htmlize(z, n);
397 n = strlen(z);
398 }
399 if(pOut!=0){
@@ -525,14 +530,23 @@
525 void *pConvert,
526 int argc,
527 const char **argv,
528 int *argl
529 ){
 
 
530 if( argc!=2 ){
531 return Th_WrongNumArgs(interp, "puts STRING");
532 }
533 sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
 
 
 
 
 
 
 
534 return TH_OK;
535 }
536
537 /*
538 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +571,15 @@
557 }
558 if( argc==3 ){
559 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560 return TH_ERROR;
561 }
 
 
 
 
 
562 }
563 if( withMethod ){
564 cgi_redirect_with_method(argv[1]);
565 }else{
566 cgi_redirect(argv[1]);
@@ -660,11 +679,11 @@
660 int nValue = 0;
661 if( argc!=2 ){
662 return Th_WrongNumArgs(interp, "markdown STRING");
663 }
664 blob_zero(&src);
665 blob_init(&src, (char*)argv[1], argl[1]);
666 blob_zero(&title); blob_zero(&body);
667 markdown_to_html(&src, &title, &body);
668 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +709,11 @@
690 if( argc!=2 ){
691 return Th_WrongNumArgs(interp, "wiki STRING");
692 }
693 if( enableOutput ){
694 Blob src;
695 blob_init(&src, (char*)argv[1], argl[1]);
696 wiki_convert(&src, 0, flags);
697 blob_reset(&src);
698 }
699 return TH_OK;
700 }
@@ -735,11 +754,11 @@
735 ){
736 char *zOut;
737 if( argc!=2 ){
738 return Th_WrongNumArgs(interp, "htmlize STRING");
739 }
740 zOut = htmlize((char*)argv[1], argl[1]);
741 Th_SetResult(interp, zOut, -1);
742 free(zOut);
743 return TH_OK;
744 }
745
@@ -757,11 +776,11 @@
757 ){
758 char *zOut;
759 if( argc!=2 ){
760 return Th_WrongNumArgs(interp, "encode64 STRING");
761 }
762 zOut = encode64((char*)argv[1], argl[1]);
763 Th_SetResult(interp, zOut, -1);
764 free(zOut);
765 return TH_OK;
766 }
767
@@ -778,11 +797,11 @@
778 int argc,
779 const char **argv,
780 int *argl
781 ){
782 char *zOut;
783 if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
784 zOut = db_text("??", "SELECT datetime('now',toLocal())");
785 }else{
786 zOut = db_text("??", "SELECT datetime('now')");
787 }
788 Th_SetResult(interp, zOut, -1);
@@ -810,13 +829,13 @@
810 if( argc<2 ){
811 return Th_WrongNumArgs(interp, "hascap STRING ...");
812 }
813 for(i=1; rc==1 && i<argc; i++){
814 if( g.thTrace ){
815 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
816 }
817 rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
818 }
819 if( g.thTrace ){
820 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821 Th_Free(interp, zCapList);
822 }
@@ -858,11 +877,11 @@
858 int i;
859
860 if( argc!=2 ){
861 return Th_WrongNumArgs(interp, "capexpr EXPR");
862 }
863 rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
864 if( rc ) return rc;
865 rc = 0;
866 for(i=0; i<nCap; i++){
867 if( azCap[i][0]=='!' ){
868 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +940,12 @@
921 if( argc<2 ){
922 return Th_WrongNumArgs(interp, "hascap STRING ...");
923 }
924 for(i=1; i<argc && rc; i++){
925 int match = 0;
926 for(j=0; j<argl[i]; j++){
 
927 switch( argv[i][j] ){
928 case 'c': match |= searchCap & SRCH_CKIN; break;
929 case 'd': match |= searchCap & SRCH_DOC; break;
930 case 't': match |= searchCap & SRCH_TKT; break;
931 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +952,11 @@
932 }
933 }
934 if( !match ) rc = 0;
935 }
936 if( g.thTrace ){
937 Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
938 }
939 Th_SetResultInt(interp, rc);
940 return TH_OK;
941 }
942
@@ -1051,11 +1071,11 @@
1051 #endif
1052 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1053 rc = 1;
1054 }
1055 if( g.thTrace ){
1056 Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1057 }
1058 Th_SetResultInt(interp, rc);
1059 return TH_OK;
1060 }
1061
@@ -1104,18 +1124,20 @@
1104 const char **argv,
1105 int *argl
1106 ){
1107 int rc = 0;
1108 int i;
 
1109 if( argc!=2 ){
1110 return Th_WrongNumArgs(interp, "anycap STRING");
1111 }
1112 for(i=0; rc==0 && i<argl[1]; i++){
 
1113 rc = login_has_capability((char*)&argv[1][i],1,0);
1114 }
1115 if( g.thTrace ){
1116 Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1117 }
1118 Th_SetResultInt(interp, rc);
1119 return TH_OK;
1120 }
1121
@@ -1140,22 +1162,23 @@
1140 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1141 }
1142 if( enableOutput ){
1143 int height;
1144 Blob name;
1145 int nValue;
1146 const char *zValue;
1147 char *z, *zH;
1148 int nElem;
1149 int *aszElem;
1150 char **azElem;
1151 int i;
1152
1153 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1154 Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
1155 blob_init(&name, (char*)argv[1], argl[1]);
1156 zValue = Th_Fetch(blob_str(&name), &nValue);
 
1157 zH = htmlize(blob_buffer(&name), blob_size(&name));
1158 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1159 free(zH);
1160 sendText(0,z, -1, 0);
1161 free(z);
@@ -1247,11 +1270,11 @@
1247 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1248 }
1249 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1250 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1251 z = argv[1];
1252 size = argl[1];
1253 for(n=1, i=0; i<size; i++){
1254 if( z[i]=='\n' ){
1255 n++;
1256 if( n>=iMax ) break;
1257 }
@@ -1407,11 +1430,12 @@
1407 return TH_OK;
1408 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1409 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1410 return TH_OK;
1411 }else{
1412 Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
 
1413 return TH_ERROR;
1414 }
1415 }
1416
1417 /*
@@ -1848,10 +1872,47 @@
1848 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1849 Th_SetResult(interp, zUTime, -1);
1850 return TH_OK;
1851 }
1852
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1853
1854 /*
1855 ** TH1 command: randhex N
1856 **
1857 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1984,13 @@
1923 int res = TH_OK;
1924 int nVar;
1925 char *zErr = 0;
1926 int noComplain = 0;
1927
1928 if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
 
 
1929 argc--;
1930 argv++;
1931 argl++;
1932 noComplain = 1;
1933 }
@@ -1939,15 +2002,22 @@
1939 Th_ErrorMessage(interp, "database is not open", 0, 0);
1940 return TH_ERROR;
1941 }
1942 zSql = argv[1];
1943 nSql = argl[1];
 
 
 
 
 
 
 
1944 while( res==TH_OK && nSql>0 ){
1945 zErr = 0;
1946 report_restrict_sql(&zErr);
1947 g.dbIgnoreErrors++;
1948 rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1949 g.dbIgnoreErrors--;
1950 report_unrestrict_sql();
1951 if( rc!=0 || zErr!=0 ){
1952 if( noComplain ) return TH_OK;
1953 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +2034,31 @@
1964 int szVar = zVar ? th_strlen(zVar) : 0;
1965 if( szVar>1 && zVar[0]=='$'
1966 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1967 int nVal;
1968 const char *zVal = Th_GetResult(interp, &nVal);
1969 sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1970 }
1971 }
1972 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
1973 int nCol = sqlite3_column_count(pStmt);
1974 for(i=0; i<nCol; i++){
1975 const char *zCol = sqlite3_column_name(pStmt, i);
1976 int szCol = th_strlen(zCol);
1977 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
1978 int szVal = sqlite3_column_bytes(pStmt, i);
1979 Th_SetVar(interp, zCol, szCol, zVal, szVal);
1980 }
1981 if( g.thTrace ){
1982 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
1983 }
1984 res = Th_Eval(interp, 0, argv[2], argl[2]);
1985 if( g.thTrace ){
1986 int nTrRes;
1987 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
1988 Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989 Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
1990 }
1991 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
1992 }
1993 rc = sqlite3_finalize(pStmt);
1994 if( rc!=SQLITE_OK ){
@@ -2038,11 +2108,11 @@
2038 Th_SetResult(interp, 0, 0);
2039 rc = TH_OK;
2040 }
2041 if( g.thTrace ){
2042 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043 argl[nArg], argv[nArg], rc);
2044 }
2045 return rc;
2046 }
2047
2048 /*
@@ -2121,11 +2191,11 @@
2121 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2122 }
2123 zErr = re_compile(&pRe, argv[nArg], noCase);
2124 if( !zErr ){
2125 Th_SetResultInt(interp, re_match(pRe,
2126 (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2127 rc = TH_OK;
2128 }else{
2129 Th_SetResult(interp, zErr, -1);
2130 rc = TH_ERROR;
2131 }
@@ -2160,11 +2230,11 @@
2160 UrlData urlData;
2161
2162 if( argc<2 || argc>5 ){
2163 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2164 }
2165 if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2166 fAsynchronous = 1; nArg++;
2167 }
2168 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2169 if( nArg+1!=argc && nArg+2!=argc ){
2170 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2259,11 @@
2189 return TH_ERROR;
2190 }
2191 re_free(pRe);
2192 blob_zero(&payload);
2193 if( nArg+2==argc ){
2194 blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2195 zType = "POST";
2196 }else{
2197 zType = "GET";
2198 }
2199 if( fAsynchronous ){
@@ -2268,11 +2338,11 @@
2268 if( argc!=2 ){
2269 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2270 }
2271 pOrig = Th_SetOutputBlob(&out);
2272 zStr = argv[1];
2273 nStr = argl[1];
2274 rc = Th_Eval(g.interp, 0, zStr, nStr);
2275 Th_SetOutputBlob(pOrig);
2276 if(0==rc){
2277 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2278 }
@@ -2387,13 +2457,15 @@
2387 {"setting", settingCmd, 0},
2388 {"styleFooter", styleFooterCmd, 0},
2389 {"styleHeader", styleHeaderCmd, 0},
2390 {"styleScript", styleScriptCmd, 0},
2391 {"submenu", submenuCmd, 0},
 
2392 {"tclReady", tclReadyCmd, 0},
2393 {"trace", traceCmd, 0},
2394 {"stime", stimeCmd, 0},
 
2395 {"unversioned", unversionedCmd, 0},
2396 {"utime", utimeCmd, 0},
2397 {"verifyCsrf", verifyCsrfCmd, 0},
2398 {"verifyLogin", verifyLoginCmd, 0},
2399 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2566,26 @@
2494 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2495 }
2496 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2497 }
2498 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2499
2500 /*
2501 ** Appends an element to a TH1 list value. This function is called by the
2502 ** transfer subsystem; therefore, it must be very careful to avoid doing
2503 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2768,11 @@
2680 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2681 /*
2682 ** Make sure that the TH1 script error was not caused by a "missing"
2683 ** command hook handler as that is not actually an error condition.
2684 */
 
2685 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2686 sendError(0,zResult, nResult, 0);
2687 }else{
2688 /*
2689 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2856,11 @@
2767 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2768 /*
2769 ** Make sure that the TH1 script error was not caused by a "missing"
2770 ** webpage hook handler as that is not actually an error condition.
2771 */
 
2772 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2773 sendError(0,zResult, nResult, 1);
2774 }else{
2775 /*
2776 ** There is no webpage hook handler "installed". This situation
@@ -2907,11 +2997,11 @@
2907 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2908 if( g.thTrace ){
2909 int nTrRes;
2910 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2911 Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912 Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2913 }
2914 if( rc!=TH_OK ) break;
2915 z += i;
2916 if( z[0] ){ z += 6; }
2917 i = 0;
@@ -2953,10 +3043,77 @@
2953 ** as appropriate. We need to pass on g.th1Flags for the case of
2954 ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
2955 ** inadvertently toggled off by a recursive call.
2956 */;
2957 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2958
2959 /*
2960 ** COMMAND: test-th-render
2961 **
2962 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3149,11 @@
2992 if( find_option("set-user-caps", 0, 0)!=0 ){
2993 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
2994 login_set_capabilities(zCap ? zCap : "sx", 0);
2995 g.useLocalauth = 1;
2996 }
 
2997 verify_all_options();
2998 if( g.argc<3 ){
2999 usage("FILE");
3000 }
3001 blob_zero(&in);
@@ -3044,10 +3202,11 @@
3044 if( find_option("set-user-caps", 0, 0)!=0 ){
3045 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3046 login_set_capabilities(zCap ? zCap : "sx", 0);
3047 g.useLocalauth = 1;
3048 }
 
3049 verify_all_options();
3050 if( g.argc!=3 ){
3051 usage("script");
3052 }
3053 if(file_isfile(g.argv[2], ExtFILE)){
3054
--- src/th_main.c
+++ src/th_main.c
@@ -262,11 +262,11 @@
262 ){
263 char *zOut;
264 if( argc!=2 ){
265 return Th_WrongNumArgs(interp, "httpize STRING");
266 }
267 zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
268 Th_SetResult(interp, zOut, -1);
269 free(zOut);
270 return TH_OK;
271 }
272
@@ -291,11 +291,12 @@
291 if( argc<2 || argc>3 ){
292 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293 }
294 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295 if( g.thTrace ){
296 Th_Trace("enable_output {%.*s} -> %d<br>\n",
297 TH1_LEN(argl[1]),argv[1],enableOutput);
298 }
299 return rc;
300 }
301
302 /*
@@ -322,11 +323,11 @@
323 buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
324 Th_SetResultInt(g.interp, buul);
325 if(argc>1){
326 if( g.thTrace ){
327 Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
328 TH1_LEN(argl[1]),argv[1],buul);
329 }
330 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
331 if(!rc){
332 if(buul){
333 g.th1Flags &= ~TH_INIT_NO_ENCODE;
@@ -381,19 +382,23 @@
382 ** g.th1Flags has the TH_INIT_NO_ENCODE flag.
383 **
384 ** If pOut is NULL and the global pThOut is not then that blob
385 ** is used for output.
386 */
387 static void sendText(Blob *pOut, const char *z, int n, int encode){
388 if(0==pOut && pThOut!=0){
389 pOut = pThOut;
390 }
391 if(TH_INIT_NO_ENCODE & g.th1Flags){
392 encode = 0;
393 }
394 if( enableOutput && n ){
395 if( n<0 ){
396 n = strlen(z);
397 }else{
398 n = TH1_LEN(n);
399 }
400 if( encode ){
401 z = htmlize(z, n);
402 n = strlen(z);
403 }
404 if(pOut!=0){
@@ -525,14 +530,23 @@
530 void *pConvert,
531 int argc,
532 const char **argv,
533 int *argl
534 ){
535 int encode = *(unsigned int*)pConvert;
536 int n;
537 if( argc!=2 ){
538 return Th_WrongNumArgs(interp, "puts STRING");
539 }
540 n = argl[1];
541 if( encode==0 && n>0 && TH1_TAINTED(n) ){
542 if( Th_ReportTaint(interp, "output string", argv[1], n) ){
543 return TH_ERROR;
544 }
545 n = TH1_LEN(n);
546 }
547 sendText(0,(char*)argv[1], n, encode);
548 return TH_OK;
549 }
550
551 /*
552 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +571,15 @@
571 }
572 if( argc==3 ){
573 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
574 return TH_ERROR;
575 }
576 }
577 if( TH1_TAINTED(argl[1])
578 && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
579 ){
580 return TH_ERROR;
581 }
582 if( withMethod ){
583 cgi_redirect_with_method(argv[1]);
584 }else{
585 cgi_redirect(argv[1]);
@@ -660,11 +679,11 @@
679 int nValue = 0;
680 if( argc!=2 ){
681 return Th_WrongNumArgs(interp, "markdown STRING");
682 }
683 blob_zero(&src);
684 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
685 blob_zero(&title); blob_zero(&body);
686 markdown_to_html(&src, &title, &body);
687 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
688 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
689 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +709,11 @@
709 if( argc!=2 ){
710 return Th_WrongNumArgs(interp, "wiki STRING");
711 }
712 if( enableOutput ){
713 Blob src;
714 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
715 wiki_convert(&src, 0, flags);
716 blob_reset(&src);
717 }
718 return TH_OK;
719 }
@@ -735,11 +754,11 @@
754 ){
755 char *zOut;
756 if( argc!=2 ){
757 return Th_WrongNumArgs(interp, "htmlize STRING");
758 }
759 zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
760 Th_SetResult(interp, zOut, -1);
761 free(zOut);
762 return TH_OK;
763 }
764
@@ -757,11 +776,11 @@
776 ){
777 char *zOut;
778 if( argc!=2 ){
779 return Th_WrongNumArgs(interp, "encode64 STRING");
780 }
781 zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
782 Th_SetResult(interp, zOut, -1);
783 free(zOut);
784 return TH_OK;
785 }
786
@@ -778,11 +797,11 @@
797 int argc,
798 const char **argv,
799 int *argl
800 ){
801 char *zOut;
802 if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
803 zOut = db_text("??", "SELECT datetime('now',toLocal())");
804 }else{
805 zOut = db_text("??", "SELECT datetime('now')");
806 }
807 Th_SetResult(interp, zOut, -1);
@@ -810,13 +829,13 @@
829 if( argc<2 ){
830 return Th_WrongNumArgs(interp, "hascap STRING ...");
831 }
832 for(i=1; rc==1 && i<argc; i++){
833 if( g.thTrace ){
834 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
835 }
836 rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
837 }
838 if( g.thTrace ){
839 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
840 Th_Free(interp, zCapList);
841 }
@@ -858,11 +877,11 @@
877 int i;
878
879 if( argc!=2 ){
880 return Th_WrongNumArgs(interp, "capexpr EXPR");
881 }
882 rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
883 if( rc ) return rc;
884 rc = 0;
885 for(i=0; i<nCap; i++){
886 if( azCap[i][0]=='!' ){
887 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +940,12 @@
940 if( argc<2 ){
941 return Th_WrongNumArgs(interp, "hascap STRING ...");
942 }
943 for(i=1; i<argc && rc; i++){
944 int match = 0;
945 int nn = TH1_LEN(argl[i]);
946 for(j=0; j<nn; j++){
947 switch( argv[i][j] ){
948 case 'c': match |= searchCap & SRCH_CKIN; break;
949 case 'd': match |= searchCap & SRCH_DOC; break;
950 case 't': match |= searchCap & SRCH_TKT; break;
951 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +952,11 @@
952 }
953 }
954 if( !match ) rc = 0;
955 }
956 if( g.thTrace ){
957 Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
958 }
959 Th_SetResultInt(interp, rc);
960 return TH_OK;
961 }
962
@@ -1051,11 +1071,11 @@
1071 #endif
1072 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1073 rc = 1;
1074 }
1075 if( g.thTrace ){
1076 Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
1077 }
1078 Th_SetResultInt(interp, rc);
1079 return TH_OK;
1080 }
1081
@@ -1104,18 +1124,20 @@
1124 const char **argv,
1125 int *argl
1126 ){
1127 int rc = 0;
1128 int i;
1129 int nn;
1130 if( argc!=2 ){
1131 return Th_WrongNumArgs(interp, "anycap STRING");
1132 }
1133 nn = TH1_LEN(argl[1]);
1134 for(i=0; rc==0 && i<nn; i++){
1135 rc = login_has_capability((char*)&argv[1][i],1,0);
1136 }
1137 if( g.thTrace ){
1138 Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
1139 }
1140 Th_SetResultInt(interp, rc);
1141 return TH_OK;
1142 }
1143
@@ -1140,22 +1162,23 @@
1162 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1163 }
1164 if( enableOutput ){
1165 int height;
1166 Blob name;
1167 int nValue = 0;
1168 const char *zValue;
1169 char *z, *zH;
1170 int nElem;
1171 int *aszElem;
1172 char **azElem;
1173 int i;
1174
1175 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1176 Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1177 blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
1178 zValue = Th_Fetch(blob_str(&name), &nValue);
1179 nValue = TH1_LEN(nValue);
1180 zH = htmlize(blob_buffer(&name), blob_size(&name));
1181 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1182 free(zH);
1183 sendText(0,z, -1, 0);
1184 free(z);
@@ -1247,11 +1270,11 @@
1270 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1271 }
1272 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1273 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1274 z = argv[1];
1275 size = TH1_LEN(argl[1]);
1276 for(n=1, i=0; i<size; i++){
1277 if( z[i]=='\n' ){
1278 n++;
1279 if( n>=iMax ) break;
1280 }
@@ -1407,11 +1430,12 @@
1430 return TH_OK;
1431 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1432 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1433 return TH_OK;
1434 }else{
1435 Th_ErrorMessage(interp, "unsupported global state:",
1436 argv[1], TH1_LEN(argl[1]));
1437 return TH_ERROR;
1438 }
1439 }
1440
1441 /*
@@ -1848,10 +1872,47 @@
1872 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1873 Th_SetResult(interp, zUTime, -1);
1874 return TH_OK;
1875 }
1876
1877 /*
1878 ** TH1 command: taint STRING
1879 **
1880 ** Return a copy of STRING that is marked as tainted.
1881 */
1882 static int taintCmd(
1883 Th_Interp *interp,
1884 void *p,
1885 int argc,
1886 const char **argv,
1887 int *argl
1888 ){
1889 if( argc!=2 ){
1890 return Th_WrongNumArgs(interp, "STRING");
1891 }
1892 Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1893 return TH_OK;
1894 }
1895
1896 /*
1897 ** TH1 command: untaint STRING
1898 **
1899 ** Return a copy of STRING that is marked as untainted.
1900 */
1901 static int untaintCmd(
1902 Th_Interp *interp,
1903 void *p,
1904 int argc,
1905 const char **argv,
1906 int *argl
1907 ){
1908 if( argc!=2 ){
1909 return Th_WrongNumArgs(interp, "STRING");
1910 }
1911 Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1912 return TH_OK;
1913 }
1914
1915 /*
1916 ** TH1 command: randhex N
1917 **
1918 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1984,13 @@
1984 int res = TH_OK;
1985 int nVar;
1986 char *zErr = 0;
1987 int noComplain = 0;
1988
1989 if( argc>3 && TH1_LEN(argl[1])==11
1990 && strncmp(argv[1], "-nocomplain", 11)==0
1991 ){
1992 argc--;
1993 argv++;
1994 argl++;
1995 noComplain = 1;
1996 }
@@ -1939,15 +2002,22 @@
2002 Th_ErrorMessage(interp, "database is not open", 0, 0);
2003 return TH_ERROR;
2004 }
2005 zSql = argv[1];
2006 nSql = argl[1];
2007 if( TH1_TAINTED(nSql) ){
2008 if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
2009 return TH_ERROR;
2010 }
2011 nSql = TH1_LEN(nSql);
2012 }
2013
2014 while( res==TH_OK && nSql>0 ){
2015 zErr = 0;
2016 report_restrict_sql(&zErr);
2017 g.dbIgnoreErrors++;
2018 rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
2019 g.dbIgnoreErrors--;
2020 report_unrestrict_sql();
2021 if( rc!=0 || zErr!=0 ){
2022 if( noComplain ) return TH_OK;
2023 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +2034,31 @@
2034 int szVar = zVar ? th_strlen(zVar) : 0;
2035 if( szVar>1 && zVar[0]=='$'
2036 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
2037 int nVal;
2038 const char *zVal = Th_GetResult(interp, &nVal);
2039 sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
2040 }
2041 }
2042 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
2043 int nCol = sqlite3_column_count(pStmt);
2044 for(i=0; i<nCol; i++){
2045 const char *zCol = sqlite3_column_name(pStmt, i);
2046 int szCol = th_strlen(zCol);
2047 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
2048 int szVal = sqlite3_column_bytes(pStmt, i);
2049 Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
2050 }
2051 if( g.thTrace ){
2052 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
2053 }
2054 res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
2055 if( g.thTrace ){
2056 int nTrRes;
2057 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2058 Th_Trace("[query_eval] => %h {%#h}<br>\n",
2059 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
2060 }
2061 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
2062 }
2063 rc = sqlite3_finalize(pStmt);
2064 if( rc!=SQLITE_OK ){
@@ -2038,11 +2108,11 @@
2108 Th_SetResult(interp, 0, 0);
2109 rc = TH_OK;
2110 }
2111 if( g.thTrace ){
2112 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2113 TH1_LEN(argl[nArg]), argv[nArg], rc);
2114 }
2115 return rc;
2116 }
2117
2118 /*
@@ -2121,11 +2191,11 @@
2191 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2192 }
2193 zErr = re_compile(&pRe, argv[nArg], noCase);
2194 if( !zErr ){
2195 Th_SetResultInt(interp, re_match(pRe,
2196 (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
2197 rc = TH_OK;
2198 }else{
2199 Th_SetResult(interp, zErr, -1);
2200 rc = TH_ERROR;
2201 }
@@ -2160,11 +2230,11 @@
2230 UrlData urlData;
2231
2232 if( argc<2 || argc>5 ){
2233 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2234 }
2235 if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
2236 fAsynchronous = 1; nArg++;
2237 }
2238 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2239 if( nArg+1!=argc && nArg+2!=argc ){
2240 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2259,11 @@
2259 return TH_ERROR;
2260 }
2261 re_free(pRe);
2262 blob_zero(&payload);
2263 if( nArg+2==argc ){
2264 blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
2265 zType = "POST";
2266 }else{
2267 zType = "GET";
2268 }
2269 if( fAsynchronous ){
@@ -2268,11 +2338,11 @@
2338 if( argc!=2 ){
2339 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2340 }
2341 pOrig = Th_SetOutputBlob(&out);
2342 zStr = argv[1];
2343 nStr = TH1_LEN(argl[1]);
2344 rc = Th_Eval(g.interp, 0, zStr, nStr);
2345 Th_SetOutputBlob(pOrig);
2346 if(0==rc){
2347 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2348 }
@@ -2387,13 +2457,15 @@
2457 {"setting", settingCmd, 0},
2458 {"styleFooter", styleFooterCmd, 0},
2459 {"styleHeader", styleHeaderCmd, 0},
2460 {"styleScript", styleScriptCmd, 0},
2461 {"submenu", submenuCmd, 0},
2462 {"taint", taintCmd, 0},
2463 {"tclReady", tclReadyCmd, 0},
2464 {"trace", traceCmd, 0},
2465 {"stime", stimeCmd, 0},
2466 {"untaint", untaintCmd, 0},
2467 {"unversioned", unversionedCmd, 0},
2468 {"utime", utimeCmd, 0},
2469 {"verifyCsrf", verifyCsrfCmd, 0},
2470 {"verifyLogin", verifyLoginCmd, 0},
2471 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2566,26 @@
2566 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2567 }
2568 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2569 }
2570 }
2571
2572 /*
2573 ** Store a string value in a variable in the interpreter
2574 ** with the "taint" marking, so that TH1 knows that this
2575 ** variable contains content under the control of the remote
2576 ** user and presents a risk of XSS or SQL-injection attacks.
2577 */
2578 void Th_StoreUnsafe(const char *zName, const char *zValue){
2579 Th_FossilInit(TH_INIT_DEFAULT);
2580 if( zValue ){
2581 if( g.thTrace ){
2582 Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2583 }
2584 Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2585 }
2586 }
2587
2588 /*
2589 ** Appends an element to a TH1 list value. This function is called by the
2590 ** transfer subsystem; therefore, it must be very careful to avoid doing
2591 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2768,11 @@
2768 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2769 /*
2770 ** Make sure that the TH1 script error was not caused by a "missing"
2771 ** command hook handler as that is not actually an error condition.
2772 */
2773 nResult = TH1_LEN(nResult);
2774 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2775 sendError(0,zResult, nResult, 0);
2776 }else{
2777 /*
2778 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2856,11 @@
2856 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2857 /*
2858 ** Make sure that the TH1 script error was not caused by a "missing"
2859 ** webpage hook handler as that is not actually an error condition.
2860 */
2861 nResult = TH1_LEN(nResult);
2862 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2863 sendError(0,zResult, nResult, 1);
2864 }else{
2865 /*
2866 ** There is no webpage hook handler "installed". This situation
@@ -2907,11 +2997,11 @@
2997 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2998 if( g.thTrace ){
2999 int nTrRes;
3000 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
3001 Th_Trace("[render_eval] => %h {%#h}<br>\n",
3002 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
3003 }
3004 if( rc!=TH_OK ) break;
3005 z += i;
3006 if( z[0] ){ z += 6; }
3007 i = 0;
@@ -2953,10 +3043,77 @@
3043 ** as appropriate. We need to pass on g.th1Flags for the case of
3044 ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
3045 ** inadvertently toggled off by a recursive call.
3046 */;
3047 }
3048
3049 /*
3050 ** SETTING: vuln-report width=8 default=log
3051 **
3052 ** This setting controls Fossil's behavior when it encounters a potential
3053 ** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3054 ** scripts. Choices are:
3055 **
3056 ** off Do nothing. Ignore the vulnerability.
3057 **
3058 ** log Write a report of the problem into the error log.
3059 **
3060 ** block Like "log" but also prevent the offending TH1 command
3061 ** from running.
3062 **
3063 ** fatal Render an error message page instead of the requested
3064 ** page.
3065 */
3066
3067 /*
3068 ** Report misuse of a tainted string in TH1.
3069 **
3070 ** The behavior depends on the vuln-report setting. If "off", this routine
3071 ** is a no-op. Otherwise, right a message into the error log. If
3072 ** vuln-report is "log", that is all that happens. But for any other
3073 ** value of vuln-report, a fatal error is raised.
3074 */
3075 int Th_ReportTaint(
3076 Th_Interp *interp, /* Report error here, if an error is reported */
3077 const char *zWhere, /* Where the tainted string appears */
3078 const char *zStr, /* The tainted string */
3079 int nStr /* Length of the tainted string */
3080 ){
3081 char *zDisp; /* Dispensation */
3082 const char *zVulnType; /* Type of vulnerability */
3083
3084 zDisp = db_get("vuln-report","log");
3085 if( is_false(zDisp) ) return 0;
3086 if( strstr(zWhere,"SQL")!=0 ){
3087 zVulnType = "SQL-injection";
3088 }else{
3089 zVulnType = "XSS";
3090 }
3091 nStr = TH1_LEN(nStr);
3092 fossil_errorlog("possible %s vulnerability due to tainted TH1 %s: \"%.*s\"",
3093 zVulnType, zWhere, nStr, zStr);
3094 if( strcmp(zDisp,"log")==0 ){
3095 return 0;
3096 }
3097 if( strcmp(zDisp,"block")==0 ){
3098 char *z = mprintf("tainted %s: \"", zWhere);
3099 Th_ErrorMessage(interp, z, zStr, nStr);
3100 fossil_free(z);
3101 }else{
3102 char *z = mprintf("%#h", nStr, zStr);
3103 cgi_reset_content();
3104 style_submenu_enable(0);
3105 style_set_current_feature("error");
3106 style_header("Configuration Error");
3107 @ <p>Error in a TH1 configuration script:
3108 @ tainted %h(zWhere): "%z(z)"
3109 style_finish_page();
3110 cgi_reply();
3111 fossil_exit(1);
3112 }
3113 return 1;
3114 }
3115
3116 /*
3117 ** COMMAND: test-th-render
3118 **
3119 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3149,11 @@
3149 if( find_option("set-user-caps", 0, 0)!=0 ){
3150 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3151 login_set_capabilities(zCap ? zCap : "sx", 0);
3152 g.useLocalauth = 1;
3153 }
3154 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3155 verify_all_options();
3156 if( g.argc<3 ){
3157 usage("FILE");
3158 }
3159 blob_zero(&in);
@@ -3044,10 +3202,11 @@
3202 if( find_option("set-user-caps", 0, 0)!=0 ){
3203 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3204 login_set_capabilities(zCap ? zCap : "sx", 0);
3205 g.useLocalauth = 1;
3206 }
3207 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3208 verify_all_options();
3209 if( g.argc!=3 ){
3210 usage("script");
3211 }
3212 if(file_isfile(g.argv[2], ExtFILE)){
3213
+18 -18
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -41,16 +41,16 @@
4141
#define USE_ARGV_TO_OBJV() \
4242
int objc; \
4343
Tcl_Obj **objv; \
4444
int obji;
4545
46
-#define COPY_ARGV_TO_OBJV() \
47
- objc = argc-1; \
48
- objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49
- for(obji=1; obji<argc; obji++){ \
50
- objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \
51
- Tcl_IncrRefCount(objv[obji-1]); \
46
+#define COPY_ARGV_TO_OBJV() \
47
+ objc = argc-1; \
48
+ objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49
+ for(obji=1; obji<argc; obji++){ \
50
+ objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
51
+ Tcl_IncrRefCount(objv[obji-1]); \
5252
}
5353
5454
#define FREE_ARGV_TO_OBJV() \
5555
for(obji=1; obji<argc; obji++){ \
5656
Tcl_DecrRefCount(objv[obji-1]); \
@@ -449,11 +449,11 @@
449449
}
450450
xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
451451
if( xNotifyProc ){
452452
rc = xNotifyProc(bIsPost ?
453453
tclContext->pPostContext : tclContext->pPreContext,
454
- interp, ctx, argc, argv, argl, rc);
454
+ interp, ctx, argc, argv, TH1_LEN(argl), rc);
455455
}
456456
return rc;
457457
}
458458
459459
/*
@@ -485,17 +485,17 @@
485485
tclInterp = GET_CTX_TCL_INTERP(ctx);
486486
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487487
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488488
return TH_ERROR;
489489
}
490
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
490
+ rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
491491
if( rc!=TH_OK ){
492492
return rc;
493493
}
494494
Tcl_Preserve((ClientData)tclInterp);
495495
if( argc==2 ){
496
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
496
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
497497
Tcl_IncrRefCount(objPtr);
498498
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499499
Tcl_DecrRefCount(objPtr); objPtr = 0;
500500
}else{
501501
USE_ARGV_TO_OBJV();
@@ -507,11 +507,11 @@
507507
FREE_ARGV_TO_OBJV();
508508
}
509509
zResult = getTclResult(tclInterp, &nResult);
510510
Th_SetResult(interp, zResult, nResult);
511511
Tcl_Release((ClientData)tclInterp);
512
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
512
+ rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
513513
getTh1ReturnCode(rc));
514514
return rc;
515515
}
516516
517517
/*
@@ -545,17 +545,17 @@
545545
tclInterp = GET_CTX_TCL_INTERP(ctx);
546546
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547547
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548548
return TH_ERROR;
549549
}
550
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
550
+ rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
551551
if( rc!=TH_OK ){
552552
return rc;
553553
}
554554
Tcl_Preserve((ClientData)tclInterp);
555555
if( argc==2 ){
556
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
556
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
557557
Tcl_IncrRefCount(objPtr);
558558
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559559
Tcl_DecrRefCount(objPtr); objPtr = 0;
560560
}else{
561561
USE_ARGV_TO_OBJV();
@@ -574,11 +574,11 @@
574574
Th_SetResult(interp, zResult, nResult);
575575
if( rc==TCL_OK ){
576576
Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577577
}
578578
Tcl_Release((ClientData)tclInterp);
579
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
579
+ rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
580580
getTh1ReturnCode(rc));
581581
return rc;
582582
}
583583
584584
/*
@@ -610,20 +610,20 @@
610610
tclInterp = GET_CTX_TCL_INTERP(ctx);
611611
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612612
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613613
return TH_ERROR;
614614
}
615
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
615
+ rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
616616
if( rc!=TH_OK ){
617617
return rc;
618618
}
619619
Tcl_Preserve((ClientData)tclInterp);
620620
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621621
if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622622
Tcl_Command command;
623623
Tcl_CmdInfo cmdInfo;
624
- Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
624
+ Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
625625
Tcl_IncrRefCount(objPtr);
626626
command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627627
if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628628
Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629629
Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,11 +649,11 @@
649649
FREE_ARGV_TO_OBJV();
650650
}
651651
zResult = getTclResult(tclInterp, &nResult);
652652
Th_SetResult(interp, zResult, nResult);
653653
Tcl_Release((ClientData)tclInterp);
654
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
654
+ rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
655655
getTh1ReturnCode(rc));
656656
return rc;
657657
}
658658
659659
/*
@@ -782,11 +782,11 @@
782782
return TCL_ERROR;
783783
}
784784
arg = Tcl_GetStringFromObj(objv[1], &nArg);
785785
rc = Th_Eval(th1Interp, 0, arg, nArg);
786786
arg = Th_GetResult(th1Interp, &nArg);
787
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
787
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
788788
return getTclReturnCode(rc);
789789
}
790790
791791
/*
792792
** Tcl command: th1Expr arg
@@ -815,11 +815,11 @@
815815
return TCL_ERROR;
816816
}
817817
arg = Tcl_GetStringFromObj(objv[1], &nArg);
818818
rc = Th_Expr(th1Interp, arg, nArg);
819819
arg = Th_GetResult(th1Interp, &nArg);
820
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
820
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
821821
return getTclReturnCode(rc);
822822
}
823823
824824
/*
825825
** Array of Tcl integration commands. Used when adding or removing the Tcl
826826
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -41,16 +41,16 @@
41 #define USE_ARGV_TO_OBJV() \
42 int objc; \
43 Tcl_Obj **objv; \
44 int obji;
45
46 #define COPY_ARGV_TO_OBJV() \
47 objc = argc-1; \
48 objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49 for(obji=1; obji<argc; obji++){ \
50 objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \
51 Tcl_IncrRefCount(objv[obji-1]); \
52 }
53
54 #define FREE_ARGV_TO_OBJV() \
55 for(obji=1; obji<argc; obji++){ \
56 Tcl_DecrRefCount(objv[obji-1]); \
@@ -449,11 +449,11 @@
449 }
450 xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
451 if( xNotifyProc ){
452 rc = xNotifyProc(bIsPost ?
453 tclContext->pPostContext : tclContext->pPreContext,
454 interp, ctx, argc, argv, argl, rc);
455 }
456 return rc;
457 }
458
459 /*
@@ -485,17 +485,17 @@
485 tclInterp = GET_CTX_TCL_INTERP(ctx);
486 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488 return TH_ERROR;
489 }
490 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
491 if( rc!=TH_OK ){
492 return rc;
493 }
494 Tcl_Preserve((ClientData)tclInterp);
495 if( argc==2 ){
496 objPtr = Tcl_NewStringObj(argv[1], argl[1]);
497 Tcl_IncrRefCount(objPtr);
498 rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499 Tcl_DecrRefCount(objPtr); objPtr = 0;
500 }else{
501 USE_ARGV_TO_OBJV();
@@ -507,11 +507,11 @@
507 FREE_ARGV_TO_OBJV();
508 }
509 zResult = getTclResult(tclInterp, &nResult);
510 Th_SetResult(interp, zResult, nResult);
511 Tcl_Release((ClientData)tclInterp);
512 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
513 getTh1ReturnCode(rc));
514 return rc;
515 }
516
517 /*
@@ -545,17 +545,17 @@
545 tclInterp = GET_CTX_TCL_INTERP(ctx);
546 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548 return TH_ERROR;
549 }
550 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
551 if( rc!=TH_OK ){
552 return rc;
553 }
554 Tcl_Preserve((ClientData)tclInterp);
555 if( argc==2 ){
556 objPtr = Tcl_NewStringObj(argv[1], argl[1]);
557 Tcl_IncrRefCount(objPtr);
558 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559 Tcl_DecrRefCount(objPtr); objPtr = 0;
560 }else{
561 USE_ARGV_TO_OBJV();
@@ -574,11 +574,11 @@
574 Th_SetResult(interp, zResult, nResult);
575 if( rc==TCL_OK ){
576 Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577 }
578 Tcl_Release((ClientData)tclInterp);
579 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
580 getTh1ReturnCode(rc));
581 return rc;
582 }
583
584 /*
@@ -610,20 +610,20 @@
610 tclInterp = GET_CTX_TCL_INTERP(ctx);
611 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613 return TH_ERROR;
614 }
615 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
616 if( rc!=TH_OK ){
617 return rc;
618 }
619 Tcl_Preserve((ClientData)tclInterp);
620 #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621 if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622 Tcl_Command command;
623 Tcl_CmdInfo cmdInfo;
624 Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
625 Tcl_IncrRefCount(objPtr);
626 command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627 if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628 Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629 Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,11 +649,11 @@
649 FREE_ARGV_TO_OBJV();
650 }
651 zResult = getTclResult(tclInterp, &nResult);
652 Th_SetResult(interp, zResult, nResult);
653 Tcl_Release((ClientData)tclInterp);
654 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
655 getTh1ReturnCode(rc));
656 return rc;
657 }
658
659 /*
@@ -782,11 +782,11 @@
782 return TCL_ERROR;
783 }
784 arg = Tcl_GetStringFromObj(objv[1], &nArg);
785 rc = Th_Eval(th1Interp, 0, arg, nArg);
786 arg = Th_GetResult(th1Interp, &nArg);
787 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
788 return getTclReturnCode(rc);
789 }
790
791 /*
792 ** Tcl command: th1Expr arg
@@ -815,11 +815,11 @@
815 return TCL_ERROR;
816 }
817 arg = Tcl_GetStringFromObj(objv[1], &nArg);
818 rc = Th_Expr(th1Interp, arg, nArg);
819 arg = Th_GetResult(th1Interp, &nArg);
820 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
821 return getTclReturnCode(rc);
822 }
823
824 /*
825 ** Array of Tcl integration commands. Used when adding or removing the Tcl
826
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -41,16 +41,16 @@
41 #define USE_ARGV_TO_OBJV() \
42 int objc; \
43 Tcl_Obj **objv; \
44 int obji;
45
46 #define COPY_ARGV_TO_OBJV() \
47 objc = argc-1; \
48 objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49 for(obji=1; obji<argc; obji++){ \
50 objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
51 Tcl_IncrRefCount(objv[obji-1]); \
52 }
53
54 #define FREE_ARGV_TO_OBJV() \
55 for(obji=1; obji<argc; obji++){ \
56 Tcl_DecrRefCount(objv[obji-1]); \
@@ -449,11 +449,11 @@
449 }
450 xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
451 if( xNotifyProc ){
452 rc = xNotifyProc(bIsPost ?
453 tclContext->pPostContext : tclContext->pPreContext,
454 interp, ctx, argc, argv, TH1_LEN(argl), rc);
455 }
456 return rc;
457 }
458
459 /*
@@ -485,17 +485,17 @@
485 tclInterp = GET_CTX_TCL_INTERP(ctx);
486 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488 return TH_ERROR;
489 }
490 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
491 if( rc!=TH_OK ){
492 return rc;
493 }
494 Tcl_Preserve((ClientData)tclInterp);
495 if( argc==2 ){
496 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
497 Tcl_IncrRefCount(objPtr);
498 rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499 Tcl_DecrRefCount(objPtr); objPtr = 0;
500 }else{
501 USE_ARGV_TO_OBJV();
@@ -507,11 +507,11 @@
507 FREE_ARGV_TO_OBJV();
508 }
509 zResult = getTclResult(tclInterp, &nResult);
510 Th_SetResult(interp, zResult, nResult);
511 Tcl_Release((ClientData)tclInterp);
512 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
513 getTh1ReturnCode(rc));
514 return rc;
515 }
516
517 /*
@@ -545,17 +545,17 @@
545 tclInterp = GET_CTX_TCL_INTERP(ctx);
546 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548 return TH_ERROR;
549 }
550 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
551 if( rc!=TH_OK ){
552 return rc;
553 }
554 Tcl_Preserve((ClientData)tclInterp);
555 if( argc==2 ){
556 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
557 Tcl_IncrRefCount(objPtr);
558 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559 Tcl_DecrRefCount(objPtr); objPtr = 0;
560 }else{
561 USE_ARGV_TO_OBJV();
@@ -574,11 +574,11 @@
574 Th_SetResult(interp, zResult, nResult);
575 if( rc==TCL_OK ){
576 Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577 }
578 Tcl_Release((ClientData)tclInterp);
579 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
580 getTh1ReturnCode(rc));
581 return rc;
582 }
583
584 /*
@@ -610,20 +610,20 @@
610 tclInterp = GET_CTX_TCL_INTERP(ctx);
611 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613 return TH_ERROR;
614 }
615 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc);
616 if( rc!=TH_OK ){
617 return rc;
618 }
619 Tcl_Preserve((ClientData)tclInterp);
620 #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621 if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622 Tcl_Command command;
623 Tcl_CmdInfo cmdInfo;
624 Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
625 Tcl_IncrRefCount(objPtr);
626 command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627 if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628 Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629 Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,11 +649,11 @@
649 FREE_ARGV_TO_OBJV();
650 }
651 zResult = getTclResult(tclInterp, &nResult);
652 Th_SetResult(interp, zResult, nResult);
653 Tcl_Release((ClientData)tclInterp);
654 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl),
655 getTh1ReturnCode(rc));
656 return rc;
657 }
658
659 /*
@@ -782,11 +782,11 @@
782 return TCL_ERROR;
783 }
784 arg = Tcl_GetStringFromObj(objv[1], &nArg);
785 rc = Th_Eval(th1Interp, 0, arg, nArg);
786 arg = Th_GetResult(th1Interp, &nArg);
787 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
788 return getTclReturnCode(rc);
789 }
790
791 /*
792 ** Tcl command: th1Expr arg
@@ -815,11 +815,11 @@
815 return TCL_ERROR;
816 }
817 arg = Tcl_GetStringFromObj(objv[1], &nArg);
818 rc = Th_Expr(th1Interp, arg, nArg);
819 arg = Th_GetResult(th1Interp, &nArg);
820 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
821 return getTclReturnCode(rc);
822 }
823
824 /*
825 ** Array of Tcl integration commands. Used when adding or removing the Tcl
826
+1 -1
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
18881888
if( zTagName ){
18891889
zType = "ci";
18901890
if( matchStyle==MS_EXACT ){
18911891
/* For exact maching, inhibit links to the selected tag. */
18921892
zThisTag = zTagName;
1893
- Th_Store("current_checkin", zTagName);
1893
+ Th_StoreUnsafe("current_checkin", zTagName);
18941894
}
18951895
18961896
/* Display a checkbox to enable/disable display of related check-ins. */
18971897
if( advancedMenu ){
18981898
style_submenu_checkbox("rel", "Related", 0, 0);
18991899
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
1888 if( zTagName ){
1889 zType = "ci";
1890 if( matchStyle==MS_EXACT ){
1891 /* For exact maching, inhibit links to the selected tag. */
1892 zThisTag = zTagName;
1893 Th_Store("current_checkin", zTagName);
1894 }
1895
1896 /* Display a checkbox to enable/disable display of related check-ins. */
1897 if( advancedMenu ){
1898 style_submenu_checkbox("rel", "Related", 0, 0);
1899
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
1888 if( zTagName ){
1889 zType = "ci";
1890 if( matchStyle==MS_EXACT ){
1891 /* For exact maching, inhibit links to the selected tag. */
1892 zThisTag = zTagName;
1893 Th_StoreUnsafe("current_checkin", zTagName);
1894 }
1895
1896 /* Display a checkbox to enable/disable display of related check-ins. */
1897 if( advancedMenu ){
1898 style_submenu_checkbox("rel", "Related", 0, 0);
1899
+10 -9
--- src/tkt.c
+++ src/tkt.c
@@ -210,21 +210,21 @@
210210
zVal = zRevealed = db_reveal(zVal);
211211
}
212212
if( (j = fieldId(zName))>=0 ){
213213
aField[j].zValue = mprintf("%s", zVal);
214214
}else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
215
- Th_Store(zName, zVal);
215
+ Th_StoreUnsafe(zName, zVal);
216216
}
217217
free(zRevealed);
218218
}
219219
Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
220220
Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
221221
}
222222
db_finalize(&q);
223223
for(i=0; i<nField; i++){
224224
if( Th_Fetch(aField[i].zName, &size)==0 ){
225
- Th_Store(aField[i].zName, aField[i].zValue);
225
+ Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
226226
}
227227
}
228228
}
229229
230230
/*
@@ -233,11 +233,11 @@
233233
static void initializeVariablesFromCGI(void){
234234
int i;
235235
const char *z;
236236
237237
for(i=0; (z = cgi_parameter_name(i))!=0; i++){
238
- Th_Store(z, P(z));
238
+ Th_StoreUnsafe(z, P(z));
239239
}
240240
}
241241
242242
/*
243243
** Information about a single J-card
@@ -818,15 +818,15 @@
818818
if( argc!=3 ){
819819
return Th_WrongNumArgs(interp, "append_field FIELD STRING");
820820
}
821821
if( g.thTrace ){
822822
Th_Trace("append_field %#h {%#h}<br>\n",
823
- argl[1], argv[1], argl[2], argv[2]);
823
+ TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
824824
}
825825
for(idx=0; idx<nField; idx++){
826
- if( memcmp(aField[idx].zName, argv[1], argl[1])==0
827
- && aField[idx].zName[argl[1]]==0 ){
826
+ if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
827
+ && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
828828
break;
829829
}
830830
}
831831
if( idx>=nField ){
832832
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -938,10 +938,11 @@
938938
const char *zValue;
939939
int nValue;
940940
if( aField[i].zAppend ) continue;
941941
zValue = Th_Fetch(aField[i].zName, &nValue);
942942
if( zValue ){
943
+ nValue = TH1_LEN(nValue);
943944
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
944945
if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
945946
|| memcmp(zValue, aField[i].zValue, nValue)!=0
946947
||(int)strlen(aField[i].zValue)!=nValue
947948
){
@@ -1040,16 +1041,16 @@
10401041
if( uid ){
10411042
char * zEmail =
10421043
db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
10431044
uid);
10441045
if( zEmail ){
1045
- Th_Store("private_contact", zEmail);
1046
+ Th_StoreUnsafe("private_contact", zEmail);
10461047
fossil_free(zEmail);
10471048
}
10481049
}
10491050
}
1050
- Th_Store("login", login_name());
1051
+ Th_StoreUnsafe("login", login_name());
10511052
Th_Store("date", db_text(0, "SELECT datetime('now')"));
10521053
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
10531054
(void*)&zNewUuid, 0);
10541055
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
10551056
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1120,11 +1121,11 @@
11201121
initializeVariablesFromDb();
11211122
if( g.zPath[0]=='d' ) showAllFields();
11221123
form_begin(0, "%R/%s", g.zPath);
11231124
@ <input type="hidden" name="name" value="%s(zName)">
11241125
zScript = ticket_editpage_code();
1125
- Th_Store("login", login_name());
1126
+ Th_StoreUnsafe("login", login_name());
11261127
Th_Store("date", db_text(0, "SELECT datetime('now')"));
11271128
Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
11281129
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
11291130
if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
11301131
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
11311132
--- src/tkt.c
+++ src/tkt.c
@@ -210,21 +210,21 @@
210 zVal = zRevealed = db_reveal(zVal);
211 }
212 if( (j = fieldId(zName))>=0 ){
213 aField[j].zValue = mprintf("%s", zVal);
214 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
215 Th_Store(zName, zVal);
216 }
217 free(zRevealed);
218 }
219 Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
220 Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
221 }
222 db_finalize(&q);
223 for(i=0; i<nField; i++){
224 if( Th_Fetch(aField[i].zName, &size)==0 ){
225 Th_Store(aField[i].zName, aField[i].zValue);
226 }
227 }
228 }
229
230 /*
@@ -233,11 +233,11 @@
233 static void initializeVariablesFromCGI(void){
234 int i;
235 const char *z;
236
237 for(i=0; (z = cgi_parameter_name(i))!=0; i++){
238 Th_Store(z, P(z));
239 }
240 }
241
242 /*
243 ** Information about a single J-card
@@ -818,15 +818,15 @@
818 if( argc!=3 ){
819 return Th_WrongNumArgs(interp, "append_field FIELD STRING");
820 }
821 if( g.thTrace ){
822 Th_Trace("append_field %#h {%#h}<br>\n",
823 argl[1], argv[1], argl[2], argv[2]);
824 }
825 for(idx=0; idx<nField; idx++){
826 if( memcmp(aField[idx].zName, argv[1], argl[1])==0
827 && aField[idx].zName[argl[1]]==0 ){
828 break;
829 }
830 }
831 if( idx>=nField ){
832 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -938,10 +938,11 @@
938 const char *zValue;
939 int nValue;
940 if( aField[i].zAppend ) continue;
941 zValue = Th_Fetch(aField[i].zName, &nValue);
942 if( zValue ){
 
943 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
944 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
945 || memcmp(zValue, aField[i].zValue, nValue)!=0
946 ||(int)strlen(aField[i].zValue)!=nValue
947 ){
@@ -1040,16 +1041,16 @@
1040 if( uid ){
1041 char * zEmail =
1042 db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
1043 uid);
1044 if( zEmail ){
1045 Th_Store("private_contact", zEmail);
1046 fossil_free(zEmail);
1047 }
1048 }
1049 }
1050 Th_Store("login", login_name());
1051 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1052 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
1053 (void*)&zNewUuid, 0);
1054 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
1055 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1120,11 +1121,11 @@
1120 initializeVariablesFromDb();
1121 if( g.zPath[0]=='d' ) showAllFields();
1122 form_begin(0, "%R/%s", g.zPath);
1123 @ <input type="hidden" name="name" value="%s(zName)">
1124 zScript = ticket_editpage_code();
1125 Th_Store("login", login_name());
1126 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1127 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
1128 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
1129 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
1130 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
1131
--- src/tkt.c
+++ src/tkt.c
@@ -210,21 +210,21 @@
210 zVal = zRevealed = db_reveal(zVal);
211 }
212 if( (j = fieldId(zName))>=0 ){
213 aField[j].zValue = mprintf("%s", zVal);
214 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
215 Th_StoreUnsafe(zName, zVal);
216 }
217 free(zRevealed);
218 }
219 Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
220 Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
221 }
222 db_finalize(&q);
223 for(i=0; i<nField; i++){
224 if( Th_Fetch(aField[i].zName, &size)==0 ){
225 Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
226 }
227 }
228 }
229
230 /*
@@ -233,11 +233,11 @@
233 static void initializeVariablesFromCGI(void){
234 int i;
235 const char *z;
236
237 for(i=0; (z = cgi_parameter_name(i))!=0; i++){
238 Th_StoreUnsafe(z, P(z));
239 }
240 }
241
242 /*
243 ** Information about a single J-card
@@ -818,15 +818,15 @@
818 if( argc!=3 ){
819 return Th_WrongNumArgs(interp, "append_field FIELD STRING");
820 }
821 if( g.thTrace ){
822 Th_Trace("append_field %#h {%#h}<br>\n",
823 TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
824 }
825 for(idx=0; idx<nField; idx++){
826 if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
827 && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
828 break;
829 }
830 }
831 if( idx>=nField ){
832 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -938,10 +938,11 @@
938 const char *zValue;
939 int nValue;
940 if( aField[i].zAppend ) continue;
941 zValue = Th_Fetch(aField[i].zName, &nValue);
942 if( zValue ){
943 nValue = TH1_LEN(nValue);
944 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
945 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
946 || memcmp(zValue, aField[i].zValue, nValue)!=0
947 ||(int)strlen(aField[i].zValue)!=nValue
948 ){
@@ -1040,16 +1041,16 @@
1041 if( uid ){
1042 char * zEmail =
1043 db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
1044 uid);
1045 if( zEmail ){
1046 Th_StoreUnsafe("private_contact", zEmail);
1047 fossil_free(zEmail);
1048 }
1049 }
1050 }
1051 Th_StoreUnsafe("login", login_name());
1052 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1053 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
1054 (void*)&zNewUuid, 0);
1055 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
1056 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1120,11 +1121,11 @@
1121 initializeVariablesFromDb();
1122 if( g.zPath[0]=='d' ) showAllFields();
1123 form_begin(0, "%R/%s", g.zPath);
1124 @ <input type="hidden" name="name" value="%s(zName)">
1125 zScript = ticket_editpage_code();
1126 Th_StoreUnsafe("login", login_name());
1127 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1128 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
1129 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
1130 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
1131 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
1132
+13 -13
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -481,11 +481,11 @@
481481
@ <th1>
482482
@ if {[info exists tkt_uuid]} {
483483
@ html "<td class='tktDspValue' colspan='3'>"
484484
@ copybtn hash-tk 0 $tkt_uuid 2
485485
@ if {[hascap s]} {
486
-@ html " ($tkt_id)"
486
+@ puts " ($tkt_id)"
487487
@ }
488488
@ html "</td></tr>\n"
489489
@ } else {
490490
@ if {[hascap s]} {
491491
@ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -522,24 +522,24 @@
522522
@ $<resolution>
523523
@ </td></tr>
524524
@ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
525525
@ <th1>
526526
@ if {[info exists tkt_datetime]} {
527
-@ html $tkt_datetime
527
+@ puts $tkt_datetime
528528
@ }
529529
@ if {[info exists tkt_mage]} {
530
-@ html "<br>$tkt_mage"
530
+@ html "<br>[htmlize $tkt_mage] ago"
531531
@ }
532532
@ </th1>
533533
@ </td>
534534
@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
535535
@ <th1>
536536
@ if {[info exists tkt_datetime_creation]} {
537
-@ html $tkt_datetime_creation
537
+@ puts $tkt_datetime_creation
538538
@ }
539539
@ if {[info exists tkt_cage]} {
540
-@ html "<br>$tkt_cage"
540
+@ html "<br>[htmlize $tkt_cage] ago"
541541
@ }
542542
@ </th1>
543543
@ </td></tr>
544544
@ <th1>enable_output [hascap e]</th1>
545545
@ <tr>
@@ -614,19 +614,19 @@
614614
@ html "User Comments:</td></tr>\n"
615615
@ html "<tr><td colspan='5' class='tktDspValue'>\n"
616616
@ set seenRow 1
617617
@ }
618618
@ html "<span class='tktDspCommenter'>"
619
-@ html "[htmlize $xlogin]"
619
+@ puts $xlogin
620620
@ if {$xlogin ne $xusername && [string length $xusername]>0} {
621
-@ html " (claiming to be [htmlize $xusername])"
621
+@ puts " (claiming to be $xusername)"
622622
@ }
623
-@ html " added on $xdate:"
623
+@ puts " added on $xdate:"
624624
@ html "</span>\n"
625625
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
626626
@ set r [randhex]
627
-@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
627
+@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
628628
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
629629
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
630630
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
631631
@ } elseif {$xmimetype eq "text/x-markdown"} {
632632
@ html [lindex [markdown $xcomment] 1]
@@ -801,19 +801,19 @@
801801
@ html "Previous User Comments:</td></tr>\n"
802802
@ html "<tr><td colspan='2' class='tktDspValue'>\n"
803803
@ set seenRow 1
804804
@ }
805805
@ html "<span class='tktDspCommenter'>"
806
-@ html "[htmlize $xlogin]"
806
+@ puts $xlogin
807807
@ if {$xlogin ne $xusername && [string length $xusername]>0} {
808
-@ html " (claiming to be [htmlize $xusername])"
808
+@ puts " (claiming to be $xusername)"
809809
@ }
810
-@ html " added on $xdate:"
810
+@ puts " added on $xdate:"
811811
@ html "</span>\n"
812812
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
813813
@ set r [randhex]
814
-@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
814
+@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
815815
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
816816
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
817817
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
818818
@ } elseif {$xmimetype eq "text/x-markdown"} {
819819
@ html [lindex [markdown $xcomment] 1]
820820
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -481,11 +481,11 @@
481 @ <th1>
482 @ if {[info exists tkt_uuid]} {
483 @ html "<td class='tktDspValue' colspan='3'>"
484 @ copybtn hash-tk 0 $tkt_uuid 2
485 @ if {[hascap s]} {
486 @ html " ($tkt_id)"
487 @ }
488 @ html "</td></tr>\n"
489 @ } else {
490 @ if {[hascap s]} {
491 @ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -522,24 +522,24 @@
522 @ $<resolution>
523 @ </td></tr>
524 @ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
525 @ <th1>
526 @ if {[info exists tkt_datetime]} {
527 @ html $tkt_datetime
528 @ }
529 @ if {[info exists tkt_mage]} {
530 @ html "<br>$tkt_mage"
531 @ }
532 @ </th1>
533 @ </td>
534 @ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
535 @ <th1>
536 @ if {[info exists tkt_datetime_creation]} {
537 @ html $tkt_datetime_creation
538 @ }
539 @ if {[info exists tkt_cage]} {
540 @ html "<br>$tkt_cage"
541 @ }
542 @ </th1>
543 @ </td></tr>
544 @ <th1>enable_output [hascap e]</th1>
545 @ <tr>
@@ -614,19 +614,19 @@
614 @ html "User Comments:</td></tr>\n"
615 @ html "<tr><td colspan='5' class='tktDspValue'>\n"
616 @ set seenRow 1
617 @ }
618 @ html "<span class='tktDspCommenter'>"
619 @ html "[htmlize $xlogin]"
620 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
621 @ html " (claiming to be [htmlize $xusername])"
622 @ }
623 @ html " added on $xdate:"
624 @ html "</span>\n"
625 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
626 @ set r [randhex]
627 @ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
628 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
629 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
630 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
631 @ } elseif {$xmimetype eq "text/x-markdown"} {
632 @ html [lindex [markdown $xcomment] 1]
@@ -801,19 +801,19 @@
801 @ html "Previous User Comments:</td></tr>\n"
802 @ html "<tr><td colspan='2' class='tktDspValue'>\n"
803 @ set seenRow 1
804 @ }
805 @ html "<span class='tktDspCommenter'>"
806 @ html "[htmlize $xlogin]"
807 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
808 @ html " (claiming to be [htmlize $xusername])"
809 @ }
810 @ html " added on $xdate:"
811 @ html "</span>\n"
812 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
813 @ set r [randhex]
814 @ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
815 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
816 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
817 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
818 @ } elseif {$xmimetype eq "text/x-markdown"} {
819 @ html [lindex [markdown $xcomment] 1]
820
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -481,11 +481,11 @@
481 @ <th1>
482 @ if {[info exists tkt_uuid]} {
483 @ html "<td class='tktDspValue' colspan='3'>"
484 @ copybtn hash-tk 0 $tkt_uuid 2
485 @ if {[hascap s]} {
486 @ puts " ($tkt_id)"
487 @ }
488 @ html "</td></tr>\n"
489 @ } else {
490 @ if {[hascap s]} {
491 @ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -522,24 +522,24 @@
522 @ $<resolution>
523 @ </td></tr>
524 @ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
525 @ <th1>
526 @ if {[info exists tkt_datetime]} {
527 @ puts $tkt_datetime
528 @ }
529 @ if {[info exists tkt_mage]} {
530 @ html "<br>[htmlize $tkt_mage] ago"
531 @ }
532 @ </th1>
533 @ </td>
534 @ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
535 @ <th1>
536 @ if {[info exists tkt_datetime_creation]} {
537 @ puts $tkt_datetime_creation
538 @ }
539 @ if {[info exists tkt_cage]} {
540 @ html "<br>[htmlize $tkt_cage] ago"
541 @ }
542 @ </th1>
543 @ </td></tr>
544 @ <th1>enable_output [hascap e]</th1>
545 @ <tr>
@@ -614,19 +614,19 @@
614 @ html "User Comments:</td></tr>\n"
615 @ html "<tr><td colspan='5' class='tktDspValue'>\n"
616 @ set seenRow 1
617 @ }
618 @ html "<span class='tktDspCommenter'>"
619 @ puts $xlogin
620 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
621 @ puts " (claiming to be $xusername)"
622 @ }
623 @ puts " added on $xdate:"
624 @ html "</span>\n"
625 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
626 @ set r [randhex]
627 @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
628 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
629 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
630 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
631 @ } elseif {$xmimetype eq "text/x-markdown"} {
632 @ html [lindex [markdown $xcomment] 1]
@@ -801,19 +801,19 @@
801 @ html "Previous User Comments:</td></tr>\n"
802 @ html "<tr><td colspan='2' class='tktDspValue'>\n"
803 @ set seenRow 1
804 @ }
805 @ html "<span class='tktDspCommenter'>"
806 @ puts $xlogin
807 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
808 @ puts " (claiming to be $xusername)"
809 @ }
810 @ puts " added on $xdate:"
811 @ html "</span>\n"
812 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
813 @ set r [randhex]
814 @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
815 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
816 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
817 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
818 @ } elseif {$xmimetype eq "text/x-markdown"} {
819 @ html [lindex [markdown $xcomment] 1]
820

Keyboard Shortcuts

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