Fossil SCM

Sync with trunk.

florian 2025-04-25 16:18 standard-cli-colors merge
Commit 855076ce79275ccf2e206b5359371a900734dff9228aae0262547f5f99e90b53
--- 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
+31 -17
--- src/alerts.c
+++ src/alerts.c
@@ -984,13 +984,10 @@
984984
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
985985
}else{
986986
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
987987
}
988988
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
989
- if( p->zListId && p->zListId[0] ){
990
- blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
991
- }
992989
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
993990
/* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
994991
** the current unix-time in hex, $(random) is a 64-bit random number,
995992
** and $(from) is the domain part of the email-self setting. */
996993
sqlite3_randomness(sizeof(r1), &r1);
@@ -3215,18 +3212,21 @@
32153212
Blob fhdr, fbody;
32163213
blob_init(&fhdr, 0, 0);
32173214
blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
32183215
blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
32193216
blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3220
- blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3221
- zUrl, zCode);
3222
- blob_appendf(&fhdr,
3223
- "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3224
- blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3225
- zUrl, zCode);
3226
- /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3227
- ** zUrl, zCode); */
3217
+ if( pSender->zListId && pSender->zListId[0] ){
3218
+ blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
3219
+ blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3220
+ zUrl, zCode);
3221
+ blob_appendf(&fhdr,
3222
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3223
+ blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3224
+ zUrl, zCode);
3225
+ /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3226
+ ** zUrl, zCode); */
3227
+ }
32283228
alert_send(pSender,&fhdr,&fbody,p->zFromName);
32293229
nSent++;
32303230
blob_reset(&fhdr);
32313231
blob_reset(&fbody);
32323232
}else{
@@ -3245,15 +3245,19 @@
32453245
blob_append(&body, "\n", 1);
32463246
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
32473247
}
32483248
}
32493249
if( nHit==0 ) continue;
3250
- blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3251
- zUrl, zCode);
3252
- blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3253
- blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3254
- zUrl, zCode);
3250
+ if( pSender->zListId && pSender->zListId[0] ){
3251
+ blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3252
+ blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3253
+ zUrl, zCode);
3254
+ blob_appendf(&hdr,
3255
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3256
+ blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3257
+ zUrl, zCode);
3258
+ }
32553259
alert_send(pSender,&hdr,&body,0);
32563260
nSent++;
32573261
blob_truncate(&hdr, 0);
32583262
blob_truncate(&body, 0);
32593263
}
@@ -3295,18 +3299,28 @@
32953299
" AND length(sdigest)>0",
32963300
iNewWarn, iOldWarn
32973301
);
32983302
while( db_step(&q)==SQLITE_ROW ){
32993303
Blob hdr, body;
3304
+ const char *zCode = db_column_text(&q,0);
33003305
blob_init(&hdr, 0, 0);
33013306
blob_init(&body, 0, 0);
33023307
alert_renewal_msg(&hdr, &body,
3303
- db_column_text(&q,0),
3308
+ zCode,
33043309
db_column_int(&q,1),
33053310
db_column_text(&q,2),
33063311
db_column_text(&q,3),
33073312
zRepoName, zUrl);
3313
+ if( pSender->zListId && pSender->zListId[0] ){
3314
+ blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3315
+ blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3316
+ zUrl, zCode);
3317
+ blob_appendf(&hdr,
3318
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3319
+ blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3320
+ zUrl, zCode);
3321
+ }
33083322
alert_send(pSender,&hdr,&body,0);
33093323
blob_reset(&hdr);
33103324
blob_reset(&body);
33113325
}
33123326
db_finalize(&q);
33133327
--- src/alerts.c
+++ src/alerts.c
@@ -984,13 +984,10 @@
984 blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
985 }else{
986 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
987 }
988 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
989 if( p->zListId && p->zListId[0] ){
990 blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
991 }
992 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
993 /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
994 ** the current unix-time in hex, $(random) is a 64-bit random number,
995 ** and $(from) is the domain part of the email-self setting. */
996 sqlite3_randomness(sizeof(r1), &r1);
@@ -3215,18 +3212,21 @@
3215 Blob fhdr, fbody;
3216 blob_init(&fhdr, 0, 0);
3217 blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
3218 blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
3219 blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3220 blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3221 zUrl, zCode);
3222 blob_appendf(&fhdr,
3223 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3224 blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3225 zUrl, zCode);
3226 /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3227 ** zUrl, zCode); */
 
 
 
3228 alert_send(pSender,&fhdr,&fbody,p->zFromName);
3229 nSent++;
3230 blob_reset(&fhdr);
3231 blob_reset(&fbody);
3232 }else{
@@ -3245,15 +3245,19 @@
3245 blob_append(&body, "\n", 1);
3246 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
3247 }
3248 }
3249 if( nHit==0 ) continue;
3250 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3251 zUrl, zCode);
3252 blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3253 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3254 zUrl, zCode);
 
 
 
 
3255 alert_send(pSender,&hdr,&body,0);
3256 nSent++;
3257 blob_truncate(&hdr, 0);
3258 blob_truncate(&body, 0);
3259 }
@@ -3295,18 +3299,28 @@
3295 " AND length(sdigest)>0",
3296 iNewWarn, iOldWarn
3297 );
3298 while( db_step(&q)==SQLITE_ROW ){
3299 Blob hdr, body;
 
3300 blob_init(&hdr, 0, 0);
3301 blob_init(&body, 0, 0);
3302 alert_renewal_msg(&hdr, &body,
3303 db_column_text(&q,0),
3304 db_column_int(&q,1),
3305 db_column_text(&q,2),
3306 db_column_text(&q,3),
3307 zRepoName, zUrl);
 
 
 
 
 
 
 
 
 
3308 alert_send(pSender,&hdr,&body,0);
3309 blob_reset(&hdr);
3310 blob_reset(&body);
3311 }
3312 db_finalize(&q);
3313
--- src/alerts.c
+++ src/alerts.c
@@ -984,13 +984,10 @@
984 blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
985 }else{
986 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
987 }
988 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
 
 
 
989 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
990 /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
991 ** the current unix-time in hex, $(random) is a 64-bit random number,
992 ** and $(from) is the domain part of the email-self setting. */
993 sqlite3_randomness(sizeof(r1), &r1);
@@ -3215,18 +3212,21 @@
3212 Blob fhdr, fbody;
3213 blob_init(&fhdr, 0, 0);
3214 blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
3215 blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
3216 blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3217 if( pSender->zListId && pSender->zListId[0] ){
3218 blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
3219 blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3220 zUrl, zCode);
3221 blob_appendf(&fhdr,
3222 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3223 blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3224 zUrl, zCode);
3225 /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3226 ** zUrl, zCode); */
3227 }
3228 alert_send(pSender,&fhdr,&fbody,p->zFromName);
3229 nSent++;
3230 blob_reset(&fhdr);
3231 blob_reset(&fbody);
3232 }else{
@@ -3245,15 +3245,19 @@
3245 blob_append(&body, "\n", 1);
3246 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
3247 }
3248 }
3249 if( nHit==0 ) continue;
3250 if( pSender->zListId && pSender->zListId[0] ){
3251 blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3252 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3253 zUrl, zCode);
3254 blob_appendf(&hdr,
3255 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3256 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3257 zUrl, zCode);
3258 }
3259 alert_send(pSender,&hdr,&body,0);
3260 nSent++;
3261 blob_truncate(&hdr, 0);
3262 blob_truncate(&body, 0);
3263 }
@@ -3295,18 +3299,28 @@
3299 " AND length(sdigest)>0",
3300 iNewWarn, iOldWarn
3301 );
3302 while( db_step(&q)==SQLITE_ROW ){
3303 Blob hdr, body;
3304 const char *zCode = db_column_text(&q,0);
3305 blob_init(&hdr, 0, 0);
3306 blob_init(&body, 0, 0);
3307 alert_renewal_msg(&hdr, &body,
3308 zCode,
3309 db_column_int(&q,1),
3310 db_column_text(&q,2),
3311 db_column_text(&q,3),
3312 zRepoName, zUrl);
3313 if( pSender->zListId && pSender->zListId[0] ){
3314 blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3315 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3316 zUrl, zCode);
3317 blob_appendf(&hdr,
3318 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3319 blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3320 zUrl, zCode);
3321 }
3322 alert_send(pSender,&hdr,&body,0);
3323 blob_reset(&hdr);
3324 blob_reset(&body);
3325 }
3326 db_finalize(&q);
3327
+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
+252 -187
--- src/cgi.c
+++ src/cgi.c
@@ -2497,29 +2497,10 @@
24972497
}
24982498
fossil_free(zToFree);
24992499
fgetc(g.httpIn); /* Read past the "," separating header from content */
25002500
cgi_init();
25012501
}
2502
-
2503
-#if !defined(_WIN32)
2504
-/*
2505
-** Change the listening socket, if necessary, so that it will accept both IPv4
2506
-** and IPv6
2507
-*/
2508
-static void allowBothIpV4andV6(int listener){
2509
-#if defined(IPV6_V6ONLY)
2510
- int ipv6only = -1;
2511
- socklen_t ipv6only_size = sizeof(ipv6only);
2512
- getsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, &ipv6only_size);
2513
- if( ipv6only ){
2514
- ipv6only = 0;
2515
- setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, ipv6only_size);
2516
- }
2517
-#endif /* defined(IPV6_ONLY) */
2518
-}
2519
-#endif /* !defined(_WIN32) */
2520
-
25212502
25222503
#if INTERFACE
25232504
/*
25242505
** Bitmap values for the flags parameter to cgi_http_server().
25252506
*/
@@ -2559,150 +2540,222 @@
25592540
){
25602541
#if defined(_WIN32)
25612542
/* Use win32_http_server() instead */
25622543
fossil_exit(1);
25632544
#else
2564
- int listener = -1; /* The server socket */
2565
- int connection; /* A socket for each individual connection */
2545
+ int listen4 = -1; /* Main socket; IPv4 or unix-domain */
2546
+ int listen6 = -1; /* Aux socket for corresponding IPv6 */
2547
+ int mxListen = -1; /* Maximum of listen4 and listen6 */
2548
+ int connection; /* An incoming connection */
25662549
int nRequest = 0; /* Number of requests handled so far */
25672550
fd_set readfds; /* Set of file descriptors for select() */
25682551
socklen_t lenaddr; /* Length of the inaddr structure */
25692552
int child; /* PID of the child process */
25702553
int nchildren = 0; /* Number of child processes */
25712554
struct timeval delay; /* How long to wait inside select() */
2572
- struct sockaddr_in6 inaddr; /* The socket address */
2573
- struct sockaddr_in inaddr4; /* IPv4 address; needed by OpenBSD */
2555
+ struct sockaddr_in6 inaddr6; /* Address for IPv6 */
2556
+ struct sockaddr_in inaddr4; /* Address for IPv4 */
25742557
struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
25752558
int opt = 1; /* setsockopt flag */
25762559
int rc; /* Result code from system calls */
25772560
int iPort = mnPort; /* Port to try to use */
2578
- int bIPv4 = 0; /* Use IPv4 only; use inaddr4, not inaddr */
2579
-
2580
- while( iPort<=mxPort ){
2581
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2582
- /* Initialize a Unix socket named g.zSockName */
2583
- assert( g.zSockName!=0 );
2584
- memset(&uxaddr, 0, sizeof(uxaddr));
2585
- if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2586
- fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2587
- g.zSockName, (int)sizeof(uxaddr.sun_path));
2588
- }
2589
- if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2590
- if( !file_issocket(g.zSockName) ){
2591
- fossil_fatal("cannot name socket \"%s\" because another object"
2592
- " with that name already exists", g.zSockName);
2593
- }else{
2594
- unlink(g.zSockName);
2595
- }
2596
- }
2597
- uxaddr.sun_family = AF_UNIX;
2598
- strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2599
- listener = socket(AF_UNIX, SOCK_STREAM, 0);
2600
- if( listener<0 ){
2601
- fossil_fatal("unable to create a unix socket named %s",
2602
- g.zSockName);
2603
- }
2604
- /* Set the access permission for the new socket. Default to 0660.
2605
- ** But use an alternative specified by --socket-mode if available.
2606
- ** Do this before bind() to avoid a race condition. */
2607
- if( g.zSockMode ){
2608
- file_set_mode(g.zSockName, listener, g.zSockMode, 0);
2609
- }else{
2610
- file_set_mode(g.zSockName, listener, "0660", 1);
2611
- }
2612
- }else{
2613
- /* Initialize a TCP/IP socket on port iPort */
2614
- if( (flags & HTTP_SERVER_LOCALHOST)!=0 && zIpAddr==0 ){
2615
- /* Map all loopback to 127.0.0.1, since this is the easiest way
2616
- ** to support OpenBSD and its limitations without burdening
2617
- ** Linux and MacOS with lots of extra code and complication. */
2618
- zIpAddr = "127.0.0.1";
2619
- }
2620
- if( zIpAddr ){
2621
- if( strchr(zIpAddr,':') ){
2622
- memset(&inaddr, 0, sizeof(inaddr));
2623
- inaddr.sin6_family = AF_INET6;
2624
- bIPv4 = 0;
2625
- if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){
2626
- fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2627
- }
2628
- }else{
2629
- memset(&inaddr4, 0, sizeof(inaddr4));
2630
- inaddr4.sin_family = AF_INET;
2631
- bIPv4 = 1;
2632
- inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2633
- if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2634
- fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2635
- }
2636
- }
2637
- }else{
2638
- /* Bind to any and all available IP addresses */
2639
- memset(&inaddr, 0, sizeof(inaddr));
2640
- inaddr.sin6_family = AF_INET6;
2641
- inaddr.sin6_addr = in6addr_any;
2642
- bIPv4 = 0;
2643
- }
2644
- if( bIPv4 ){
2645
- inaddr4.sin_port = htons(iPort);
2646
- listener = socket(AF_INET, SOCK_STREAM, 0);
2647
- }else{
2648
- inaddr.sin6_port = htons(iPort);
2649
- listener = socket(AF_INET6, SOCK_STREAM, 0);
2650
- allowBothIpV4andV6(listener);
2651
- }
2652
- if( listener<0 ){
2653
- iPort++;
2654
- continue;
2655
- }
2656
- }
2657
-
2658
- /* if we can't terminate nicely, at least allow the socket to be reused */
2659
- setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
2660
-
2661
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2662
- rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2663
- /* Set the owner of the socket if requested by --socket-owner. This
2664
- ** must wait until after bind(), after the filesystem object has been
2665
- ** created. See https://lkml.org/lkml/2004/11/1/84 and
2666
- ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2667
- if( g.zSockOwner ){
2668
- file_set_owner(g.zSockName, listener, g.zSockOwner);
2669
- }
2670
- }else if( bIPv4 ){
2671
- rc = bind(listener, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2672
- }else{
2673
- rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));
2674
- }
2675
- if( rc<0 ){
2676
- close(listener);
2677
- iPort++;
2678
- continue;
2679
- }
2680
- break;
2681
- }
2682
- if( iPort>mxPort ){
2683
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2684
- fossil_fatal("unable to listen on unix socket %s", zIpAddr);
2685
- }else if( mnPort==mxPort ){
2686
- fossil_fatal("unable to open listening socket on port %d", mnPort);
2687
- }else{
2688
- fossil_fatal("unable to open listening socket on any"
2689
- " port in the range %d..%d", mnPort, mxPort);
2690
- }
2691
- }
2692
- if( iPort>mxPort ) return 1;
2693
- listen(listener,10);
2694
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2695
- fossil_print("Listening for %s requests on unix socket %s\n",
2696
- (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2697
- g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName);
2698
- }else{
2699
- fossil_print("Listening for %s requests on TCP port %d\n",
2700
- (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2701
- g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort);
2702
- }
2703
- fflush(stdout);
2561
+ const char *zRequestType; /* Type of requests to listen for */
2562
+
2563
+
2564
+ if( flags & HTTP_SERVER_SCGI ){
2565
+ zRequestType = "SCGI";
2566
+ }else if( g.httpUseSSL ){
2567
+ zRequestType = "TLS-encrypted HTTPS";
2568
+ }else{
2569
+ zRequestType = "HTTP";
2570
+ }
2571
+
2572
+ if( flags & HTTP_SERVER_UNIXSOCKET ){
2573
+ /* CASE 1: A unix socket named g.zSockName. After creation, set the
2574
+ ** permissions on the new socket to g.zSockMode and make the
2575
+ ** owner of the socket be g.zSockOwner.
2576
+ */
2577
+ assert( g.zSockName!=0 );
2578
+ memset(&uxaddr, 0, sizeof(uxaddr));
2579
+ if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2580
+ fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2581
+ g.zSockName, (int)sizeof(uxaddr.sun_path));
2582
+ }
2583
+ if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2584
+ if( !file_issocket(g.zSockName) ){
2585
+ fossil_fatal("cannot name socket \"%s\" because another object"
2586
+ " with that name already exists", g.zSockName);
2587
+ }else{
2588
+ unlink(g.zSockName);
2589
+ }
2590
+ }
2591
+ uxaddr.sun_family = AF_UNIX;
2592
+ strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2593
+ listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
2594
+ if( listen4<0 ){
2595
+ fossil_fatal("unable to create a unix socket named %s",
2596
+ g.zSockName);
2597
+ }
2598
+ mxListen = listen4;
2599
+ listen6 = -1;
2600
+
2601
+ /* Set the access permission for the new socket. Default to 0660.
2602
+ ** But use an alternative specified by --socket-mode if available.
2603
+ ** Do this before bind() to avoid a race condition. */
2604
+ if( g.zSockMode ){
2605
+ file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
2606
+ }else{
2607
+ file_set_mode(g.zSockName, listen4, "0660", 1);
2608
+ }
2609
+ rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2610
+ /* Set the owner of the socket if requested by --socket-owner. This
2611
+ ** must wait until after bind(), after the filesystem object has been
2612
+ ** created. See https://lkml.org/lkml/2004/11/1/84 and
2613
+ ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2614
+ if( g.zSockOwner ){
2615
+ file_set_owner(g.zSockName, listen4, g.zSockOwner);
2616
+ }
2617
+ fossil_print("Listening for %s requests on unix socket %s\n",
2618
+ zRequestType, g.zSockName);
2619
+ fflush(stdout);
2620
+ }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
2621
+ /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
2622
+ */
2623
+ assert( mnPort==mxPort );
2624
+ memset(&inaddr6, 0, sizeof(inaddr6));
2625
+ inaddr6.sin6_family = AF_INET6;
2626
+ inaddr6.sin6_port = htons(iPort);
2627
+ if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
2628
+ fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2629
+ }
2630
+ listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2631
+ if( listen6>0 ){
2632
+ opt = 1;
2633
+ setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2634
+ rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2635
+ if( rc<0 ){
2636
+ close(listen6);
2637
+ listen6 = -1;
2638
+ }
2639
+ }
2640
+ if( listen6<0 ){
2641
+ fossil_fatal("cannot open a listening socket on [%s]:%d",
2642
+ zIpAddr, mnPort);
2643
+ }
2644
+ mxListen = listen6;
2645
+ listen4 = -1;
2646
+ fossil_print("Listening for %s requests on [%s]:%d\n",
2647
+ zRequestType, zIpAddr, iPort);
2648
+ fflush(stdout);
2649
+ }else if( zIpAddr && zIpAddr[0] ){
2650
+ /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
2651
+ */
2652
+ assert( mnPort==mxPort );
2653
+ memset(&inaddr4, 0, sizeof(inaddr4));
2654
+ inaddr4.sin_family = AF_INET;
2655
+ inaddr4.sin_port = htons(iPort);
2656
+ if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
2657
+ inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2658
+ if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2659
+ fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2660
+ }
2661
+ listen4 = socket(AF_INET, SOCK_STREAM, 0);
2662
+ if( listen4>0 ){
2663
+ setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2664
+ rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2665
+ if( rc<0 ){
2666
+ close(listen6);
2667
+ listen4 = -1;
2668
+ }
2669
+ }
2670
+ if( listen4<0 ){
2671
+ fossil_fatal("cannot open a listening socket on %s:%d",
2672
+ zIpAddr, mnPort);
2673
+ }
2674
+ mxListen = listen4;
2675
+ listen6 = -1;
2676
+ fossil_print("Listening for %s requests on TCP port %s:%d\n",
2677
+ zRequestType, zIpAddr, iPort);
2678
+ fflush(stdout);
2679
+ }else{
2680
+ /* CASE 4: Listen on all available IP addresses, or on only loopback
2681
+ ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
2682
+ ** first available in the range of mnPort..mxPort. Listen
2683
+ ** on both IPv4 and IPv6, if possible. The TCP port scan is done
2684
+ ** on IPv4.
2685
+ */
2686
+ while( iPort<=mxPort ){
2687
+ const char *zProto;
2688
+ memset(&inaddr4, 0, sizeof(inaddr4));
2689
+ inaddr4.sin_family = AF_INET;
2690
+ inaddr4.sin_port = htons(iPort);
2691
+ if( flags & HTTP_SERVER_LOCALHOST ){
2692
+ inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2693
+ }else{
2694
+ inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
2695
+ }
2696
+ listen4 = socket(AF_INET, SOCK_STREAM, 0);
2697
+ if( listen4>0 ){
2698
+ setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2699
+ rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2700
+ if( rc<0 ){
2701
+ close(listen4);
2702
+ listen4 = -1;
2703
+ }
2704
+ }
2705
+ if( listen4<0 ){
2706
+ iPort++;
2707
+ continue;
2708
+ }
2709
+ mxListen = listen4;
2710
+
2711
+ /* If we get here, that means we found an open TCP port at iPort for
2712
+ ** IPv4. Try to set up a corresponding IPv6 socket on the same port.
2713
+ */
2714
+ memset(&inaddr6, 0, sizeof(inaddr6));
2715
+ inaddr6.sin6_family = AF_INET6;
2716
+ inaddr6.sin6_port = htons(iPort);
2717
+ if( flags & HTTP_SERVER_LOCALHOST ){
2718
+ inaddr6.sin6_addr = in6addr_loopback;
2719
+ }else{
2720
+ inaddr6.sin6_addr = in6addr_any;
2721
+ }
2722
+ listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2723
+ if( listen6>0 ){
2724
+ setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2725
+ setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
2726
+ rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2727
+ if( rc<0 ){
2728
+ close(listen6);
2729
+ listen6 = -1;
2730
+ }
2731
+ }
2732
+ if( listen6<0 ){
2733
+ zProto = "IPv4 only";
2734
+ }else{
2735
+ zProto = "IPv4 and IPv6";
2736
+ if( listen6>listen4 ) mxListen = listen6;
2737
+ }
2738
+
2739
+ fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
2740
+ zRequestType,
2741
+ (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
2742
+ iPort, zProto);
2743
+ fflush(stdout);
2744
+ break;
2745
+ }
2746
+ if( iPort>mxPort ){
2747
+ fossil_fatal("no available TCP ports in the range %d..%d",
2748
+ mnPort, mxPort);
2749
+ }
2750
+ }
2751
+
2752
+ /* If we get to this point, that means there is at least one listening
2753
+ ** socket on either listen4 or listen6 and perhaps on both. */
2754
+ assert( listen4>0 || listen6>0 );
2755
+ if( listen4>0 ) listen(listen4,10);
2756
+ if( listen6>0 ) listen(listen6,10);
27042757
if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
27052758
assert( strstr(zBrowser,"%d")!=0 );
27062759
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
27072760
#if defined(__CYGWIN__)
27082761
/* On Cygwin, we can do better than "echo" */
@@ -2716,57 +2769,69 @@
27162769
#endif
27172770
if( fossil_system(zBrowser)<0 ){
27182771
fossil_warning("cannot start browser: %s\n", zBrowser);
27192772
}
27202773
}
2774
+
2775
+ /* What for incomming requests. For each request, fork() a child process
2776
+ ** to deal with that request. The child process returns. The parent
2777
+ ** keeps on listening and never returns.
2778
+ */
27212779
while( 1 ){
27222780
#if FOSSIL_MAX_CONNECTIONS>0
27232781
while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
27242782
if( wait(0)>=0 ) nchildren--;
27252783
}
27262784
#endif
27272785
delay.tv_sec = 0;
27282786
delay.tv_usec = 100000;
27292787
FD_ZERO(&readfds);
2730
- assert( listener>=0 );
2731
- FD_SET( listener, &readfds);
2732
- select( listener+1, &readfds, 0, 0, &delay);
2733
- if( FD_ISSET(listener, &readfds) ){
2734
- lenaddr = sizeof(inaddr);
2735
- connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
2736
- if( connection>=0 ){
2737
- if( flags & HTTP_SERVER_NOFORK ){
2738
- child = 0;
2739
- }else{
2740
- child = fork();
2741
- }
2742
- if( child!=0 ){
2743
- if( child>0 ){
2744
- nchildren++;
2745
- nRequest++;
2746
- }
2747
- close(connection);
2748
- }else{
2749
- int nErr = 0, fd;
2750
- g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2751
- close(0);
2752
- fd = dup(connection);
2753
- if( fd!=0 ) nErr++;
2754
- close(1);
2755
- fd = dup(connection);
2756
- if( fd!=1 ) nErr++;
2757
- if( 0 && !g.fAnyTrace ){
2758
- close(2);
2759
- fd = dup(connection);
2760
- if( fd!=2 ) nErr++;
2761
- }
2762
- close(connection);
2763
- close(listener);
2764
- g.nPendingRequest = nchildren+1;
2765
- g.nRequest = nRequest+1;
2766
- return nErr;
2767
- }
2788
+ assert( listen4>0 || listen6>0 );
2789
+ if( listen4>0 ) FD_SET( listen4, &readfds);
2790
+ if( listen6>0 ) FD_SET( listen6, &readfds);
2791
+ select( mxListen+1, &readfds, 0, 0, &delay);
2792
+ if( listen4>0 && FD_ISSET(listen4, &readfds) ){
2793
+ lenaddr = sizeof(inaddr4);
2794
+ connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
2795
+ }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
2796
+ lenaddr = sizeof(inaddr6);
2797
+ connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
2798
+ }else{
2799
+ connection = -1;
2800
+ }
2801
+ if( connection>=0 ){
2802
+ if( flags & HTTP_SERVER_NOFORK ){
2803
+ child = 0;
2804
+ }else{
2805
+ child = fork();
2806
+ }
2807
+ if( child!=0 ){
2808
+ if( child>0 ){
2809
+ nchildren++;
2810
+ nRequest++;
2811
+ }
2812
+ close(connection);
2813
+ }else{
2814
+ int nErr = 0, fd;
2815
+ g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2816
+ close(0);
2817
+ fd = dup(connection);
2818
+ if( fd!=0 ) nErr++;
2819
+ close(1);
2820
+ fd = dup(connection);
2821
+ if( fd!=1 ) nErr++;
2822
+ if( 0 && !g.fAnyTrace ){
2823
+ close(2);
2824
+ fd = dup(connection);
2825
+ if( fd!=2 ) nErr++;
2826
+ }
2827
+ close(connection);
2828
+ if( listen4>0 ) close(listen4);
2829
+ if( listen6>0 ) close(listen6);
2830
+ g.nPendingRequest = nchildren+1;
2831
+ g.nRequest = nRequest+1;
2832
+ return nErr;
27682833
}
27692834
}
27702835
/* Bury dead children */
27712836
if( nchildren ){
27722837
while(1){
27732838
--- src/cgi.c
+++ src/cgi.c
@@ -2497,29 +2497,10 @@
2497 }
2498 fossil_free(zToFree);
2499 fgetc(g.httpIn); /* Read past the "," separating header from content */
2500 cgi_init();
2501 }
2502
2503 #if !defined(_WIN32)
2504 /*
2505 ** Change the listening socket, if necessary, so that it will accept both IPv4
2506 ** and IPv6
2507 */
2508 static void allowBothIpV4andV6(int listener){
2509 #if defined(IPV6_V6ONLY)
2510 int ipv6only = -1;
2511 socklen_t ipv6only_size = sizeof(ipv6only);
2512 getsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, &ipv6only_size);
2513 if( ipv6only ){
2514 ipv6only = 0;
2515 setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, ipv6only_size);
2516 }
2517 #endif /* defined(IPV6_ONLY) */
2518 }
2519 #endif /* !defined(_WIN32) */
2520
2521
2522 #if INTERFACE
2523 /*
2524 ** Bitmap values for the flags parameter to cgi_http_server().
2525 */
@@ -2559,150 +2540,222 @@
2559 ){
2560 #if defined(_WIN32)
2561 /* Use win32_http_server() instead */
2562 fossil_exit(1);
2563 #else
2564 int listener = -1; /* The server socket */
2565 int connection; /* A socket for each individual connection */
 
 
2566 int nRequest = 0; /* Number of requests handled so far */
2567 fd_set readfds; /* Set of file descriptors for select() */
2568 socklen_t lenaddr; /* Length of the inaddr structure */
2569 int child; /* PID of the child process */
2570 int nchildren = 0; /* Number of child processes */
2571 struct timeval delay; /* How long to wait inside select() */
2572 struct sockaddr_in6 inaddr; /* The socket address */
2573 struct sockaddr_in inaddr4; /* IPv4 address; needed by OpenBSD */
2574 struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
2575 int opt = 1; /* setsockopt flag */
2576 int rc; /* Result code from system calls */
2577 int iPort = mnPort; /* Port to try to use */
2578 int bIPv4 = 0; /* Use IPv4 only; use inaddr4, not inaddr */
2579
2580 while( iPort<=mxPort ){
2581 if( flags & HTTP_SERVER_UNIXSOCKET ){
2582 /* Initialize a Unix socket named g.zSockName */
2583 assert( g.zSockName!=0 );
2584 memset(&uxaddr, 0, sizeof(uxaddr));
2585 if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2586 fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2587 g.zSockName, (int)sizeof(uxaddr.sun_path));
2588 }
2589 if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2590 if( !file_issocket(g.zSockName) ){
2591 fossil_fatal("cannot name socket \"%s\" because another object"
2592 " with that name already exists", g.zSockName);
2593 }else{
2594 unlink(g.zSockName);
2595 }
2596 }
2597 uxaddr.sun_family = AF_UNIX;
2598 strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2599 listener = socket(AF_UNIX, SOCK_STREAM, 0);
2600 if( listener<0 ){
2601 fossil_fatal("unable to create a unix socket named %s",
2602 g.zSockName);
2603 }
2604 /* Set the access permission for the new socket. Default to 0660.
2605 ** But use an alternative specified by --socket-mode if available.
2606 ** Do this before bind() to avoid a race condition. */
2607 if( g.zSockMode ){
2608 file_set_mode(g.zSockName, listener, g.zSockMode, 0);
2609 }else{
2610 file_set_mode(g.zSockName, listener, "0660", 1);
2611 }
2612 }else{
2613 /* Initialize a TCP/IP socket on port iPort */
2614 if( (flags & HTTP_SERVER_LOCALHOST)!=0 && zIpAddr==0 ){
2615 /* Map all loopback to 127.0.0.1, since this is the easiest way
2616 ** to support OpenBSD and its limitations without burdening
2617 ** Linux and MacOS with lots of extra code and complication. */
2618 zIpAddr = "127.0.0.1";
2619 }
2620 if( zIpAddr ){
2621 if( strchr(zIpAddr,':') ){
2622 memset(&inaddr, 0, sizeof(inaddr));
2623 inaddr.sin6_family = AF_INET6;
2624 bIPv4 = 0;
2625 if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){
2626 fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2627 }
2628 }else{
2629 memset(&inaddr4, 0, sizeof(inaddr4));
2630 inaddr4.sin_family = AF_INET;
2631 bIPv4 = 1;
2632 inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2633 if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2634 fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2635 }
2636 }
2637 }else{
2638 /* Bind to any and all available IP addresses */
2639 memset(&inaddr, 0, sizeof(inaddr));
2640 inaddr.sin6_family = AF_INET6;
2641 inaddr.sin6_addr = in6addr_any;
2642 bIPv4 = 0;
2643 }
2644 if( bIPv4 ){
2645 inaddr4.sin_port = htons(iPort);
2646 listener = socket(AF_INET, SOCK_STREAM, 0);
2647 }else{
2648 inaddr.sin6_port = htons(iPort);
2649 listener = socket(AF_INET6, SOCK_STREAM, 0);
2650 allowBothIpV4andV6(listener);
2651 }
2652 if( listener<0 ){
2653 iPort++;
2654 continue;
2655 }
2656 }
2657
2658 /* if we can't terminate nicely, at least allow the socket to be reused */
2659 setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
2660
2661 if( flags & HTTP_SERVER_UNIXSOCKET ){
2662 rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2663 /* Set the owner of the socket if requested by --socket-owner. This
2664 ** must wait until after bind(), after the filesystem object has been
2665 ** created. See https://lkml.org/lkml/2004/11/1/84 and
2666 ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2667 if( g.zSockOwner ){
2668 file_set_owner(g.zSockName, listener, g.zSockOwner);
2669 }
2670 }else if( bIPv4 ){
2671 rc = bind(listener, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2672 }else{
2673 rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));
2674 }
2675 if( rc<0 ){
2676 close(listener);
2677 iPort++;
2678 continue;
2679 }
2680 break;
2681 }
2682 if( iPort>mxPort ){
2683 if( flags & HTTP_SERVER_UNIXSOCKET ){
2684 fossil_fatal("unable to listen on unix socket %s", zIpAddr);
2685 }else if( mnPort==mxPort ){
2686 fossil_fatal("unable to open listening socket on port %d", mnPort);
2687 }else{
2688 fossil_fatal("unable to open listening socket on any"
2689 " port in the range %d..%d", mnPort, mxPort);
2690 }
2691 }
2692 if( iPort>mxPort ) return 1;
2693 listen(listener,10);
2694 if( flags & HTTP_SERVER_UNIXSOCKET ){
2695 fossil_print("Listening for %s requests on unix socket %s\n",
2696 (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2697 g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName);
2698 }else{
2699 fossil_print("Listening for %s requests on TCP port %d\n",
2700 (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2701 g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort);
2702 }
2703 fflush(stdout);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2704 if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
2705 assert( strstr(zBrowser,"%d")!=0 );
2706 zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2707 #if defined(__CYGWIN__)
2708 /* On Cygwin, we can do better than "echo" */
@@ -2716,57 +2769,69 @@
2716 #endif
2717 if( fossil_system(zBrowser)<0 ){
2718 fossil_warning("cannot start browser: %s\n", zBrowser);
2719 }
2720 }
 
 
 
 
 
2721 while( 1 ){
2722 #if FOSSIL_MAX_CONNECTIONS>0
2723 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2724 if( wait(0)>=0 ) nchildren--;
2725 }
2726 #endif
2727 delay.tv_sec = 0;
2728 delay.tv_usec = 100000;
2729 FD_ZERO(&readfds);
2730 assert( listener>=0 );
2731 FD_SET( listener, &readfds);
2732 select( listener+1, &readfds, 0, 0, &delay);
2733 if( FD_ISSET(listener, &readfds) ){
2734 lenaddr = sizeof(inaddr);
2735 connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
2736 if( connection>=0 ){
2737 if( flags & HTTP_SERVER_NOFORK ){
2738 child = 0;
2739 }else{
2740 child = fork();
2741 }
2742 if( child!=0 ){
2743 if( child>0 ){
2744 nchildren++;
2745 nRequest++;
2746 }
2747 close(connection);
2748 }else{
2749 int nErr = 0, fd;
2750 g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2751 close(0);
2752 fd = dup(connection);
2753 if( fd!=0 ) nErr++;
2754 close(1);
2755 fd = dup(connection);
2756 if( fd!=1 ) nErr++;
2757 if( 0 && !g.fAnyTrace ){
2758 close(2);
2759 fd = dup(connection);
2760 if( fd!=2 ) nErr++;
2761 }
2762 close(connection);
2763 close(listener);
2764 g.nPendingRequest = nchildren+1;
2765 g.nRequest = nRequest+1;
2766 return nErr;
2767 }
 
 
 
 
 
 
 
2768 }
2769 }
2770 /* Bury dead children */
2771 if( nchildren ){
2772 while(1){
2773
--- src/cgi.c
+++ src/cgi.c
@@ -2497,29 +2497,10 @@
2497 }
2498 fossil_free(zToFree);
2499 fgetc(g.httpIn); /* Read past the "," separating header from content */
2500 cgi_init();
2501 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2502
2503 #if INTERFACE
2504 /*
2505 ** Bitmap values for the flags parameter to cgi_http_server().
2506 */
@@ -2559,150 +2540,222 @@
2540 ){
2541 #if defined(_WIN32)
2542 /* Use win32_http_server() instead */
2543 fossil_exit(1);
2544 #else
2545 int listen4 = -1; /* Main socket; IPv4 or unix-domain */
2546 int listen6 = -1; /* Aux socket for corresponding IPv6 */
2547 int mxListen = -1; /* Maximum of listen4 and listen6 */
2548 int connection; /* An incoming connection */
2549 int nRequest = 0; /* Number of requests handled so far */
2550 fd_set readfds; /* Set of file descriptors for select() */
2551 socklen_t lenaddr; /* Length of the inaddr structure */
2552 int child; /* PID of the child process */
2553 int nchildren = 0; /* Number of child processes */
2554 struct timeval delay; /* How long to wait inside select() */
2555 struct sockaddr_in6 inaddr6; /* Address for IPv6 */
2556 struct sockaddr_in inaddr4; /* Address for IPv4 */
2557 struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
2558 int opt = 1; /* setsockopt flag */
2559 int rc; /* Result code from system calls */
2560 int iPort = mnPort; /* Port to try to use */
2561 const char *zRequestType; /* Type of requests to listen for */
2562
2563
2564 if( flags & HTTP_SERVER_SCGI ){
2565 zRequestType = "SCGI";
2566 }else if( g.httpUseSSL ){
2567 zRequestType = "TLS-encrypted HTTPS";
2568 }else{
2569 zRequestType = "HTTP";
2570 }
2571
2572 if( flags & HTTP_SERVER_UNIXSOCKET ){
2573 /* CASE 1: A unix socket named g.zSockName. After creation, set the
2574 ** permissions on the new socket to g.zSockMode and make the
2575 ** owner of the socket be g.zSockOwner.
2576 */
2577 assert( g.zSockName!=0 );
2578 memset(&uxaddr, 0, sizeof(uxaddr));
2579 if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2580 fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2581 g.zSockName, (int)sizeof(uxaddr.sun_path));
2582 }
2583 if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2584 if( !file_issocket(g.zSockName) ){
2585 fossil_fatal("cannot name socket \"%s\" because another object"
2586 " with that name already exists", g.zSockName);
2587 }else{
2588 unlink(g.zSockName);
2589 }
2590 }
2591 uxaddr.sun_family = AF_UNIX;
2592 strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2593 listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
2594 if( listen4<0 ){
2595 fossil_fatal("unable to create a unix socket named %s",
2596 g.zSockName);
2597 }
2598 mxListen = listen4;
2599 listen6 = -1;
2600
2601 /* Set the access permission for the new socket. Default to 0660.
2602 ** But use an alternative specified by --socket-mode if available.
2603 ** Do this before bind() to avoid a race condition. */
2604 if( g.zSockMode ){
2605 file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
2606 }else{
2607 file_set_mode(g.zSockName, listen4, "0660", 1);
2608 }
2609 rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2610 /* Set the owner of the socket if requested by --socket-owner. This
2611 ** must wait until after bind(), after the filesystem object has been
2612 ** created. See https://lkml.org/lkml/2004/11/1/84 and
2613 ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2614 if( g.zSockOwner ){
2615 file_set_owner(g.zSockName, listen4, g.zSockOwner);
2616 }
2617 fossil_print("Listening for %s requests on unix socket %s\n",
2618 zRequestType, g.zSockName);
2619 fflush(stdout);
2620 }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
2621 /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
2622 */
2623 assert( mnPort==mxPort );
2624 memset(&inaddr6, 0, sizeof(inaddr6));
2625 inaddr6.sin6_family = AF_INET6;
2626 inaddr6.sin6_port = htons(iPort);
2627 if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
2628 fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2629 }
2630 listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2631 if( listen6>0 ){
2632 opt = 1;
2633 setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2634 rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2635 if( rc<0 ){
2636 close(listen6);
2637 listen6 = -1;
2638 }
2639 }
2640 if( listen6<0 ){
2641 fossil_fatal("cannot open a listening socket on [%s]:%d",
2642 zIpAddr, mnPort);
2643 }
2644 mxListen = listen6;
2645 listen4 = -1;
2646 fossil_print("Listening for %s requests on [%s]:%d\n",
2647 zRequestType, zIpAddr, iPort);
2648 fflush(stdout);
2649 }else if( zIpAddr && zIpAddr[0] ){
2650 /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
2651 */
2652 assert( mnPort==mxPort );
2653 memset(&inaddr4, 0, sizeof(inaddr4));
2654 inaddr4.sin_family = AF_INET;
2655 inaddr4.sin_port = htons(iPort);
2656 if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
2657 inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2658 if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2659 fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2660 }
2661 listen4 = socket(AF_INET, SOCK_STREAM, 0);
2662 if( listen4>0 ){
2663 setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2664 rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2665 if( rc<0 ){
2666 close(listen6);
2667 listen4 = -1;
2668 }
2669 }
2670 if( listen4<0 ){
2671 fossil_fatal("cannot open a listening socket on %s:%d",
2672 zIpAddr, mnPort);
2673 }
2674 mxListen = listen4;
2675 listen6 = -1;
2676 fossil_print("Listening for %s requests on TCP port %s:%d\n",
2677 zRequestType, zIpAddr, iPort);
2678 fflush(stdout);
2679 }else{
2680 /* CASE 4: Listen on all available IP addresses, or on only loopback
2681 ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
2682 ** first available in the range of mnPort..mxPort. Listen
2683 ** on both IPv4 and IPv6, if possible. The TCP port scan is done
2684 ** on IPv4.
2685 */
2686 while( iPort<=mxPort ){
2687 const char *zProto;
2688 memset(&inaddr4, 0, sizeof(inaddr4));
2689 inaddr4.sin_family = AF_INET;
2690 inaddr4.sin_port = htons(iPort);
2691 if( flags & HTTP_SERVER_LOCALHOST ){
2692 inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2693 }else{
2694 inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
2695 }
2696 listen4 = socket(AF_INET, SOCK_STREAM, 0);
2697 if( listen4>0 ){
2698 setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2699 rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2700 if( rc<0 ){
2701 close(listen4);
2702 listen4 = -1;
2703 }
2704 }
2705 if( listen4<0 ){
2706 iPort++;
2707 continue;
2708 }
2709 mxListen = listen4;
2710
2711 /* If we get here, that means we found an open TCP port at iPort for
2712 ** IPv4. Try to set up a corresponding IPv6 socket on the same port.
2713 */
2714 memset(&inaddr6, 0, sizeof(inaddr6));
2715 inaddr6.sin6_family = AF_INET6;
2716 inaddr6.sin6_port = htons(iPort);
2717 if( flags & HTTP_SERVER_LOCALHOST ){
2718 inaddr6.sin6_addr = in6addr_loopback;
2719 }else{
2720 inaddr6.sin6_addr = in6addr_any;
2721 }
2722 listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2723 if( listen6>0 ){
2724 setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2725 setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
2726 rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2727 if( rc<0 ){
2728 close(listen6);
2729 listen6 = -1;
2730 }
2731 }
2732 if( listen6<0 ){
2733 zProto = "IPv4 only";
2734 }else{
2735 zProto = "IPv4 and IPv6";
2736 if( listen6>listen4 ) mxListen = listen6;
2737 }
2738
2739 fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
2740 zRequestType,
2741 (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
2742 iPort, zProto);
2743 fflush(stdout);
2744 break;
2745 }
2746 if( iPort>mxPort ){
2747 fossil_fatal("no available TCP ports in the range %d..%d",
2748 mnPort, mxPort);
2749 }
2750 }
2751
2752 /* If we get to this point, that means there is at least one listening
2753 ** socket on either listen4 or listen6 and perhaps on both. */
2754 assert( listen4>0 || listen6>0 );
2755 if( listen4>0 ) listen(listen4,10);
2756 if( listen6>0 ) listen(listen6,10);
2757 if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
2758 assert( strstr(zBrowser,"%d")!=0 );
2759 zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2760 #if defined(__CYGWIN__)
2761 /* On Cygwin, we can do better than "echo" */
@@ -2716,57 +2769,69 @@
2769 #endif
2770 if( fossil_system(zBrowser)<0 ){
2771 fossil_warning("cannot start browser: %s\n", zBrowser);
2772 }
2773 }
2774
2775 /* What for incomming requests. For each request, fork() a child process
2776 ** to deal with that request. The child process returns. The parent
2777 ** keeps on listening and never returns.
2778 */
2779 while( 1 ){
2780 #if FOSSIL_MAX_CONNECTIONS>0
2781 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2782 if( wait(0)>=0 ) nchildren--;
2783 }
2784 #endif
2785 delay.tv_sec = 0;
2786 delay.tv_usec = 100000;
2787 FD_ZERO(&readfds);
2788 assert( listen4>0 || listen6>0 );
2789 if( listen4>0 ) FD_SET( listen4, &readfds);
2790 if( listen6>0 ) FD_SET( listen6, &readfds);
2791 select( mxListen+1, &readfds, 0, 0, &delay);
2792 if( listen4>0 && FD_ISSET(listen4, &readfds) ){
2793 lenaddr = sizeof(inaddr4);
2794 connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
2795 }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
2796 lenaddr = sizeof(inaddr6);
2797 connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
2798 }else{
2799 connection = -1;
2800 }
2801 if( connection>=0 ){
2802 if( flags & HTTP_SERVER_NOFORK ){
2803 child = 0;
2804 }else{
2805 child = fork();
2806 }
2807 if( child!=0 ){
2808 if( child>0 ){
2809 nchildren++;
2810 nRequest++;
2811 }
2812 close(connection);
2813 }else{
2814 int nErr = 0, fd;
2815 g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2816 close(0);
2817 fd = dup(connection);
2818 if( fd!=0 ) nErr++;
2819 close(1);
2820 fd = dup(connection);
2821 if( fd!=1 ) nErr++;
2822 if( 0 && !g.fAnyTrace ){
2823 close(2);
2824 fd = dup(connection);
2825 if( fd!=2 ) nErr++;
2826 }
2827 close(connection);
2828 if( listen4>0 ) close(listen4);
2829 if( listen6>0 ) close(listen6);
2830 g.nPendingRequest = nchildren+1;
2831 g.nRequest = nRequest+1;
2832 return nErr;
2833 }
2834 }
2835 /* Bury dead children */
2836 if( nchildren ){
2837 while(1){
2838
+1 -1
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
33693369
if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
33703370
if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
33713371
fossil_print("project-id: %s\n", db_get("project-code", 0));
33723372
fossil_print("server-id: %s\n", db_get("server-code", 0));
33733373
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374
- fossil_print("admin-user: %s (initial password is \"%s\")\n",
3374
+ fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
33753375
g.zLogin, zPassword);
33763376
hash_user_password(g.zLogin);
33773377
}
33783378
33793379
/*
33803380
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
3369 if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
3370 if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
3371 fossil_print("project-id: %s\n", db_get("project-code", 0));
3372 fossil_print("server-id: %s\n", db_get("server-code", 0));
3373 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374 fossil_print("admin-user: %s (initial password is \"%s\")\n",
3375 g.zLogin, zPassword);
3376 hash_user_password(g.zLogin);
3377 }
3378
3379 /*
3380
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
3369 if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
3370 if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
3371 fossil_print("project-id: %s\n", db_get("project-code", 0));
3372 fossil_print("server-id: %s\n", db_get("server-code", 0));
3373 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374 fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
3375 g.zLogin, zPassword);
3376 hash_user_password(g.zLogin);
3377 }
3378
3379 /*
3380
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751751
border-left: 1px solid gold;
752752
}
753753
body.cpage-ckout .file-change-line,
754754
body.cpage-info .file-change-line,
755755
body.cpage-vinfo .file-change-line,
756
+body.cpage-ci .file-change-line,
756757
body.cpage-vdiff .file-change-line {
757758
margin-top: 16px;
758759
margin-bottom: 16px;
759760
margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
760761
display: flex;
761762
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vinfo .file-change-line,
 
756 body.cpage-vdiff .file-change-line {
757 margin-top: 16px;
758 margin-bottom: 16px;
759 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
760 display: flex;
761
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vinfo .file-change-line,
756 body.cpage-ci .file-change-line,
757 body.cpage-vdiff .file-change-line {
758 margin-top: 16px;
759 margin-bottom: 16px;
760 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
761 display: flex;
762
+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
+3 -3
--- 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",
@@ -1182,14 +1182,14 @@
11821182
@ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
11831183
@ Side-by-Side&nbsp;Diff</a>
11841184
}
11851185
if( diffType!=0 ){
11861186
if( *zW ){
1187
- @ %z(chref("button","%R/%s/%T",zPage,zName))
1187
+ @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
11881188
@ Show&nbsp;Whitespace&nbsp;Changes</a>
11891189
}else{
1190
- @ %z(chref("button","%R/%s/%T?w",zPage,zName))
1190
+ @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
11911191
@ Ignore&nbsp;Whitespace</a>
11921192
}
11931193
}
11941194
if( zParent ){
11951195
@ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
11961196
--- 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",
@@ -1182,14 +1182,14 @@
1182 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
1183 @ Side-by-Side&nbsp;Diff</a>
1184 }
1185 if( diffType!=0 ){
1186 if( *zW ){
1187 @ %z(chref("button","%R/%s/%T",zPage,zName))
1188 @ Show&nbsp;Whitespace&nbsp;Changes</a>
1189 }else{
1190 @ %z(chref("button","%R/%s/%T?w",zPage,zName))
1191 @ Ignore&nbsp;Whitespace</a>
1192 }
1193 }
1194 if( zParent ){
1195 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1196
--- 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",
@@ -1182,14 +1182,14 @@
1182 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
1183 @ Side-by-Side&nbsp;Diff</a>
1184 }
1185 if( diffType!=0 ){
1186 if( *zW ){
1187 @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
1188 @ Show&nbsp;Whitespace&nbsp;Changes</a>
1189 }else{
1190 @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
1191 @ Ignore&nbsp;Whitespace</a>
1192 }
1193 }
1194 if( zParent ){
1195 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1196
+1 -1
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
14161416
*/
14171417
zIpAddr = PD("REMOTE_ADDR","nil");
14181418
if( ( cgi_is_loopback(zIpAddr)
14191419
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
14201420
&& g.useLocalauth
1421
- && db_get_int("localauth",0)==0
1421
+ && db_get_boolean("localauth",0)==0
14221422
&& P("HTTPS")==0
14231423
){
14241424
char *zSeed;
14251425
if( g.localOpen ) zLogin = db_lget("default-user",0);
14261426
if( zLogin!=0 ){
14271427
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
1416 */
1417 zIpAddr = PD("REMOTE_ADDR","nil");
1418 if( ( cgi_is_loopback(zIpAddr)
1419 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
1420 && g.useLocalauth
1421 && db_get_int("localauth",0)==0
1422 && P("HTTPS")==0
1423 ){
1424 char *zSeed;
1425 if( g.localOpen ) zLogin = db_lget("default-user",0);
1426 if( zLogin!=0 ){
1427
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
1416 */
1417 zIpAddr = PD("REMOTE_ADDR","nil");
1418 if( ( cgi_is_loopback(zIpAddr)
1419 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
1420 && g.useLocalauth
1421 && db_get_boolean("localauth",0)==0
1422 && P("HTTPS")==0
1423 ){
1424 char *zSeed;
1425 if( g.localOpen ) zLogin = db_lget("default-user",0);
1426 if( zLogin!=0 ){
1427
+29 -9
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
37643764
** case=3 Extra db_end_transaction()
37653765
** case=4 Error during SQL processing
37663766
** case=5 Call the segfault handler
37673767
** case=6 Call webpage_assert()
37683768
** case=7 Call webpage_error()
3769
+** case=8 Simulate a timeout
3770
+** case=9 Simulate a TH1 XSS vulnerability
3771
+** case=10 Simulate a TH1 SQL-injection vulnerability
37693772
*/
37703773
void test_warning_page(void){
37713774
int iCase = atoi(PD("case","0"));
37723775
int i;
37733776
login_check_credentials();
@@ -3776,17 +3779,15 @@
37763779
return;
37773780
}
37783781
style_set_current_feature("test");
37793782
style_header("Warning Test Page");
37803783
style_submenu_element("Error Log","%R/errorlog");
3781
- if( iCase<1 || iCase>4 ){
3782
- @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783
- @ by clicking on one of the following cases:
3784
- }else{
3785
- @ <p>This is the test page for case=%d(iCase). All possible cases:
3786
- }
3787
- for(i=1; i<=8; i++){
3784
+ @ <p>This page will generate various kinds of errors to test Fossil's
3785
+ @ reaction. Depending on settings, a message might be written
3786
+ @ into the <a href="%R/errorlog">error log</a>. Click on
3787
+ @ one of the following hyperlinks to generate a simulated error:
3788
+ for(i=1; i<=10; i++){
37883789
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
37893790
}
37903791
@ </p>
37913792
@ <p><ol>
37923793
@ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
38153816
}
38163817
@ <li value='6'> call webpage_assert(0)
38173818
if( iCase==6 ){
38183819
webpage_assert( 5==7 );
38193820
}
3820
- @ <li value='7'> call webpage_error()"
3821
+ @ <li value='7'> call webpage_error()
38213822
if( iCase==7 ){
38223823
cgi_reset_content();
38233824
webpage_error("Case 7 from /test-warning");
38243825
}
3825
- @ <li value='8'> simulated timeout"
3826
+ @ <li value='8'> simulated timeout
38263827
if( iCase==8 ){
38273828
fossil_set_timeout(1);
38283829
cgi_reset_content();
38293830
sqlite3_sleep(1100);
3831
+ }
3832
+ @ <li value='9'> simulated TH1 XSS vulnerability
3833
+ @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834
+ if( iCase==9 || iCase==10 ){
3835
+ const char *zR;
3836
+ int n, rc;
3837
+ static const char *zTH1[] = {
3838
+ /* case 9 */ "html [taint {<b>XSS</b>}]",
3839
+ /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840
+ " html \"<b>[htmlize $msg]</b>\"\n"
3841
+ "}"
3842
+ };
3843
+ rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844
+ zR = Th_GetResult(g.interp, &n);
3845
+ if( rc==TH_OK ){
3846
+ @ <pre class="th1result">%h(zR)</pre>
3847
+ }else{
3848
+ @ <pre class="th1error">%h(zR)</pre>
3849
+ }
38303850
}
38313851
@ </ol>
38323852
@ <p>End of test</p>
38333853
style_finish_page();
38343854
}
38353855
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
 
 
 
3769 */
3770 void test_warning_page(void){
3771 int iCase = atoi(PD("case","0"));
3772 int i;
3773 login_check_credentials();
@@ -3776,17 +3779,15 @@
3776 return;
3777 }
3778 style_set_current_feature("test");
3779 style_header("Warning Test Page");
3780 style_submenu_element("Error Log","%R/errorlog");
3781 if( iCase<1 || iCase>4 ){
3782 @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783 @ by clicking on one of the following cases:
3784 }else{
3785 @ <p>This is the test page for case=%d(iCase). All possible cases:
3786 }
3787 for(i=1; i<=8; i++){
3788 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3789 }
3790 @ </p>
3791 @ <p><ol>
3792 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3815 }
3816 @ <li value='6'> call webpage_assert(0)
3817 if( iCase==6 ){
3818 webpage_assert( 5==7 );
3819 }
3820 @ <li value='7'> call webpage_error()"
3821 if( iCase==7 ){
3822 cgi_reset_content();
3823 webpage_error("Case 7 from /test-warning");
3824 }
3825 @ <li value='8'> simulated timeout"
3826 if( iCase==8 ){
3827 fossil_set_timeout(1);
3828 cgi_reset_content();
3829 sqlite3_sleep(1100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3830 }
3831 @ </ol>
3832 @ <p>End of test</p>
3833 style_finish_page();
3834 }
3835
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
3769 ** case=8 Simulate a timeout
3770 ** case=9 Simulate a TH1 XSS vulnerability
3771 ** case=10 Simulate a TH1 SQL-injection vulnerability
3772 */
3773 void test_warning_page(void){
3774 int iCase = atoi(PD("case","0"));
3775 int i;
3776 login_check_credentials();
@@ -3776,17 +3779,15 @@
3779 return;
3780 }
3781 style_set_current_feature("test");
3782 style_header("Warning Test Page");
3783 style_submenu_element("Error Log","%R/errorlog");
3784 @ <p>This page will generate various kinds of errors to test Fossil's
3785 @ reaction. Depending on settings, a message might be written
3786 @ into the <a href="%R/errorlog">error log</a>. Click on
3787 @ one of the following hyperlinks to generate a simulated error:
3788 for(i=1; i<=10; i++){
 
 
3789 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3790 }
3791 @ </p>
3792 @ <p><ol>
3793 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3816 }
3817 @ <li value='6'> call webpage_assert(0)
3818 if( iCase==6 ){
3819 webpage_assert( 5==7 );
3820 }
3821 @ <li value='7'> call webpage_error()
3822 if( iCase==7 ){
3823 cgi_reset_content();
3824 webpage_error("Case 7 from /test-warning");
3825 }
3826 @ <li value='8'> simulated timeout
3827 if( iCase==8 ){
3828 fossil_set_timeout(1);
3829 cgi_reset_content();
3830 sqlite3_sleep(1100);
3831 }
3832 @ <li value='9'> simulated TH1 XSS vulnerability
3833 @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834 if( iCase==9 || iCase==10 ){
3835 const char *zR;
3836 int n, rc;
3837 static const char *zTH1[] = {
3838 /* case 9 */ "html [taint {<b>XSS</b>}]",
3839 /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840 " html \"<b>[htmlize $msg]</b>\"\n"
3841 "}"
3842 };
3843 rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844 zR = Th_GetResult(g.interp, &n);
3845 if( rc==TH_OK ){
3846 @ <pre class="th1result">%h(zR)</pre>
3847 }else{
3848 @ <pre class="th1error">%h(zR)</pre>
3849 }
3850 }
3851 @ </ol>
3852 @ <p>End of test</p>
3853 style_finish_page();
3854 }
3855
+29 -9
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
37643764
** case=3 Extra db_end_transaction()
37653765
** case=4 Error during SQL processing
37663766
** case=5 Call the segfault handler
37673767
** case=6 Call webpage_assert()
37683768
** case=7 Call webpage_error()
3769
+** case=8 Simulate a timeout
3770
+** case=9 Simulate a TH1 XSS vulnerability
3771
+** case=10 Simulate a TH1 SQL-injection vulnerability
37693772
*/
37703773
void test_warning_page(void){
37713774
int iCase = atoi(PD("case","0"));
37723775
int i;
37733776
login_check_credentials();
@@ -3776,17 +3779,15 @@
37763779
return;
37773780
}
37783781
style_set_current_feature("test");
37793782
style_header("Warning Test Page");
37803783
style_submenu_element("Error Log","%R/errorlog");
3781
- if( iCase<1 || iCase>4 ){
3782
- @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783
- @ by clicking on one of the following cases:
3784
- }else{
3785
- @ <p>This is the test page for case=%d(iCase). All possible cases:
3786
- }
3787
- for(i=1; i<=8; i++){
3784
+ @ <p>This page will generate various kinds of errors to test Fossil's
3785
+ @ reaction. Depending on settings, a message might be written
3786
+ @ into the <a href="%R/errorlog">error log</a>. Click on
3787
+ @ one of the following hyperlinks to generate a simulated error:
3788
+ for(i=1; i<=10; i++){
37883789
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
37893790
}
37903791
@ </p>
37913792
@ <p><ol>
37923793
@ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
38153816
}
38163817
@ <li value='6'> call webpage_assert(0)
38173818
if( iCase==6 ){
38183819
webpage_assert( 5==7 );
38193820
}
3820
- @ <li value='7'> call webpage_error()"
3821
+ @ <li value='7'> call webpage_error()
38213822
if( iCase==7 ){
38223823
cgi_reset_content();
38233824
webpage_error("Case 7 from /test-warning");
38243825
}
3825
- @ <li value='8'> simulated timeout"
3826
+ @ <li value='8'> simulated timeout
38263827
if( iCase==8 ){
38273828
fossil_set_timeout(1);
38283829
cgi_reset_content();
38293830
sqlite3_sleep(1100);
3831
+ }
3832
+ @ <li value='9'> simulated TH1 XSS vulnerability
3833
+ @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834
+ if( iCase==9 || iCase==10 ){
3835
+ const char *zR;
3836
+ int n, rc;
3837
+ static const char *zTH1[] = {
3838
+ /* case 9 */ "html [taint {<b>XSS</b>}]",
3839
+ /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840
+ " html \"<b>[htmlize $msg]</b>\"\n"
3841
+ "}"
3842
+ };
3843
+ rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844
+ zR = Th_GetResult(g.interp, &n);
3845
+ if( rc==TH_OK ){
3846
+ @ <pre class="th1result">%h(zR)</pre>
3847
+ }else{
3848
+ @ <pre class="th1error">%h(zR)</pre>
3849
+ }
38303850
}
38313851
@ </ol>
38323852
@ <p>End of test</p>
38333853
style_finish_page();
38343854
}
38353855
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
 
 
 
3769 */
3770 void test_warning_page(void){
3771 int iCase = atoi(PD("case","0"));
3772 int i;
3773 login_check_credentials();
@@ -3776,17 +3779,15 @@
3776 return;
3777 }
3778 style_set_current_feature("test");
3779 style_header("Warning Test Page");
3780 style_submenu_element("Error Log","%R/errorlog");
3781 if( iCase<1 || iCase>4 ){
3782 @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783 @ by clicking on one of the following cases:
3784 }else{
3785 @ <p>This is the test page for case=%d(iCase). All possible cases:
3786 }
3787 for(i=1; i<=8; i++){
3788 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3789 }
3790 @ </p>
3791 @ <p><ol>
3792 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3815 }
3816 @ <li value='6'> call webpage_assert(0)
3817 if( iCase==6 ){
3818 webpage_assert( 5==7 );
3819 }
3820 @ <li value='7'> call webpage_error()"
3821 if( iCase==7 ){
3822 cgi_reset_content();
3823 webpage_error("Case 7 from /test-warning");
3824 }
3825 @ <li value='8'> simulated timeout"
3826 if( iCase==8 ){
3827 fossil_set_timeout(1);
3828 cgi_reset_content();
3829 sqlite3_sleep(1100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3830 }
3831 @ </ol>
3832 @ <p>End of test</p>
3833 style_finish_page();
3834 }
3835
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
3769 ** case=8 Simulate a timeout
3770 ** case=9 Simulate a TH1 XSS vulnerability
3771 ** case=10 Simulate a TH1 SQL-injection vulnerability
3772 */
3773 void test_warning_page(void){
3774 int iCase = atoi(PD("case","0"));
3775 int i;
3776 login_check_credentials();
@@ -3776,17 +3779,15 @@
3779 return;
3780 }
3781 style_set_current_feature("test");
3782 style_header("Warning Test Page");
3783 style_submenu_element("Error Log","%R/errorlog");
3784 @ <p>This page will generate various kinds of errors to test Fossil's
3785 @ reaction. Depending on settings, a message might be written
3786 @ into the <a href="%R/errorlog">error log</a>. Click on
3787 @ one of the following hyperlinks to generate a simulated error:
3788 for(i=1; i<=10; i++){
 
 
3789 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3790 }
3791 @ </p>
3792 @ <p><ol>
3793 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3816 }
3817 @ <li value='6'> call webpage_assert(0)
3818 if( iCase==6 ){
3819 webpage_assert( 5==7 );
3820 }
3821 @ <li value='7'> call webpage_error()
3822 if( iCase==7 ){
3823 cgi_reset_content();
3824 webpage_error("Case 7 from /test-warning");
3825 }
3826 @ <li value='8'> simulated timeout
3827 if( iCase==8 ){
3828 fossil_set_timeout(1);
3829 cgi_reset_content();
3830 sqlite3_sleep(1100);
3831 }
3832 @ <li value='9'> simulated TH1 XSS vulnerability
3833 @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834 if( iCase==9 || iCase==10 ){
3835 const char *zR;
3836 int n, rc;
3837 static const char *zTH1[] = {
3838 /* case 9 */ "html [taint {<b>XSS</b>}]",
3839 /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840 " html \"<b>[htmlize $msg]</b>\"\n"
3841 "}"
3842 };
3843 rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844 zR = Th_GetResult(g.interp, &n);
3845 if( rc==TH_OK ){
3846 @ <pre class="th1result">%h(zR)</pre>
3847 }else{
3848 @ <pre class="th1error">%h(zR)</pre>
3849 }
3850 }
3851 @ </ol>
3852 @ <p>End of test</p>
3853 style_finish_page();
3854 }
3855
+29 -9
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
37643764
** case=3 Extra db_end_transaction()
37653765
** case=4 Error during SQL processing
37663766
** case=5 Call the segfault handler
37673767
** case=6 Call webpage_assert()
37683768
** case=7 Call webpage_error()
3769
+** case=8 Simulate a timeout
3770
+** case=9 Simulate a TH1 XSS vulnerability
3771
+** case=10 Simulate a TH1 SQL-injection vulnerability
37693772
*/
37703773
void test_warning_page(void){
37713774
int iCase = atoi(PD("case","0"));
37723775
int i;
37733776
login_check_credentials();
@@ -3776,17 +3779,15 @@
37763779
return;
37773780
}
37783781
style_set_current_feature("test");
37793782
style_header("Warning Test Page");
37803783
style_submenu_element("Error Log","%R/errorlog");
3781
- if( iCase<1 || iCase>4 ){
3782
- @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783
- @ by clicking on one of the following cases:
3784
- }else{
3785
- @ <p>This is the test page for case=%d(iCase). All possible cases:
3786
- }
3787
- for(i=1; i<=8; i++){
3784
+ @ <p>This page will generate various kinds of errors to test Fossil's
3785
+ @ reaction. Depending on settings, a message might be written
3786
+ @ into the <a href="%R/errorlog">error log</a>. Click on
3787
+ @ one of the following hyperlinks to generate a simulated error:
3788
+ for(i=1; i<=10; i++){
37883789
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
37893790
}
37903791
@ </p>
37913792
@ <p><ol>
37923793
@ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
38153816
}
38163817
@ <li value='6'> call webpage_assert(0)
38173818
if( iCase==6 ){
38183819
webpage_assert( 5==7 );
38193820
}
3820
- @ <li value='7'> call webpage_error()"
3821
+ @ <li value='7'> call webpage_error()
38213822
if( iCase==7 ){
38223823
cgi_reset_content();
38233824
webpage_error("Case 7 from /test-warning");
38243825
}
3825
- @ <li value='8'> simulated timeout"
3826
+ @ <li value='8'> simulated timeout
38263827
if( iCase==8 ){
38273828
fossil_set_timeout(1);
38283829
cgi_reset_content();
38293830
sqlite3_sleep(1100);
3831
+ }
3832
+ @ <li value='9'> simulated TH1 XSS vulnerability
3833
+ @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834
+ if( iCase==9 || iCase==10 ){
3835
+ const char *zR;
3836
+ int n, rc;
3837
+ static const char *zTH1[] = {
3838
+ /* case 9 */ "html [taint {<b>XSS</b>}]",
3839
+ /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840
+ " html \"<b>[htmlize $msg]</b>\"\n"
3841
+ "}"
3842
+ };
3843
+ rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844
+ zR = Th_GetResult(g.interp, &n);
3845
+ if( rc==TH_OK ){
3846
+ @ <pre class="th1result">%h(zR)</pre>
3847
+ }else{
3848
+ @ <pre class="th1error">%h(zR)</pre>
3849
+ }
38303850
}
38313851
@ </ol>
38323852
@ <p>End of test</p>
38333853
style_finish_page();
38343854
}
38353855
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
 
 
 
3769 */
3770 void test_warning_page(void){
3771 int iCase = atoi(PD("case","0"));
3772 int i;
3773 login_check_credentials();
@@ -3776,17 +3779,15 @@
3776 return;
3777 }
3778 style_set_current_feature("test");
3779 style_header("Warning Test Page");
3780 style_submenu_element("Error Log","%R/errorlog");
3781 if( iCase<1 || iCase>4 ){
3782 @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3783 @ by clicking on one of the following cases:
3784 }else{
3785 @ <p>This is the test page for case=%d(iCase). All possible cases:
3786 }
3787 for(i=1; i<=8; i++){
3788 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3789 }
3790 @ </p>
3791 @ <p><ol>
3792 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3815 }
3816 @ <li value='6'> call webpage_assert(0)
3817 if( iCase==6 ){
3818 webpage_assert( 5==7 );
3819 }
3820 @ <li value='7'> call webpage_error()"
3821 if( iCase==7 ){
3822 cgi_reset_content();
3823 webpage_error("Case 7 from /test-warning");
3824 }
3825 @ <li value='8'> simulated timeout"
3826 if( iCase==8 ){
3827 fossil_set_timeout(1);
3828 cgi_reset_content();
3829 sqlite3_sleep(1100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3830 }
3831 @ </ol>
3832 @ <p>End of test</p>
3833 style_finish_page();
3834 }
3835
--- src/main.c
+++ src/main.c
@@ -3764,10 +3764,13 @@
3764 ** case=3 Extra db_end_transaction()
3765 ** case=4 Error during SQL processing
3766 ** case=5 Call the segfault handler
3767 ** case=6 Call webpage_assert()
3768 ** case=7 Call webpage_error()
3769 ** case=8 Simulate a timeout
3770 ** case=9 Simulate a TH1 XSS vulnerability
3771 ** case=10 Simulate a TH1 SQL-injection vulnerability
3772 */
3773 void test_warning_page(void){
3774 int iCase = atoi(PD("case","0"));
3775 int i;
3776 login_check_credentials();
@@ -3776,17 +3779,15 @@
3779 return;
3780 }
3781 style_set_current_feature("test");
3782 style_header("Warning Test Page");
3783 style_submenu_element("Error Log","%R/errorlog");
3784 @ <p>This page will generate various kinds of errors to test Fossil's
3785 @ reaction. Depending on settings, a message might be written
3786 @ into the <a href="%R/errorlog">error log</a>. Click on
3787 @ one of the following hyperlinks to generate a simulated error:
3788 for(i=1; i<=10; i++){
 
 
3789 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3790 }
3791 @ </p>
3792 @ <p><ol>
3793 @ <li value='1'> Call fossil_warning()
@@ -3815,20 +3816,39 @@
3816 }
3817 @ <li value='6'> call webpage_assert(0)
3818 if( iCase==6 ){
3819 webpage_assert( 5==7 );
3820 }
3821 @ <li value='7'> call webpage_error()
3822 if( iCase==7 ){
3823 cgi_reset_content();
3824 webpage_error("Case 7 from /test-warning");
3825 }
3826 @ <li value='8'> simulated timeout
3827 if( iCase==8 ){
3828 fossil_set_timeout(1);
3829 cgi_reset_content();
3830 sqlite3_sleep(1100);
3831 }
3832 @ <li value='9'> simulated TH1 XSS vulnerability
3833 @ <li value='10'> simulated TH1 SQL-injection vulnerability
3834 if( iCase==9 || iCase==10 ){
3835 const char *zR;
3836 int n, rc;
3837 static const char *zTH1[] = {
3838 /* case 9 */ "html [taint {<b>XSS</b>}]",
3839 /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3840 " html \"<b>[htmlize $msg]</b>\"\n"
3841 "}"
3842 };
3843 rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3844 zR = Th_GetResult(g.interp, &n);
3845 if( rc==TH_OK ){
3846 @ <pre class="th1result">%h(zR)</pre>
3847 }else{
3848 @ <pre class="th1error">%h(zR)</pre>
3849 }
3850 }
3851 @ </ol>
3852 @ <p>End of test</p>
3853 style_finish_page();
3854 }
3855
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696696
){
697697
blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698698
}
699699
blob_append(&bSrc, zSrc, nSrc)
700700
/*have to dup input to ensure a NUL-terminated source string */;
701
- pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
701
+ pikchr_process(blob_str(&bSrc), pikFlags, ob);
702702
blob_reset(&bSrc);
703703
}
704704
705705
/* Invoked for `...` blocks where there are nSep grave accents in a
706706
** row that serve as the delimiter. According to CommonMark:
707707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696696
){
697697
blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698698
}
699699
blob_append(&bSrc, zSrc, nSrc)
700700
/*have to dup input to ensure a NUL-terminated source string */;
701
- pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
701
+ pikchr_process(blob_str(&bSrc), pikFlags, ob);
702702
blob_reset(&bSrc);
703703
}
704704
705705
/* Invoked for `...` blocks where there are nSep grave accents in a
706706
** row that serve as the delimiter. According to CommonMark:
707707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
+89 -182
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
2727
/* The first two must match the values from pikchr.c */
2828
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
2929
#define PIKCHR_PROCESS_DARK_MODE 0x0002
3030
/* end of flags supported directly by pikchr() */
3131
#define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32
-#define PIKCHR_PROCESS_TH1 0x0004
33
-#define PIKCHR_PROCESS_TH1_NOSVG 0x0008
3432
#define PIKCHR_PROCESS_NONCE 0x0010
3533
#define PIKCHR_PROCESS_ERR_PRE 0x0020
3634
#define PIKCHR_PROCESS_SRC 0x0040
3735
#define PIKCHR_PROCESS_DIV 0x0080
3836
#define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
4341
#define PIKCHR_PROCESS_DIV_SOURCE 0x2000
4442
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
4543
#endif
4644
4745
/*
48
-** Processes a pikchr script, optionally with embedded TH1, and
49
-** produces HTML code for it. zIn is the NUL-terminated input
46
+** Processes a pikchr script. zIn is the NUL-terminated input
5047
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51
-** flags documented below. thFlags may be a bitmask of any of the
52
-** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53
-** appending to it without modifying any prior contents.
48
+** flags documented below. Output is sent to pOut,
5449
**
55
-** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56
-** processing failed. In either case, the error message (if any) from
57
-** TH1 or pikchr will be appended to pOut.
50
+** Returns 0 on success, or non-zero if pikchr processing failed.
51
+** In either case, the error message (if any) from pikchr will be
52
+** appended to pOut.
5853
**
5954
** pikFlags flag descriptions:
6055
**
61
-** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62
-** init flags specified in the 3rd argument. If thFlags is non-0 then
63
-** this flag is assumed even if it is not specified.
64
-**
65
-** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66
-** TH1 eval step, thus the output will be (presumably) a
67
-** TH1-generated/processed pikchr script (or whatever else the TH1
68
-** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69
-** if it is not specified.
70
-**
71
-** All of the remaining flags listed below are ignored if
72
-** PIKCHR_PROCESS_TH1_NOSVG is specified!
73
-**
7456
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
7557
** element which specifies a max-width style value based on the SVG's
7658
** calculated size. This flag has multiple mutually exclusive forms:
7759
**
7860
** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
11698
**
11799
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118100
** error report is wrapped in a PRE element, else it is retained
119101
** as-is (intended only for console output).
120102
*/
121
-int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122
- Blob * pOut){
123
- Blob bIn = empty_blob;
103
+int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
124104
int isErr = 0;
105
+ int w = 0, h = 0;
106
+ char *zOut;
125107
const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126108
? safe_html_nonce(1) : 0;
127109
128110
if(!(PIKCHR_PROCESS_DIV & pikFlags)
129111
/* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135117
| PIKCHR_PROCESS_DIV_SOURCE_INLINE
136118
| PIKCHR_PROCESS_DIV_TOGGLE
137119
) & pikFlags){
138120
pikFlags |= PIKCHR_PROCESS_DIV;
139121
}
140
- if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141
- /* If any TH1_xxx flags are set, set TH1 */
142
- && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143
- pikFlags |= PIKCHR_PROCESS_TH1;
144
- }
145
- if(zNonce){
146
- blob_appendf(pOut, "%s\n", zNonce);
147
- }
148
- if(PIKCHR_PROCESS_TH1 & pikFlags){
149
- Blob out = empty_blob;
150
- isErr = Th_RenderToBlob(zIn, &out, thFlags)
151
- ? 1 : 0;
152
- if(isErr){
153
- blob_append(pOut, blob_str(&out), blob_size(&out));
154
- blob_reset(&out);
155
- }else{
156
- bIn = out;
157
- }
158
- }else{
159
- blob_init(&bIn, zIn, -1);
160
- }
161
- if(!isErr){
162
- if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163
- blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164
- }else{
165
- int w = 0, h = 0;
166
- const char * zContent = blob_str(&bIn);
167
- char *zOut;
168
- zOut = pikchr(zContent, "pikchr",
169
- 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170
- &w, &h);
171
- if( w>0 && h>0 ){
172
- const char * zClassToggle = "";
173
- const char * zClassSource = "";
174
- const char * zWrapperClass = "";
175
- if(PIKCHR_PROCESS_DIV & pikFlags){
176
- if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177
- zWrapperClass = " center";
178
- }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179
- zWrapperClass = " indent";
180
- }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181
- zWrapperClass = " float-left";
182
- }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183
- zWrapperClass = " float-right";
184
- }
185
- if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186
- zClassToggle = " toggle";
187
- }
188
- if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189
- if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190
- zClassSource = " source source-inline";
191
- }else{
192
- zClassSource = " source-inline";
193
- }
194
- pikFlags |= PIKCHR_PROCESS_SRC;
195
- }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196
- zClassSource = " source";
197
- pikFlags |= PIKCHR_PROCESS_SRC;
198
- }
199
- blob_appendf(pOut,"<div class='pikchr-wrapper"
200
- "%s%s%s'>"
201
- "<div class=\"pikchr-svg\" "
202
- "style=\"max-width:%dpx\">\n",
203
- zWrapperClass/*safe-for-%s*/,
204
- zClassToggle/*safe-for-%s*/,
205
- zClassSource/*safe-for-%s*/, w);
206
- }
207
- blob_append(pOut, zOut, -1);
208
- if(PIKCHR_PROCESS_DIV & pikFlags){
209
- blob_append(pOut, "</div>\n", 7);
210
- }
211
- if(PIKCHR_PROCESS_SRC & pikFlags){
212
- static int counter = 0;
213
- ++counter;
214
- blob_appendf(pOut, "<div class='pikchr-src'>"
215
- "<pre id='pikchr-src-%d'>%h</pre>"
216
- "<span class='hidden'>"
217
- "<a href='%R/pikchrshow?fromSession' "
218
- "class='pikchr-src-pikchrshow' target='_new-%d' "
219
- "data-pikchrid='pikchr-src-%d' "
220
- "title='Open this pikchr in /pikchrshow'"
221
- ">&rarr; /pikchrshow</a></span>"
222
- "</div>\n",
223
- counter, blob_str(&bIn), counter, counter);
224
- }
225
- if(PIKCHR_PROCESS_DIV & pikFlags){
226
- blob_append(pOut, "</div>\n", 7);
227
- }
228
- }else{
229
- isErr = 2;
230
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231
- blob_append(pOut, "<pre class='error'>\n", 20);
232
- }
233
- blob_appendf(pOut, "%h", zOut);
234
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235
- blob_append(pOut, "\n</pre>\n", 8);
236
- }
237
- }
238
- fossil_free(zOut);
239
- }
240
- }
241
- if(zNonce){
242
- blob_appendf(pOut, "%s\n", zNonce);
243
- }
244
- blob_reset(&bIn);
122
+ if(zNonce){
123
+ blob_appendf(pOut, "%s\n", zNonce);
124
+ }
125
+ zOut = pikchr(zIn, "pikchr",
126
+ 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127
+ &w, &h);
128
+ if( w>0 && h>0 ){
129
+ const char * zClassToggle = "";
130
+ const char * zClassSource = "";
131
+ const char * zWrapperClass = "";
132
+ if(PIKCHR_PROCESS_DIV & pikFlags){
133
+ if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134
+ zWrapperClass = " center";
135
+ }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136
+ zWrapperClass = " indent";
137
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138
+ zWrapperClass = " float-left";
139
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140
+ zWrapperClass = " float-right";
141
+ }
142
+ if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143
+ zClassToggle = " toggle";
144
+ }
145
+ if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146
+ if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147
+ zClassSource = " source source-inline";
148
+ }else{
149
+ zClassSource = " source-inline";
150
+ }
151
+ pikFlags |= PIKCHR_PROCESS_SRC;
152
+ }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153
+ zClassSource = " source";
154
+ pikFlags |= PIKCHR_PROCESS_SRC;
155
+ }
156
+ blob_appendf(pOut,"<div class='pikchr-wrapper"
157
+ "%s%s%s'>"
158
+ "<div class=\"pikchr-svg\" "
159
+ "style=\"max-width:%dpx\">\n",
160
+ zWrapperClass/*safe-for-%s*/,
161
+ zClassToggle/*safe-for-%s*/,
162
+ zClassSource/*safe-for-%s*/, w);
163
+ }
164
+ blob_append(pOut, zOut, -1);
165
+ if(PIKCHR_PROCESS_DIV & pikFlags){
166
+ blob_append(pOut, "</div>\n", 7);
167
+ }
168
+ if(PIKCHR_PROCESS_SRC & pikFlags){
169
+ static int counter = 0;
170
+ ++counter;
171
+ blob_appendf(pOut, "<div class='pikchr-src'>"
172
+ "<pre id='pikchr-src-%d'>%h</pre>"
173
+ "<span class='hidden'>"
174
+ "<a href='%R/pikchrshow?fromSession' "
175
+ "class='pikchr-src-pikchrshow' target='_new-%d' "
176
+ "data-pikchrid='pikchr-src-%d' "
177
+ "title='Open this pikchr in /pikchrshow'"
178
+ ">&rarr; /pikchrshow</a></span>"
179
+ "</div>\n",
180
+ counter, zIn, counter, counter);
181
+ }
182
+ if(PIKCHR_PROCESS_DIV & pikFlags){
183
+ blob_append(pOut, "</div>\n", 7);
184
+ }
185
+ }else{
186
+ isErr = 2;
187
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188
+ blob_append(pOut, "<pre class='error'>\n", 20);
189
+ }
190
+ blob_appendf(pOut, "%h", zOut);
191
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192
+ blob_append(pOut, "\n</pre>\n", 8);
193
+ }
194
+ }
195
+ fossil_free(zOut);
196
+ if(zNonce){
197
+ blob_appendf(pOut, "%s\n", zNonce);
198
+ }
245199
return isErr;
246200
}
247201
248202
/*
249203
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279233
TODO: respond with JSON instead.*/
280234
cgi_set_content_type("text/html");
281235
if(zContent && *zContent){
282236
Blob out = empty_blob;
283237
const int isErr =
284
- pikchr_process(zContent, pikFlags, 0, &out);
238
+ pikchr_process(zContent, pikFlags, &out);
285239
if(isErr){
286240
cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287241
}
288242
CX("%b", &out);
289243
blob_reset(&out);
@@ -384,11 +338,11 @@
384338
/* Reminder: Firefox does not properly flexbox a LEGEND
385339
element, always flowing it in column mode. */);
386340
CX("<div id='pikchrshow-output'>");
387341
if(*zContent){
388342
Blob out = empty_blob;
389
- pikchr_process(zContent, pikFlags, 0, &out);
343
+ pikchr_process(zContent, pikFlags, &out);
390344
CX("%b", &out);
391345
blob_reset(&out);
392346
} CX("</div>"/*#pikchrshow-output*/);
393347
} CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394348
} CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561515
**
562516
** -src Store the input pikchr's source code in the output as
563517
** a separate element adjacent to the SVG one. Implied
564518
** by -div-source.
565519
**
566
-**
567
-** -th Process the input using TH1 before passing it to pikchr
568
-**
569
-** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570
-** pikchr script uses '$' for its own purposes and that
571
-** causes issues. This only affects parsing of '$' outside
572
-** of TH1 script blocks. Code in such blocks is unaffected.
573
-**
574
-** -th-nosvg When using -th, output the post-TH1'd script
575
-** instead of the pikchr-rendered output
576
-**
577
-** -th-trace Trace TH1 execution (for debugging purposes)
578
-**
579520
** -dark Change pikchr colors to assume a dark-mode theme.
580521
**
581522
**
582523
** The -div-indent/center/left/right flags may not be combined.
583
-**
584
-** TH1-related Notes and Caveats:
585
-**
586
-** If the -th flag is used, this command must open a fossil database
587
-** for certain functionality to work (via a check-out or the -R REPO
588
-** flag). If opening a db fails, execution will continue but any TH1
589
-** commands which require a db will trigger a fatal error.
590
-**
591
-** In Fossil skins, TH1 variables in the form $varName are expanded
592
-** as-is and those in the form $<varName> are htmlized in the
593
-** resulting output. This processor disables the htmlizing step, so $x
594
-** and $<x> are equivalent unless the TH1-processed pikchr script
595
-** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596
-** that option will interfere with pikchr output, however, e.g. by
597
-** HTML-encoding double-quotes.
598
-**
599
-** Many of the fossil-installed TH1 functions simply do not make any
600
-** sense for pikchr scripts.
601524
*/
602525
void pikchr_cmd(void){
603526
Blob bIn = empty_blob;
604527
Blob bOut = empty_blob;
605528
const char * zInfile = "-";
606529
const char * zOutfile = "-";
607
- const int fTh1 = find_option("th",0,0)!=0;
608
- const int fNosvg = find_option("th-nosvg",0,0)!=0;
609530
int isErr = 0;
610531
int pikFlags = find_option("src",0,0)!=0
611532
? PIKCHR_PROCESS_SRC : 0;
612
- u32 fThFlags = TH_INIT_NO_ENCODE
613
- | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
-
615
- Th_InitTraceLog()/*processes -th-trace flag*/;
616533
617534
if(find_option("div",0,0)!=0){
618535
pikFlags |= PIKCHR_PROCESS_DIV;
619536
}else if(find_option("div-indent",0,0)!=0){
620537
pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644561
}
645562
if(g.argc>3){
646563
zOutfile = g.argv[3];
647564
}
648565
blob_read_from_file(&bIn, zInfile, ExtFILE);
649
- if(fTh1){
650
- db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651
- /* ^^^ needed for certain TH1 functions to work */;
652
- pikFlags |= PIKCHR_PROCESS_TH1;
653
- if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654
- }
655
- isErr = pikchr_process(blob_str(&bIn), pikFlags,
656
- fTh1 ? fThFlags : 0, &bOut);
566
+ isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
657567
if(isErr){
658
- fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659
- 1==isErr ? ' ' : '\n',
660
- &bOut);
568
+ fossil_fatal("pikchr ERROR: %b", &bOut);
661569
}else{
662570
blob_write_to_file(&bOut, zOutfile);
663571
}
664
- Th_PrintTraceLog();
665572
blob_reset(&bIn);
666573
blob_reset(&bOut);
667574
}
668575
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32 #define PIKCHR_PROCESS_TH1 0x0004
33 #define PIKCHR_PROCESS_TH1_NOSVG 0x0008
34 #define PIKCHR_PROCESS_NONCE 0x0010
35 #define PIKCHR_PROCESS_ERR_PRE 0x0020
36 #define PIKCHR_PROCESS_SRC 0x0040
37 #define PIKCHR_PROCESS_DIV 0x0080
38 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
43 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
44 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
45 #endif
46
47 /*
48 ** Processes a pikchr script, optionally with embedded TH1, and
49 ** produces HTML code for it. zIn is the NUL-terminated input
50 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51 ** flags documented below. thFlags may be a bitmask of any of the
52 ** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53 ** appending to it without modifying any prior contents.
54 **
55 ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56 ** processing failed. In either case, the error message (if any) from
57 ** TH1 or pikchr will be appended to pOut.
58 **
59 ** pikFlags flag descriptions:
60 **
61 ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62 ** init flags specified in the 3rd argument. If thFlags is non-0 then
63 ** this flag is assumed even if it is not specified.
64 **
65 ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66 ** TH1 eval step, thus the output will be (presumably) a
67 ** TH1-generated/processed pikchr script (or whatever else the TH1
68 ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69 ** if it is not specified.
70 **
71 ** All of the remaining flags listed below are ignored if
72 ** PIKCHR_PROCESS_TH1_NOSVG is specified!
73 **
74 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
75 ** element which specifies a max-width style value based on the SVG's
76 ** calculated size. This flag has multiple mutually exclusive forms:
77 **
78 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
116 **
117 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118 ** error report is wrapped in a PRE element, else it is retained
119 ** as-is (intended only for console output).
120 */
121 int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122 Blob * pOut){
123 Blob bIn = empty_blob;
124 int isErr = 0;
 
 
125 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126 ? safe_html_nonce(1) : 0;
127
128 if(!(PIKCHR_PROCESS_DIV & pikFlags)
129 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
136 | PIKCHR_PROCESS_DIV_TOGGLE
137 ) & pikFlags){
138 pikFlags |= PIKCHR_PROCESS_DIV;
139 }
140 if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141 /* If any TH1_xxx flags are set, set TH1 */
142 && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143 pikFlags |= PIKCHR_PROCESS_TH1;
144 }
145 if(zNonce){
146 blob_appendf(pOut, "%s\n", zNonce);
147 }
148 if(PIKCHR_PROCESS_TH1 & pikFlags){
149 Blob out = empty_blob;
150 isErr = Th_RenderToBlob(zIn, &out, thFlags)
151 ? 1 : 0;
152 if(isErr){
153 blob_append(pOut, blob_str(&out), blob_size(&out));
154 blob_reset(&out);
155 }else{
156 bIn = out;
157 }
158 }else{
159 blob_init(&bIn, zIn, -1);
160 }
161 if(!isErr){
162 if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163 blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164 }else{
165 int w = 0, h = 0;
166 const char * zContent = blob_str(&bIn);
167 char *zOut;
168 zOut = pikchr(zContent, "pikchr",
169 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170 &w, &h);
171 if( w>0 && h>0 ){
172 const char * zClassToggle = "";
173 const char * zClassSource = "";
174 const char * zWrapperClass = "";
175 if(PIKCHR_PROCESS_DIV & pikFlags){
176 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177 zWrapperClass = " center";
178 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179 zWrapperClass = " indent";
180 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181 zWrapperClass = " float-left";
182 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183 zWrapperClass = " float-right";
184 }
185 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186 zClassToggle = " toggle";
187 }
188 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190 zClassSource = " source source-inline";
191 }else{
192 zClassSource = " source-inline";
193 }
194 pikFlags |= PIKCHR_PROCESS_SRC;
195 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196 zClassSource = " source";
197 pikFlags |= PIKCHR_PROCESS_SRC;
198 }
199 blob_appendf(pOut,"<div class='pikchr-wrapper"
200 "%s%s%s'>"
201 "<div class=\"pikchr-svg\" "
202 "style=\"max-width:%dpx\">\n",
203 zWrapperClass/*safe-for-%s*/,
204 zClassToggle/*safe-for-%s*/,
205 zClassSource/*safe-for-%s*/, w);
206 }
207 blob_append(pOut, zOut, -1);
208 if(PIKCHR_PROCESS_DIV & pikFlags){
209 blob_append(pOut, "</div>\n", 7);
210 }
211 if(PIKCHR_PROCESS_SRC & pikFlags){
212 static int counter = 0;
213 ++counter;
214 blob_appendf(pOut, "<div class='pikchr-src'>"
215 "<pre id='pikchr-src-%d'>%h</pre>"
216 "<span class='hidden'>"
217 "<a href='%R/pikchrshow?fromSession' "
218 "class='pikchr-src-pikchrshow' target='_new-%d' "
219 "data-pikchrid='pikchr-src-%d' "
220 "title='Open this pikchr in /pikchrshow'"
221 ">&rarr; /pikchrshow</a></span>"
222 "</div>\n",
223 counter, blob_str(&bIn), counter, counter);
224 }
225 if(PIKCHR_PROCESS_DIV & pikFlags){
226 blob_append(pOut, "</div>\n", 7);
227 }
228 }else{
229 isErr = 2;
230 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231 blob_append(pOut, "<pre class='error'>\n", 20);
232 }
233 blob_appendf(pOut, "%h", zOut);
234 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235 blob_append(pOut, "\n</pre>\n", 8);
236 }
237 }
238 fossil_free(zOut);
239 }
240 }
241 if(zNonce){
242 blob_appendf(pOut, "%s\n", zNonce);
243 }
244 blob_reset(&bIn);
245 return isErr;
246 }
247
248 /*
249 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279 TODO: respond with JSON instead.*/
280 cgi_set_content_type("text/html");
281 if(zContent && *zContent){
282 Blob out = empty_blob;
283 const int isErr =
284 pikchr_process(zContent, pikFlags, 0, &out);
285 if(isErr){
286 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287 }
288 CX("%b", &out);
289 blob_reset(&out);
@@ -384,11 +338,11 @@
384 /* Reminder: Firefox does not properly flexbox a LEGEND
385 element, always flowing it in column mode. */);
386 CX("<div id='pikchrshow-output'>");
387 if(*zContent){
388 Blob out = empty_blob;
389 pikchr_process(zContent, pikFlags, 0, &out);
390 CX("%b", &out);
391 blob_reset(&out);
392 } CX("</div>"/*#pikchrshow-output*/);
393 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561 **
562 ** -src Store the input pikchr's source code in the output as
563 ** a separate element adjacent to the SVG one. Implied
564 ** by -div-source.
565 **
566 **
567 ** -th Process the input using TH1 before passing it to pikchr
568 **
569 ** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570 ** pikchr script uses '$' for its own purposes and that
571 ** causes issues. This only affects parsing of '$' outside
572 ** of TH1 script blocks. Code in such blocks is unaffected.
573 **
574 ** -th-nosvg When using -th, output the post-TH1'd script
575 ** instead of the pikchr-rendered output
576 **
577 ** -th-trace Trace TH1 execution (for debugging purposes)
578 **
579 ** -dark Change pikchr colors to assume a dark-mode theme.
580 **
581 **
582 ** The -div-indent/center/left/right flags may not be combined.
583 **
584 ** TH1-related Notes and Caveats:
585 **
586 ** If the -th flag is used, this command must open a fossil database
587 ** for certain functionality to work (via a check-out or the -R REPO
588 ** flag). If opening a db fails, execution will continue but any TH1
589 ** commands which require a db will trigger a fatal error.
590 **
591 ** In Fossil skins, TH1 variables in the form $varName are expanded
592 ** as-is and those in the form $<varName> are htmlized in the
593 ** resulting output. This processor disables the htmlizing step, so $x
594 ** and $<x> are equivalent unless the TH1-processed pikchr script
595 ** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596 ** that option will interfere with pikchr output, however, e.g. by
597 ** HTML-encoding double-quotes.
598 **
599 ** Many of the fossil-installed TH1 functions simply do not make any
600 ** sense for pikchr scripts.
601 */
602 void pikchr_cmd(void){
603 Blob bIn = empty_blob;
604 Blob bOut = empty_blob;
605 const char * zInfile = "-";
606 const char * zOutfile = "-";
607 const int fTh1 = find_option("th",0,0)!=0;
608 const int fNosvg = find_option("th-nosvg",0,0)!=0;
609 int isErr = 0;
610 int pikFlags = find_option("src",0,0)!=0
611 ? PIKCHR_PROCESS_SRC : 0;
612 u32 fThFlags = TH_INIT_NO_ENCODE
613 | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
615 Th_InitTraceLog()/*processes -th-trace flag*/;
616
617 if(find_option("div",0,0)!=0){
618 pikFlags |= PIKCHR_PROCESS_DIV;
619 }else if(find_option("div-indent",0,0)!=0){
620 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644 }
645 if(g.argc>3){
646 zOutfile = g.argv[3];
647 }
648 blob_read_from_file(&bIn, zInfile, ExtFILE);
649 if(fTh1){
650 db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651 /* ^^^ needed for certain TH1 functions to work */;
652 pikFlags |= PIKCHR_PROCESS_TH1;
653 if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654 }
655 isErr = pikchr_process(blob_str(&bIn), pikFlags,
656 fTh1 ? fThFlags : 0, &bOut);
657 if(isErr){
658 fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659 1==isErr ? ' ' : '\n',
660 &bOut);
661 }else{
662 blob_write_to_file(&bOut, zOutfile);
663 }
664 Th_PrintTraceLog();
665 blob_reset(&bIn);
666 blob_reset(&bOut);
667 }
668
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
 
 
32 #define PIKCHR_PROCESS_NONCE 0x0010
33 #define PIKCHR_PROCESS_ERR_PRE 0x0020
34 #define PIKCHR_PROCESS_SRC 0x0040
35 #define PIKCHR_PROCESS_DIV 0x0080
36 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
41 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
42 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
43 #endif
44
45 /*
46 ** Processes a pikchr script. zIn is the NUL-terminated input
 
47 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
48 ** flags documented below. Output is sent to pOut,
 
 
49 **
50 ** Returns 0 on success, or non-zero if pikchr processing failed.
51 ** In either case, the error message (if any) from pikchr will be
52 ** appended to pOut.
53 **
54 ** pikFlags flag descriptions:
55 **
 
 
 
 
 
 
 
 
 
 
 
 
 
56 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
57 ** element which specifies a max-width style value based on the SVG's
58 ** calculated size. This flag has multiple mutually exclusive forms:
59 **
60 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
98 **
99 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
100 ** error report is wrapped in a PRE element, else it is retained
101 ** as-is (intended only for console output).
102 */
103 int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
 
 
104 int isErr = 0;
105 int w = 0, h = 0;
106 char *zOut;
107 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
108 ? safe_html_nonce(1) : 0;
109
110 if(!(PIKCHR_PROCESS_DIV & pikFlags)
111 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
117 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
118 | PIKCHR_PROCESS_DIV_TOGGLE
119 ) & pikFlags){
120 pikFlags |= PIKCHR_PROCESS_DIV;
121 }
122 if(zNonce){
123 blob_appendf(pOut, "%s\n", zNonce);
124 }
125 zOut = pikchr(zIn, "pikchr",
126 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127 &w, &h);
128 if( w>0 && h>0 ){
129 const char * zClassToggle = "";
130 const char * zClassSource = "";
131 const char * zWrapperClass = "";
132 if(PIKCHR_PROCESS_DIV & pikFlags){
133 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134 zWrapperClass = " center";
135 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136 zWrapperClass = " indent";
137 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138 zWrapperClass = " float-left";
139 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140 zWrapperClass = " float-right";
141 }
142 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143 zClassToggle = " toggle";
144 }
145 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147 zClassSource = " source source-inline";
148 }else{
149 zClassSource = " source-inline";
150 }
151 pikFlags |= PIKCHR_PROCESS_SRC;
152 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153 zClassSource = " source";
154 pikFlags |= PIKCHR_PROCESS_SRC;
155 }
156 blob_appendf(pOut,"<div class='pikchr-wrapper"
157 "%s%s%s'>"
158 "<div class=\"pikchr-svg\" "
159 "style=\"max-width:%dpx\">\n",
160 zWrapperClass/*safe-for-%s*/,
161 zClassToggle/*safe-for-%s*/,
162 zClassSource/*safe-for-%s*/, w);
163 }
164 blob_append(pOut, zOut, -1);
165 if(PIKCHR_PROCESS_DIV & pikFlags){
166 blob_append(pOut, "</div>\n", 7);
167 }
168 if(PIKCHR_PROCESS_SRC & pikFlags){
169 static int counter = 0;
170 ++counter;
171 blob_appendf(pOut, "<div class='pikchr-src'>"
172 "<pre id='pikchr-src-%d'>%h</pre>"
173 "<span class='hidden'>"
174 "<a href='%R/pikchrshow?fromSession' "
175 "class='pikchr-src-pikchrshow' target='_new-%d' "
176 "data-pikchrid='pikchr-src-%d' "
177 "title='Open this pikchr in /pikchrshow'"
178 ">&rarr; /pikchrshow</a></span>"
179 "</div>\n",
180 counter, zIn, counter, counter);
181 }
182 if(PIKCHR_PROCESS_DIV & pikFlags){
183 blob_append(pOut, "</div>\n", 7);
184 }
185 }else{
186 isErr = 2;
187 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188 blob_append(pOut, "<pre class='error'>\n", 20);
189 }
190 blob_appendf(pOut, "%h", zOut);
191 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192 blob_append(pOut, "\n</pre>\n", 8);
193 }
194 }
195 fossil_free(zOut);
196 if(zNonce){
197 blob_appendf(pOut, "%s\n", zNonce);
198 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199 return isErr;
200 }
201
202 /*
203 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
233 TODO: respond with JSON instead.*/
234 cgi_set_content_type("text/html");
235 if(zContent && *zContent){
236 Blob out = empty_blob;
237 const int isErr =
238 pikchr_process(zContent, pikFlags, &out);
239 if(isErr){
240 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
241 }
242 CX("%b", &out);
243 blob_reset(&out);
@@ -384,11 +338,11 @@
338 /* Reminder: Firefox does not properly flexbox a LEGEND
339 element, always flowing it in column mode. */);
340 CX("<div id='pikchrshow-output'>");
341 if(*zContent){
342 Blob out = empty_blob;
343 pikchr_process(zContent, pikFlags, &out);
344 CX("%b", &out);
345 blob_reset(&out);
346 } CX("</div>"/*#pikchrshow-output*/);
347 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
348 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
515 **
516 ** -src Store the input pikchr's source code in the output as
517 ** a separate element adjacent to the SVG one. Implied
518 ** by -div-source.
519 **
 
 
 
 
 
 
 
 
 
 
 
 
 
520 ** -dark Change pikchr colors to assume a dark-mode theme.
521 **
522 **
523 ** The -div-indent/center/left/right flags may not be combined.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524 */
525 void pikchr_cmd(void){
526 Blob bIn = empty_blob;
527 Blob bOut = empty_blob;
528 const char * zInfile = "-";
529 const char * zOutfile = "-";
 
 
530 int isErr = 0;
531 int pikFlags = find_option("src",0,0)!=0
532 ? PIKCHR_PROCESS_SRC : 0;
 
 
 
 
533
534 if(find_option("div",0,0)!=0){
535 pikFlags |= PIKCHR_PROCESS_DIV;
536 }else if(find_option("div-indent",0,0)!=0){
537 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
561 }
562 if(g.argc>3){
563 zOutfile = g.argv[3];
564 }
565 blob_read_from_file(&bIn, zInfile, ExtFILE);
566 isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
 
 
 
 
 
 
 
567 if(isErr){
568 fossil_fatal("pikchr ERROR: %b", &bOut);
 
 
569 }else{
570 blob_write_to_file(&bOut, zOutfile);
571 }
 
572 blob_reset(&bIn);
573 blob_reset(&bOut);
574 }
575
+89 -182
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
2727
/* The first two must match the values from pikchr.c */
2828
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
2929
#define PIKCHR_PROCESS_DARK_MODE 0x0002
3030
/* end of flags supported directly by pikchr() */
3131
#define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32
-#define PIKCHR_PROCESS_TH1 0x0004
33
-#define PIKCHR_PROCESS_TH1_NOSVG 0x0008
3432
#define PIKCHR_PROCESS_NONCE 0x0010
3533
#define PIKCHR_PROCESS_ERR_PRE 0x0020
3634
#define PIKCHR_PROCESS_SRC 0x0040
3735
#define PIKCHR_PROCESS_DIV 0x0080
3836
#define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
4341
#define PIKCHR_PROCESS_DIV_SOURCE 0x2000
4442
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
4543
#endif
4644
4745
/*
48
-** Processes a pikchr script, optionally with embedded TH1, and
49
-** produces HTML code for it. zIn is the NUL-terminated input
46
+** Processes a pikchr script. zIn is the NUL-terminated input
5047
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51
-** flags documented below. thFlags may be a bitmask of any of the
52
-** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53
-** appending to it without modifying any prior contents.
48
+** flags documented below. Output is sent to pOut,
5449
**
55
-** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56
-** processing failed. In either case, the error message (if any) from
57
-** TH1 or pikchr will be appended to pOut.
50
+** Returns 0 on success, or non-zero if pikchr processing failed.
51
+** In either case, the error message (if any) from pikchr will be
52
+** appended to pOut.
5853
**
5954
** pikFlags flag descriptions:
6055
**
61
-** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62
-** init flags specified in the 3rd argument. If thFlags is non-0 then
63
-** this flag is assumed even if it is not specified.
64
-**
65
-** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66
-** TH1 eval step, thus the output will be (presumably) a
67
-** TH1-generated/processed pikchr script (or whatever else the TH1
68
-** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69
-** if it is not specified.
70
-**
71
-** All of the remaining flags listed below are ignored if
72
-** PIKCHR_PROCESS_TH1_NOSVG is specified!
73
-**
7456
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
7557
** element which specifies a max-width style value based on the SVG's
7658
** calculated size. This flag has multiple mutually exclusive forms:
7759
**
7860
** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
11698
**
11799
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118100
** error report is wrapped in a PRE element, else it is retained
119101
** as-is (intended only for console output).
120102
*/
121
-int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122
- Blob * pOut){
123
- Blob bIn = empty_blob;
103
+int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
124104
int isErr = 0;
105
+ int w = 0, h = 0;
106
+ char *zOut;
125107
const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126108
? safe_html_nonce(1) : 0;
127109
128110
if(!(PIKCHR_PROCESS_DIV & pikFlags)
129111
/* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135117
| PIKCHR_PROCESS_DIV_SOURCE_INLINE
136118
| PIKCHR_PROCESS_DIV_TOGGLE
137119
) & pikFlags){
138120
pikFlags |= PIKCHR_PROCESS_DIV;
139121
}
140
- if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141
- /* If any TH1_xxx flags are set, set TH1 */
142
- && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143
- pikFlags |= PIKCHR_PROCESS_TH1;
144
- }
145
- if(zNonce){
146
- blob_appendf(pOut, "%s\n", zNonce);
147
- }
148
- if(PIKCHR_PROCESS_TH1 & pikFlags){
149
- Blob out = empty_blob;
150
- isErr = Th_RenderToBlob(zIn, &out, thFlags)
151
- ? 1 : 0;
152
- if(isErr){
153
- blob_append(pOut, blob_str(&out), blob_size(&out));
154
- blob_reset(&out);
155
- }else{
156
- bIn = out;
157
- }
158
- }else{
159
- blob_init(&bIn, zIn, -1);
160
- }
161
- if(!isErr){
162
- if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163
- blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164
- }else{
165
- int w = 0, h = 0;
166
- const char * zContent = blob_str(&bIn);
167
- char *zOut;
168
- zOut = pikchr(zContent, "pikchr",
169
- 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170
- &w, &h);
171
- if( w>0 && h>0 ){
172
- const char * zClassToggle = "";
173
- const char * zClassSource = "";
174
- const char * zWrapperClass = "";
175
- if(PIKCHR_PROCESS_DIV & pikFlags){
176
- if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177
- zWrapperClass = " center";
178
- }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179
- zWrapperClass = " indent";
180
- }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181
- zWrapperClass = " float-left";
182
- }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183
- zWrapperClass = " float-right";
184
- }
185
- if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186
- zClassToggle = " toggle";
187
- }
188
- if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189
- if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190
- zClassSource = " source source-inline";
191
- }else{
192
- zClassSource = " source-inline";
193
- }
194
- pikFlags |= PIKCHR_PROCESS_SRC;
195
- }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196
- zClassSource = " source";
197
- pikFlags |= PIKCHR_PROCESS_SRC;
198
- }
199
- blob_appendf(pOut,"<div class='pikchr-wrapper"
200
- "%s%s%s'>"
201
- "<div class=\"pikchr-svg\" "
202
- "style=\"max-width:%dpx\">\n",
203
- zWrapperClass/*safe-for-%s*/,
204
- zClassToggle/*safe-for-%s*/,
205
- zClassSource/*safe-for-%s*/, w);
206
- }
207
- blob_append(pOut, zOut, -1);
208
- if(PIKCHR_PROCESS_DIV & pikFlags){
209
- blob_append(pOut, "</div>\n", 7);
210
- }
211
- if(PIKCHR_PROCESS_SRC & pikFlags){
212
- static int counter = 0;
213
- ++counter;
214
- blob_appendf(pOut, "<div class='pikchr-src'>"
215
- "<pre id='pikchr-src-%d'>%h</pre>"
216
- "<span class='hidden'>"
217
- "<a href='%R/pikchrshow?fromSession' "
218
- "class='pikchr-src-pikchrshow' target='_new-%d' "
219
- "data-pikchrid='pikchr-src-%d' "
220
- "title='Open this pikchr in /pikchrshow'"
221
- ">&rarr; /pikchrshow</a></span>"
222
- "</div>\n",
223
- counter, blob_str(&bIn), counter, counter);
224
- }
225
- if(PIKCHR_PROCESS_DIV & pikFlags){
226
- blob_append(pOut, "</div>\n", 7);
227
- }
228
- }else{
229
- isErr = 2;
230
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231
- blob_append(pOut, "<pre class='error'>\n", 20);
232
- }
233
- blob_appendf(pOut, "%h", zOut);
234
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235
- blob_append(pOut, "\n</pre>\n", 8);
236
- }
237
- }
238
- fossil_free(zOut);
239
- }
240
- }
241
- if(zNonce){
242
- blob_appendf(pOut, "%s\n", zNonce);
243
- }
244
- blob_reset(&bIn);
122
+ if(zNonce){
123
+ blob_appendf(pOut, "%s\n", zNonce);
124
+ }
125
+ zOut = pikchr(zIn, "pikchr",
126
+ 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127
+ &w, &h);
128
+ if( w>0 && h>0 ){
129
+ const char * zClassToggle = "";
130
+ const char * zClassSource = "";
131
+ const char * zWrapperClass = "";
132
+ if(PIKCHR_PROCESS_DIV & pikFlags){
133
+ if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134
+ zWrapperClass = " center";
135
+ }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136
+ zWrapperClass = " indent";
137
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138
+ zWrapperClass = " float-left";
139
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140
+ zWrapperClass = " float-right";
141
+ }
142
+ if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143
+ zClassToggle = " toggle";
144
+ }
145
+ if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146
+ if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147
+ zClassSource = " source source-inline";
148
+ }else{
149
+ zClassSource = " source-inline";
150
+ }
151
+ pikFlags |= PIKCHR_PROCESS_SRC;
152
+ }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153
+ zClassSource = " source";
154
+ pikFlags |= PIKCHR_PROCESS_SRC;
155
+ }
156
+ blob_appendf(pOut,"<div class='pikchr-wrapper"
157
+ "%s%s%s'>"
158
+ "<div class=\"pikchr-svg\" "
159
+ "style=\"max-width:%dpx\">\n",
160
+ zWrapperClass/*safe-for-%s*/,
161
+ zClassToggle/*safe-for-%s*/,
162
+ zClassSource/*safe-for-%s*/, w);
163
+ }
164
+ blob_append(pOut, zOut, -1);
165
+ if(PIKCHR_PROCESS_DIV & pikFlags){
166
+ blob_append(pOut, "</div>\n", 7);
167
+ }
168
+ if(PIKCHR_PROCESS_SRC & pikFlags){
169
+ static int counter = 0;
170
+ ++counter;
171
+ blob_appendf(pOut, "<div class='pikchr-src'>"
172
+ "<pre id='pikchr-src-%d'>%h</pre>"
173
+ "<span class='hidden'>"
174
+ "<a href='%R/pikchrshow?fromSession' "
175
+ "class='pikchr-src-pikchrshow' target='_new-%d' "
176
+ "data-pikchrid='pikchr-src-%d' "
177
+ "title='Open this pikchr in /pikchrshow'"
178
+ ">&rarr; /pikchrshow</a></span>"
179
+ "</div>\n",
180
+ counter, zIn, counter, counter);
181
+ }
182
+ if(PIKCHR_PROCESS_DIV & pikFlags){
183
+ blob_append(pOut, "</div>\n", 7);
184
+ }
185
+ }else{
186
+ isErr = 2;
187
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188
+ blob_append(pOut, "<pre class='error'>\n", 20);
189
+ }
190
+ blob_appendf(pOut, "%h", zOut);
191
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192
+ blob_append(pOut, "\n</pre>\n", 8);
193
+ }
194
+ }
195
+ fossil_free(zOut);
196
+ if(zNonce){
197
+ blob_appendf(pOut, "%s\n", zNonce);
198
+ }
245199
return isErr;
246200
}
247201
248202
/*
249203
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279233
TODO: respond with JSON instead.*/
280234
cgi_set_content_type("text/html");
281235
if(zContent && *zContent){
282236
Blob out = empty_blob;
283237
const int isErr =
284
- pikchr_process(zContent, pikFlags, 0, &out);
238
+ pikchr_process(zContent, pikFlags, &out);
285239
if(isErr){
286240
cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287241
}
288242
CX("%b", &out);
289243
blob_reset(&out);
@@ -384,11 +338,11 @@
384338
/* Reminder: Firefox does not properly flexbox a LEGEND
385339
element, always flowing it in column mode. */);
386340
CX("<div id='pikchrshow-output'>");
387341
if(*zContent){
388342
Blob out = empty_blob;
389
- pikchr_process(zContent, pikFlags, 0, &out);
343
+ pikchr_process(zContent, pikFlags, &out);
390344
CX("%b", &out);
391345
blob_reset(&out);
392346
} CX("</div>"/*#pikchrshow-output*/);
393347
} CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394348
} CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561515
**
562516
** -src Store the input pikchr's source code in the output as
563517
** a separate element adjacent to the SVG one. Implied
564518
** by -div-source.
565519
**
566
-**
567
-** -th Process the input using TH1 before passing it to pikchr
568
-**
569
-** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570
-** pikchr script uses '$' for its own purposes and that
571
-** causes issues. This only affects parsing of '$' outside
572
-** of TH1 script blocks. Code in such blocks is unaffected.
573
-**
574
-** -th-nosvg When using -th, output the post-TH1'd script
575
-** instead of the pikchr-rendered output
576
-**
577
-** -th-trace Trace TH1 execution (for debugging purposes)
578
-**
579520
** -dark Change pikchr colors to assume a dark-mode theme.
580521
**
581522
**
582523
** The -div-indent/center/left/right flags may not be combined.
583
-**
584
-** TH1-related Notes and Caveats:
585
-**
586
-** If the -th flag is used, this command must open a fossil database
587
-** for certain functionality to work (via a check-out or the -R REPO
588
-** flag). If opening a db fails, execution will continue but any TH1
589
-** commands which require a db will trigger a fatal error.
590
-**
591
-** In Fossil skins, TH1 variables in the form $varName are expanded
592
-** as-is and those in the form $<varName> are htmlized in the
593
-** resulting output. This processor disables the htmlizing step, so $x
594
-** and $<x> are equivalent unless the TH1-processed pikchr script
595
-** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596
-** that option will interfere with pikchr output, however, e.g. by
597
-** HTML-encoding double-quotes.
598
-**
599
-** Many of the fossil-installed TH1 functions simply do not make any
600
-** sense for pikchr scripts.
601524
*/
602525
void pikchr_cmd(void){
603526
Blob bIn = empty_blob;
604527
Blob bOut = empty_blob;
605528
const char * zInfile = "-";
606529
const char * zOutfile = "-";
607
- const int fTh1 = find_option("th",0,0)!=0;
608
- const int fNosvg = find_option("th-nosvg",0,0)!=0;
609530
int isErr = 0;
610531
int pikFlags = find_option("src",0,0)!=0
611532
? PIKCHR_PROCESS_SRC : 0;
612
- u32 fThFlags = TH_INIT_NO_ENCODE
613
- | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
-
615
- Th_InitTraceLog()/*processes -th-trace flag*/;
616533
617534
if(find_option("div",0,0)!=0){
618535
pikFlags |= PIKCHR_PROCESS_DIV;
619536
}else if(find_option("div-indent",0,0)!=0){
620537
pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644561
}
645562
if(g.argc>3){
646563
zOutfile = g.argv[3];
647564
}
648565
blob_read_from_file(&bIn, zInfile, ExtFILE);
649
- if(fTh1){
650
- db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651
- /* ^^^ needed for certain TH1 functions to work */;
652
- pikFlags |= PIKCHR_PROCESS_TH1;
653
- if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654
- }
655
- isErr = pikchr_process(blob_str(&bIn), pikFlags,
656
- fTh1 ? fThFlags : 0, &bOut);
566
+ isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
657567
if(isErr){
658
- fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659
- 1==isErr ? ' ' : '\n',
660
- &bOut);
568
+ fossil_fatal("pikchr ERROR: %b", &bOut);
661569
}else{
662570
blob_write_to_file(&bOut, zOutfile);
663571
}
664
- Th_PrintTraceLog();
665572
blob_reset(&bIn);
666573
blob_reset(&bOut);
667574
}
668575
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32 #define PIKCHR_PROCESS_TH1 0x0004
33 #define PIKCHR_PROCESS_TH1_NOSVG 0x0008
34 #define PIKCHR_PROCESS_NONCE 0x0010
35 #define PIKCHR_PROCESS_ERR_PRE 0x0020
36 #define PIKCHR_PROCESS_SRC 0x0040
37 #define PIKCHR_PROCESS_DIV 0x0080
38 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
43 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
44 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
45 #endif
46
47 /*
48 ** Processes a pikchr script, optionally with embedded TH1, and
49 ** produces HTML code for it. zIn is the NUL-terminated input
50 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51 ** flags documented below. thFlags may be a bitmask of any of the
52 ** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53 ** appending to it without modifying any prior contents.
54 **
55 ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56 ** processing failed. In either case, the error message (if any) from
57 ** TH1 or pikchr will be appended to pOut.
58 **
59 ** pikFlags flag descriptions:
60 **
61 ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62 ** init flags specified in the 3rd argument. If thFlags is non-0 then
63 ** this flag is assumed even if it is not specified.
64 **
65 ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66 ** TH1 eval step, thus the output will be (presumably) a
67 ** TH1-generated/processed pikchr script (or whatever else the TH1
68 ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69 ** if it is not specified.
70 **
71 ** All of the remaining flags listed below are ignored if
72 ** PIKCHR_PROCESS_TH1_NOSVG is specified!
73 **
74 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
75 ** element which specifies a max-width style value based on the SVG's
76 ** calculated size. This flag has multiple mutually exclusive forms:
77 **
78 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
116 **
117 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118 ** error report is wrapped in a PRE element, else it is retained
119 ** as-is (intended only for console output).
120 */
121 int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122 Blob * pOut){
123 Blob bIn = empty_blob;
124 int isErr = 0;
 
 
125 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126 ? safe_html_nonce(1) : 0;
127
128 if(!(PIKCHR_PROCESS_DIV & pikFlags)
129 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
136 | PIKCHR_PROCESS_DIV_TOGGLE
137 ) & pikFlags){
138 pikFlags |= PIKCHR_PROCESS_DIV;
139 }
140 if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141 /* If any TH1_xxx flags are set, set TH1 */
142 && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143 pikFlags |= PIKCHR_PROCESS_TH1;
144 }
145 if(zNonce){
146 blob_appendf(pOut, "%s\n", zNonce);
147 }
148 if(PIKCHR_PROCESS_TH1 & pikFlags){
149 Blob out = empty_blob;
150 isErr = Th_RenderToBlob(zIn, &out, thFlags)
151 ? 1 : 0;
152 if(isErr){
153 blob_append(pOut, blob_str(&out), blob_size(&out));
154 blob_reset(&out);
155 }else{
156 bIn = out;
157 }
158 }else{
159 blob_init(&bIn, zIn, -1);
160 }
161 if(!isErr){
162 if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163 blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164 }else{
165 int w = 0, h = 0;
166 const char * zContent = blob_str(&bIn);
167 char *zOut;
168 zOut = pikchr(zContent, "pikchr",
169 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170 &w, &h);
171 if( w>0 && h>0 ){
172 const char * zClassToggle = "";
173 const char * zClassSource = "";
174 const char * zWrapperClass = "";
175 if(PIKCHR_PROCESS_DIV & pikFlags){
176 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177 zWrapperClass = " center";
178 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179 zWrapperClass = " indent";
180 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181 zWrapperClass = " float-left";
182 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183 zWrapperClass = " float-right";
184 }
185 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186 zClassToggle = " toggle";
187 }
188 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190 zClassSource = " source source-inline";
191 }else{
192 zClassSource = " source-inline";
193 }
194 pikFlags |= PIKCHR_PROCESS_SRC;
195 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196 zClassSource = " source";
197 pikFlags |= PIKCHR_PROCESS_SRC;
198 }
199 blob_appendf(pOut,"<div class='pikchr-wrapper"
200 "%s%s%s'>"
201 "<div class=\"pikchr-svg\" "
202 "style=\"max-width:%dpx\">\n",
203 zWrapperClass/*safe-for-%s*/,
204 zClassToggle/*safe-for-%s*/,
205 zClassSource/*safe-for-%s*/, w);
206 }
207 blob_append(pOut, zOut, -1);
208 if(PIKCHR_PROCESS_DIV & pikFlags){
209 blob_append(pOut, "</div>\n", 7);
210 }
211 if(PIKCHR_PROCESS_SRC & pikFlags){
212 static int counter = 0;
213 ++counter;
214 blob_appendf(pOut, "<div class='pikchr-src'>"
215 "<pre id='pikchr-src-%d'>%h</pre>"
216 "<span class='hidden'>"
217 "<a href='%R/pikchrshow?fromSession' "
218 "class='pikchr-src-pikchrshow' target='_new-%d' "
219 "data-pikchrid='pikchr-src-%d' "
220 "title='Open this pikchr in /pikchrshow'"
221 ">&rarr; /pikchrshow</a></span>"
222 "</div>\n",
223 counter, blob_str(&bIn), counter, counter);
224 }
225 if(PIKCHR_PROCESS_DIV & pikFlags){
226 blob_append(pOut, "</div>\n", 7);
227 }
228 }else{
229 isErr = 2;
230 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231 blob_append(pOut, "<pre class='error'>\n", 20);
232 }
233 blob_appendf(pOut, "%h", zOut);
234 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235 blob_append(pOut, "\n</pre>\n", 8);
236 }
237 }
238 fossil_free(zOut);
239 }
240 }
241 if(zNonce){
242 blob_appendf(pOut, "%s\n", zNonce);
243 }
244 blob_reset(&bIn);
245 return isErr;
246 }
247
248 /*
249 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279 TODO: respond with JSON instead.*/
280 cgi_set_content_type("text/html");
281 if(zContent && *zContent){
282 Blob out = empty_blob;
283 const int isErr =
284 pikchr_process(zContent, pikFlags, 0, &out);
285 if(isErr){
286 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287 }
288 CX("%b", &out);
289 blob_reset(&out);
@@ -384,11 +338,11 @@
384 /* Reminder: Firefox does not properly flexbox a LEGEND
385 element, always flowing it in column mode. */);
386 CX("<div id='pikchrshow-output'>");
387 if(*zContent){
388 Blob out = empty_blob;
389 pikchr_process(zContent, pikFlags, 0, &out);
390 CX("%b", &out);
391 blob_reset(&out);
392 } CX("</div>"/*#pikchrshow-output*/);
393 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561 **
562 ** -src Store the input pikchr's source code in the output as
563 ** a separate element adjacent to the SVG one. Implied
564 ** by -div-source.
565 **
566 **
567 ** -th Process the input using TH1 before passing it to pikchr
568 **
569 ** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570 ** pikchr script uses '$' for its own purposes and that
571 ** causes issues. This only affects parsing of '$' outside
572 ** of TH1 script blocks. Code in such blocks is unaffected.
573 **
574 ** -th-nosvg When using -th, output the post-TH1'd script
575 ** instead of the pikchr-rendered output
576 **
577 ** -th-trace Trace TH1 execution (for debugging purposes)
578 **
579 ** -dark Change pikchr colors to assume a dark-mode theme.
580 **
581 **
582 ** The -div-indent/center/left/right flags may not be combined.
583 **
584 ** TH1-related Notes and Caveats:
585 **
586 ** If the -th flag is used, this command must open a fossil database
587 ** for certain functionality to work (via a check-out or the -R REPO
588 ** flag). If opening a db fails, execution will continue but any TH1
589 ** commands which require a db will trigger a fatal error.
590 **
591 ** In Fossil skins, TH1 variables in the form $varName are expanded
592 ** as-is and those in the form $<varName> are htmlized in the
593 ** resulting output. This processor disables the htmlizing step, so $x
594 ** and $<x> are equivalent unless the TH1-processed pikchr script
595 ** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596 ** that option will interfere with pikchr output, however, e.g. by
597 ** HTML-encoding double-quotes.
598 **
599 ** Many of the fossil-installed TH1 functions simply do not make any
600 ** sense for pikchr scripts.
601 */
602 void pikchr_cmd(void){
603 Blob bIn = empty_blob;
604 Blob bOut = empty_blob;
605 const char * zInfile = "-";
606 const char * zOutfile = "-";
607 const int fTh1 = find_option("th",0,0)!=0;
608 const int fNosvg = find_option("th-nosvg",0,0)!=0;
609 int isErr = 0;
610 int pikFlags = find_option("src",0,0)!=0
611 ? PIKCHR_PROCESS_SRC : 0;
612 u32 fThFlags = TH_INIT_NO_ENCODE
613 | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
615 Th_InitTraceLog()/*processes -th-trace flag*/;
616
617 if(find_option("div",0,0)!=0){
618 pikFlags |= PIKCHR_PROCESS_DIV;
619 }else if(find_option("div-indent",0,0)!=0){
620 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644 }
645 if(g.argc>3){
646 zOutfile = g.argv[3];
647 }
648 blob_read_from_file(&bIn, zInfile, ExtFILE);
649 if(fTh1){
650 db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651 /* ^^^ needed for certain TH1 functions to work */;
652 pikFlags |= PIKCHR_PROCESS_TH1;
653 if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654 }
655 isErr = pikchr_process(blob_str(&bIn), pikFlags,
656 fTh1 ? fThFlags : 0, &bOut);
657 if(isErr){
658 fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659 1==isErr ? ' ' : '\n',
660 &bOut);
661 }else{
662 blob_write_to_file(&bOut, zOutfile);
663 }
664 Th_PrintTraceLog();
665 blob_reset(&bIn);
666 blob_reset(&bOut);
667 }
668
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
 
 
32 #define PIKCHR_PROCESS_NONCE 0x0010
33 #define PIKCHR_PROCESS_ERR_PRE 0x0020
34 #define PIKCHR_PROCESS_SRC 0x0040
35 #define PIKCHR_PROCESS_DIV 0x0080
36 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
41 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
42 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
43 #endif
44
45 /*
46 ** Processes a pikchr script. zIn is the NUL-terminated input
 
47 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
48 ** flags documented below. Output is sent to pOut,
 
 
49 **
50 ** Returns 0 on success, or non-zero if pikchr processing failed.
51 ** In either case, the error message (if any) from pikchr will be
52 ** appended to pOut.
53 **
54 ** pikFlags flag descriptions:
55 **
 
 
 
 
 
 
 
 
 
 
 
 
 
56 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
57 ** element which specifies a max-width style value based on the SVG's
58 ** calculated size. This flag has multiple mutually exclusive forms:
59 **
60 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
98 **
99 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
100 ** error report is wrapped in a PRE element, else it is retained
101 ** as-is (intended only for console output).
102 */
103 int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
 
 
104 int isErr = 0;
105 int w = 0, h = 0;
106 char *zOut;
107 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
108 ? safe_html_nonce(1) : 0;
109
110 if(!(PIKCHR_PROCESS_DIV & pikFlags)
111 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
117 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
118 | PIKCHR_PROCESS_DIV_TOGGLE
119 ) & pikFlags){
120 pikFlags |= PIKCHR_PROCESS_DIV;
121 }
122 if(zNonce){
123 blob_appendf(pOut, "%s\n", zNonce);
124 }
125 zOut = pikchr(zIn, "pikchr",
126 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127 &w, &h);
128 if( w>0 && h>0 ){
129 const char * zClassToggle = "";
130 const char * zClassSource = "";
131 const char * zWrapperClass = "";
132 if(PIKCHR_PROCESS_DIV & pikFlags){
133 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134 zWrapperClass = " center";
135 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136 zWrapperClass = " indent";
137 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138 zWrapperClass = " float-left";
139 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140 zWrapperClass = " float-right";
141 }
142 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143 zClassToggle = " toggle";
144 }
145 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147 zClassSource = " source source-inline";
148 }else{
149 zClassSource = " source-inline";
150 }
151 pikFlags |= PIKCHR_PROCESS_SRC;
152 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153 zClassSource = " source";
154 pikFlags |= PIKCHR_PROCESS_SRC;
155 }
156 blob_appendf(pOut,"<div class='pikchr-wrapper"
157 "%s%s%s'>"
158 "<div class=\"pikchr-svg\" "
159 "style=\"max-width:%dpx\">\n",
160 zWrapperClass/*safe-for-%s*/,
161 zClassToggle/*safe-for-%s*/,
162 zClassSource/*safe-for-%s*/, w);
163 }
164 blob_append(pOut, zOut, -1);
165 if(PIKCHR_PROCESS_DIV & pikFlags){
166 blob_append(pOut, "</div>\n", 7);
167 }
168 if(PIKCHR_PROCESS_SRC & pikFlags){
169 static int counter = 0;
170 ++counter;
171 blob_appendf(pOut, "<div class='pikchr-src'>"
172 "<pre id='pikchr-src-%d'>%h</pre>"
173 "<span class='hidden'>"
174 "<a href='%R/pikchrshow?fromSession' "
175 "class='pikchr-src-pikchrshow' target='_new-%d' "
176 "data-pikchrid='pikchr-src-%d' "
177 "title='Open this pikchr in /pikchrshow'"
178 ">&rarr; /pikchrshow</a></span>"
179 "</div>\n",
180 counter, zIn, counter, counter);
181 }
182 if(PIKCHR_PROCESS_DIV & pikFlags){
183 blob_append(pOut, "</div>\n", 7);
184 }
185 }else{
186 isErr = 2;
187 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188 blob_append(pOut, "<pre class='error'>\n", 20);
189 }
190 blob_appendf(pOut, "%h", zOut);
191 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192 blob_append(pOut, "\n</pre>\n", 8);
193 }
194 }
195 fossil_free(zOut);
196 if(zNonce){
197 blob_appendf(pOut, "%s\n", zNonce);
198 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199 return isErr;
200 }
201
202 /*
203 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
233 TODO: respond with JSON instead.*/
234 cgi_set_content_type("text/html");
235 if(zContent && *zContent){
236 Blob out = empty_blob;
237 const int isErr =
238 pikchr_process(zContent, pikFlags, &out);
239 if(isErr){
240 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
241 }
242 CX("%b", &out);
243 blob_reset(&out);
@@ -384,11 +338,11 @@
338 /* Reminder: Firefox does not properly flexbox a LEGEND
339 element, always flowing it in column mode. */);
340 CX("<div id='pikchrshow-output'>");
341 if(*zContent){
342 Blob out = empty_blob;
343 pikchr_process(zContent, pikFlags, &out);
344 CX("%b", &out);
345 blob_reset(&out);
346 } CX("</div>"/*#pikchrshow-output*/);
347 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
348 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
515 **
516 ** -src Store the input pikchr's source code in the output as
517 ** a separate element adjacent to the SVG one. Implied
518 ** by -div-source.
519 **
 
 
 
 
 
 
 
 
 
 
 
 
 
520 ** -dark Change pikchr colors to assume a dark-mode theme.
521 **
522 **
523 ** The -div-indent/center/left/right flags may not be combined.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524 */
525 void pikchr_cmd(void){
526 Blob bIn = empty_blob;
527 Blob bOut = empty_blob;
528 const char * zInfile = "-";
529 const char * zOutfile = "-";
 
 
530 int isErr = 0;
531 int pikFlags = find_option("src",0,0)!=0
532 ? PIKCHR_PROCESS_SRC : 0;
 
 
 
 
533
534 if(find_option("div",0,0)!=0){
535 pikFlags |= PIKCHR_PROCESS_DIV;
536 }else if(find_option("div-indent",0,0)!=0){
537 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
561 }
562 if(g.argc>3){
563 zOutfile = g.argv[3];
564 }
565 blob_read_from_file(&bIn, zInfile, ExtFILE);
566 isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
 
 
 
 
 
 
 
567 if(isErr){
568 fossil_fatal("pikchr ERROR: %b", &bOut);
 
 
569 }else{
570 blob_write_to_file(&bOut, zOutfile);
571 }
 
572 blob_reset(&bIn);
573 blob_reset(&bOut);
574 }
575
+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
+1 -22
--- src/repolist.c
+++ src/repolist.c
@@ -101,25 +101,10 @@
101101
finish_repo_list:
102102
g.dbIgnoreErrors--;
103103
sqlite3_close(db);
104104
}
105105
106
-/*
107
-** SETTING: show-repolist-desc boolean default=off
108
-**
109
-** If the value of this setting is "1" globally, then the repository-list
110
-** page will show the description of each repository. This setting only
111
-** has effect when it is in the global setting database.
112
-*/
113
-/*
114
-** SETTING: show-repolist-lg boolean default=off
115
-**
116
-** If the value of this setting is "1" globally, then the repository-list
117
-** page will show the login-group for each repository. This setting only
118
-** has effect when it is in the global setting database.
119
-*/
120
-
121106
/*
122107
** Generate a web-page that lists all repositories located under the
123108
** g.zRepositoryName directory and return non-zero.
124109
**
125110
** For the special case when g.zRepositoryName is a non-chroot-jail "/",
@@ -150,17 +135,10 @@
150135
assert( g.db==0 );
151136
zShow = P("FOSSIL_REPOLIST_SHOW");
152137
if( zShow ){
153138
bShowDesc = strstr(zShow,"description")!=0;
154139
bShowLg = strstr(zShow,"login-group")!=0;
155
- }else if( db_open_config(1, 1)
156
- && db_table_exists("configdb", "global_config")
157
- ){
158
- bShowDesc = db_int(bShowDesc, "SELECT value FROM global_config"
159
- " WHERE name='show-repolist-desc'");
160
- bShowLg = db_int(bShowLg, "SELECT value FROM global_config"
161
- " WHERE name='show-repolist-lg'");
162140
}
163141
blob_init(&html, 0, 0);
164142
if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
165143
/* For the special case of the "repository directory" being "/",
166144
** show all of the repositories named in the ~/.fossil database.
@@ -168,10 +146,11 @@
168146
** On unix systems, then entries are of the form "repo:/home/..."
169147
** and on Windows systems they are like on unix, starting with a "/"
170148
** or they can begin with a drive letter: "repo:C:/Users/...". In either
171149
** case, we want returned path to omit any initial "/".
172150
*/
151
+ db_open_config(1, 0);
173152
db_multi_exec(
174153
"CREATE TEMP VIEW sfile AS"
175154
" SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
176155
" WHERE name GLOB 'repo:*'"
177156
);
178157
--- src/repolist.c
+++ src/repolist.c
@@ -101,25 +101,10 @@
101 finish_repo_list:
102 g.dbIgnoreErrors--;
103 sqlite3_close(db);
104 }
105
106 /*
107 ** SETTING: show-repolist-desc boolean default=off
108 **
109 ** If the value of this setting is "1" globally, then the repository-list
110 ** page will show the description of each repository. This setting only
111 ** has effect when it is in the global setting database.
112 */
113 /*
114 ** SETTING: show-repolist-lg boolean default=off
115 **
116 ** If the value of this setting is "1" globally, then the repository-list
117 ** page will show the login-group for each repository. This setting only
118 ** has effect when it is in the global setting database.
119 */
120
121 /*
122 ** Generate a web-page that lists all repositories located under the
123 ** g.zRepositoryName directory and return non-zero.
124 **
125 ** For the special case when g.zRepositoryName is a non-chroot-jail "/",
@@ -150,17 +135,10 @@
150 assert( g.db==0 );
151 zShow = P("FOSSIL_REPOLIST_SHOW");
152 if( zShow ){
153 bShowDesc = strstr(zShow,"description")!=0;
154 bShowLg = strstr(zShow,"login-group")!=0;
155 }else if( db_open_config(1, 1)
156 && db_table_exists("configdb", "global_config")
157 ){
158 bShowDesc = db_int(bShowDesc, "SELECT value FROM global_config"
159 " WHERE name='show-repolist-desc'");
160 bShowLg = db_int(bShowLg, "SELECT value FROM global_config"
161 " WHERE name='show-repolist-lg'");
162 }
163 blob_init(&html, 0, 0);
164 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
165 /* For the special case of the "repository directory" being "/",
166 ** show all of the repositories named in the ~/.fossil database.
@@ -168,10 +146,11 @@
168 ** On unix systems, then entries are of the form "repo:/home/..."
169 ** and on Windows systems they are like on unix, starting with a "/"
170 ** or they can begin with a drive letter: "repo:C:/Users/...". In either
171 ** case, we want returned path to omit any initial "/".
172 */
 
173 db_multi_exec(
174 "CREATE TEMP VIEW sfile AS"
175 " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
176 " WHERE name GLOB 'repo:*'"
177 );
178
--- src/repolist.c
+++ src/repolist.c
@@ -101,25 +101,10 @@
101 finish_repo_list:
102 g.dbIgnoreErrors--;
103 sqlite3_close(db);
104 }
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106 /*
107 ** Generate a web-page that lists all repositories located under the
108 ** g.zRepositoryName directory and return non-zero.
109 **
110 ** For the special case when g.zRepositoryName is a non-chroot-jail "/",
@@ -150,17 +135,10 @@
135 assert( g.db==0 );
136 zShow = P("FOSSIL_REPOLIST_SHOW");
137 if( zShow ){
138 bShowDesc = strstr(zShow,"description")!=0;
139 bShowLg = strstr(zShow,"login-group")!=0;
 
 
 
 
 
 
 
140 }
141 blob_init(&html, 0, 0);
142 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
143 /* For the special case of the "repository directory" being "/",
144 ** show all of the repositories named in the ~/.fossil database.
@@ -168,10 +146,11 @@
146 ** On unix systems, then entries are of the form "repo:/home/..."
147 ** and on Windows systems they are like on unix, starting with a "/"
148 ** or they can begin with a drive letter: "repo:C:/Users/...". In either
149 ** case, we want returned path to omit any initial "/".
150 */
151 db_open_config(1, 0);
152 db_multi_exec(
153 "CREATE TEMP VIEW sfile AS"
154 " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
155 " WHERE name GLOB 'repo:*'"
156 );
157
--- src/security_audit.c
+++ src/security_audit.c
@@ -100,10 +100,11 @@
100100
const char *zReadCap; /* Capabilities of user group "reader" */
101101
const char *zPubPages; /* GLOB pattern for public pages */
102102
const char *zSelfCap; /* Capabilities of self-registered users */
103103
int hasSelfReg = 0; /* True if able to self-register */
104104
const char *zPublicUrl; /* Canonical access URL */
105
+ const char *zVulnReport; /* The vuln-report setting */
105106
Blob cmd;
106107
char *z;
107108
int n, i;
108109
CapabilityString *pCap;
109110
char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
362363
@ <li><p><b>WARNING:</b>
363364
@ The "strict-manifest-syntax" flag is off. This is a security
364365
@ risk. Turn this setting on (its default) to protect the users
365366
@ of this repository.
366367
}
368
+
369
+ zVulnReport = db_get("vuln-report","log");
370
+ if( fossil_strcmp(zVulnReport,"block")!=0
371
+ && fossil_strcmp(zVulnReport,"fatal")!=0
372
+ ){
373
+ @ <li><p><b>WARNING:</b>
374
+ @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
375
+ @ has a value of "%h(zVulnReport)". This disables defenses against
376
+ @ XSS or SQL-injection vulnerabilities caused by coding errors in
377
+ @ custom TH1 scripts. For the best security, change
378
+ @ the value of the vuln-report setting to "block" or "fatal".
379
+ }
367380
368381
/* Obsolete: */
369382
if( hasAnyCap(zAnonCap, "d") ||
370383
hasAnyCap(zDevCap, "d") ||
371384
hasAnyCap(zReadCap, "d") ){
@@ -810,27 +823,28 @@
810823
** WEBPAGE: errorlog
811824
**
812825
** Show the content of the error log. Only the administrator can view
813826
** this page.
814827
**
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
828
+** y=0x001 Show only hack attempts
829
+** y=0x002 Show only panics and assertion faults
830
+** y=0x004 Show hung backoffice processes
831
+** y=0x008 Show POST requests from a different origin
832
+** y=0x010 Show SQLITE_AUTH and similar
833
+** y=0x020 Show SMTP error reports
834
+** y=0x040 Show TH1 vulnerability reports
835
+** y=0x800 Show other uncategorized messages
822836
**
823837
** If y is omitted or is zero, a count of the various message types is
824838
** shown.
825839
*/
826840
void errorlog_page(void){
827841
i64 szFile;
828842
FILE *in;
829843
char *zLog;
830844
const char *zType = P("y");
831
- static const int eAllTypes = 0x7f;
845
+ static const int eAllTypes = 0x87f;
832846
long eType = 0;
833847
int bOutput = 0;
834848
int prevWasTime = 0;
835849
int nHack = 0;
836850
int nPanic = 0;
@@ -837,10 +851,11 @@
837851
int nOther = 0;
838852
int nHang = 0;
839853
int nXPost = 0;
840854
int nAuth = 0;
841855
int nSmtp = 0;
856
+ int nVuln = 0;
842857
char z[10000];
843858
char zTime[10000];
844859
845860
login_check_credentials();
846861
if( !g.perm.Admin ){
@@ -917,10 +932,13 @@
917932
}
918933
if( eType & 0x20 ){
919934
@ <li>SMTP malfunctions
920935
}
921936
if( eType & 0x40 ){
937
+ @ <li>TH1 vulnerabilities
938
+ }
939
+ if( eType & 0x800 ){
922940
@ <li>Other uncategorized messages
923941
}
924942
@ </ul>
925943
}
926944
@ <hr>
@@ -953,12 +971,16 @@
953971
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
954972
){
955973
bOutput = (eType & 0x10)!=0;
956974
nAuth++;
957975
}else
958
- {
976
+ if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
959977
bOutput = (eType & 0x40)!=0;
978
+ nVuln++;
979
+ }else
980
+ {
981
+ bOutput = (eType & 0x800)!=0;
960982
nOther++;
961983
}
962984
if( bOutput ){
963985
@ %h(zTime)\
964986
}
@@ -978,17 +1000,21 @@
9781000
fclose(in);
9791001
if( eType ){
9801002
@ </pre>
9811003
}
9821004
if( eType==0 ){
983
- int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther;
1005
+ int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
9841006
int nTotal = nNonHack + nHack + nXPost;
9851007
@ <p><table border="a" cellspacing="0" cellpadding="5">
9861008
if( nPanic>0 ){
9871009
@ <tr><td align="right">%d(nPanic)</td>
9881010
@ <td><a href="./errorlog?y=2">Panics</a></td>
9891011
}
1012
+ if( nVuln>0 ){
1013
+ @ <tr><td align="right">%d(nVuln)</td>
1014
+ @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1015
+ }
9901016
if( nHack>0 ){
9911017
@ <tr><td align="right">%d(nHack)</td>
9921018
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
9931019
}
9941020
if( nHang>0 ){
@@ -1007,17 +1033,17 @@
10071033
@ <tr><td align="right">%d(nSmtp)</td>
10081034
@ <td><a href="./errorlog?y=32">SMTP faults</a></td>
10091035
}
10101036
if( nOther>0 ){
10111037
@ <tr><td align="right">%d(nOther)</td>
1012
- @ <td><a href="./errorlog?y=64">Other</a></td>
1038
+ @ <td><a href="./errorlog?y=2048">Other</a></td>
10131039
}
10141040
@ <tr><td align="right">%d(nTotal)</td>
10151041
if( nTotal>0 ){
1016
- @ <td><a href="./errorlog?y=255">All Messages</a></td>
1042
+ @ <td><a href="./errorlog?y=4095">All Messages</a></td>
10171043
}else{
10181044
@ <td>All Messages</td>
10191045
}
10201046
@ </table>
10211047
}
10221048
style_finish_page();
10231049
}
10241050
--- src/security_audit.c
+++ src/security_audit.c
@@ -100,10 +100,11 @@
100 const char *zReadCap; /* Capabilities of user group "reader" */
101 const char *zPubPages; /* GLOB pattern for public pages */
102 const char *zSelfCap; /* Capabilities of self-registered users */
103 int hasSelfReg = 0; /* True if able to self-register */
104 const char *zPublicUrl; /* Canonical access URL */
 
105 Blob cmd;
106 char *z;
107 int n, i;
108 CapabilityString *pCap;
109 char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
362 @ <li><p><b>WARNING:</b>
363 @ The "strict-manifest-syntax" flag is off. This is a security
364 @ risk. Turn this setting on (its default) to protect the users
365 @ of this repository.
366 }
 
 
 
 
 
 
 
 
 
 
 
 
367
368 /* Obsolete: */
369 if( hasAnyCap(zAnonCap, "d") ||
370 hasAnyCap(zDevCap, "d") ||
371 hasAnyCap(zReadCap, "d") ){
@@ -810,27 +823,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 +851,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 +932,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 +971,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 +1000,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 +1033,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
@@ -100,10 +100,11 @@
100 const char *zReadCap; /* Capabilities of user group "reader" */
101 const char *zPubPages; /* GLOB pattern for public pages */
102 const char *zSelfCap; /* Capabilities of self-registered users */
103 int hasSelfReg = 0; /* True if able to self-register */
104 const char *zPublicUrl; /* Canonical access URL */
105 const char *zVulnReport; /* The vuln-report setting */
106 Blob cmd;
107 char *z;
108 int n, i;
109 CapabilityString *pCap;
110 char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
363 @ <li><p><b>WARNING:</b>
364 @ The "strict-manifest-syntax" flag is off. This is a security
365 @ risk. Turn this setting on (its default) to protect the users
366 @ of this repository.
367 }
368
369 zVulnReport = db_get("vuln-report","log");
370 if( fossil_strcmp(zVulnReport,"block")!=0
371 && fossil_strcmp(zVulnReport,"fatal")!=0
372 ){
373 @ <li><p><b>WARNING:</b>
374 @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
375 @ has a value of "%h(zVulnReport)". This disables defenses against
376 @ XSS or SQL-injection vulnerabilities caused by coding errors in
377 @ custom TH1 scripts. For the best security, change
378 @ the value of the vuln-report setting to "block" or "fatal".
379 }
380
381 /* Obsolete: */
382 if( hasAnyCap(zAnonCap, "d") ||
383 hasAnyCap(zDevCap, "d") ||
384 hasAnyCap(zReadCap, "d") ){
@@ -810,27 +823,28 @@
823 ** WEBPAGE: errorlog
824 **
825 ** Show the content of the error log. Only the administrator can view
826 ** this page.
827 **
828 ** y=0x001 Show only hack attempts
829 ** y=0x002 Show only panics and assertion faults
830 ** y=0x004 Show hung backoffice processes
831 ** y=0x008 Show POST requests from a different origin
832 ** y=0x010 Show SQLITE_AUTH and similar
833 ** y=0x020 Show SMTP error reports
834 ** y=0x040 Show TH1 vulnerability reports
835 ** y=0x800 Show other uncategorized messages
836 **
837 ** If y is omitted or is zero, a count of the various message types is
838 ** shown.
839 */
840 void errorlog_page(void){
841 i64 szFile;
842 FILE *in;
843 char *zLog;
844 const char *zType = P("y");
845 static const int eAllTypes = 0x87f;
846 long eType = 0;
847 int bOutput = 0;
848 int prevWasTime = 0;
849 int nHack = 0;
850 int nPanic = 0;
@@ -837,10 +851,11 @@
851 int nOther = 0;
852 int nHang = 0;
853 int nXPost = 0;
854 int nAuth = 0;
855 int nSmtp = 0;
856 int nVuln = 0;
857 char z[10000];
858 char zTime[10000];
859
860 login_check_credentials();
861 if( !g.perm.Admin ){
@@ -917,10 +932,13 @@
932 }
933 if( eType & 0x20 ){
934 @ <li>SMTP malfunctions
935 }
936 if( eType & 0x40 ){
937 @ <li>TH1 vulnerabilities
938 }
939 if( eType & 0x800 ){
940 @ <li>Other uncategorized messages
941 }
942 @ </ul>
943 }
944 @ <hr>
@@ -953,12 +971,16 @@
971 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
972 ){
973 bOutput = (eType & 0x10)!=0;
974 nAuth++;
975 }else
976 if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
977 bOutput = (eType & 0x40)!=0;
978 nVuln++;
979 }else
980 {
981 bOutput = (eType & 0x800)!=0;
982 nOther++;
983 }
984 if( bOutput ){
985 @ %h(zTime)\
986 }
@@ -978,17 +1000,21 @@
1000 fclose(in);
1001 if( eType ){
1002 @ </pre>
1003 }
1004 if( eType==0 ){
1005 int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
1006 int nTotal = nNonHack + nHack + nXPost;
1007 @ <p><table border="a" cellspacing="0" cellpadding="5">
1008 if( nPanic>0 ){
1009 @ <tr><td align="right">%d(nPanic)</td>
1010 @ <td><a href="./errorlog?y=2">Panics</a></td>
1011 }
1012 if( nVuln>0 ){
1013 @ <tr><td align="right">%d(nVuln)</td>
1014 @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1015 }
1016 if( nHack>0 ){
1017 @ <tr><td align="right">%d(nHack)</td>
1018 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
1019 }
1020 if( nHang>0 ){
@@ -1007,17 +1033,17 @@
1033 @ <tr><td align="right">%d(nSmtp)</td>
1034 @ <td><a href="./errorlog?y=32">SMTP faults</a></td>
1035 }
1036 if( nOther>0 ){
1037 @ <tr><td align="right">%d(nOther)</td>
1038 @ <td><a href="./errorlog?y=2048">Other</a></td>
1039 }
1040 @ <tr><td align="right">%d(nTotal)</td>
1041 if( nTotal>0 ){
1042 @ <td><a href="./errorlog?y=4095">All Messages</a></td>
1043 }else{
1044 @ <td>All Messages</td>
1045 }
1046 @ </table>
1047 }
1048 style_finish_page();
1049 }
1050
+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
+87 -54
--- 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;
@@ -1777,15 +1794,19 @@
17771794
int hasSpecialChar = 0; /* Whitespace or {}[]'" */
17781795
int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
17791796
int nBrace = 0;
17801797
17811798
output.zBuf = *pzList;
1782
- output.nBuf = *pnList;
1799
+ output.nBuf = TH1_LEN(*pnList);
17831800
output.nBufAlloc = output.nBuf;
1801
+ output.bTaint = 0;
1802
+ TH1_XFER_TAINT(output.bTaint, *pnList);
17841803
17851804
if( nElem<0 ){
17861805
nElem = th_strlen(zElem);
1806
+ }else{
1807
+ nElem = TH1_LEN(nElem);
17871808
}
17881809
if( output.nBuf>0 ){
17891810
thBufferAddChar(interp, &output, ' ');
17901811
}
17911812
@@ -1834,24 +1855,28 @@
18341855
int *pnStr, /* IN/OUT: Current length of *pzStr */
18351856
const char *zElem, /* Data to append */
18361857
int nElem /* Length of nElem */
18371858
){
18381859
char *zNew;
1839
- int nNew;
1860
+ long long int nNew;
1861
+ int nn;
18401862
18411863
if( nElem<0 ){
1842
- nElem = th_strlen(zElem);
1864
+ nn = th_strlen(zElem);
1865
+ }else{
1866
+ nn = TH1_LEN(nElem);
18431867
}
18441868
1845
- nNew = *pnStr + nElem;
1869
+ nNew = TH1_LEN(*pnStr) + nn;
1870
+ TH1_SIZECHECK(nNew);
18461871
zNew = Th_Malloc(interp, nNew);
18471872
th_memcpy(zNew, *pzStr, *pnStr);
1848
- th_memcpy(&zNew[*pnStr], zElem, nElem);
1873
+ th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
18491874
18501875
Th_Free(interp, *pzStr);
18511876
*pzStr = zNew;
1852
- *pnStr = nNew;
1877
+ *pnStr = (int)nNew;
18531878
18541879
return TH_OK;
18551880
}
18561881
18571882
/*
@@ -2106,16 +2131,18 @@
21062131
/* Evaluate left and right arguments, if they exist. */
21072132
if( pExpr->pLeft ){
21082133
rc = exprEval(interp, pExpr->pLeft);
21092134
if( rc==TH_OK ){
21102135
zLeft = Th_TakeResult(interp, &nLeft);
2136
+ nLeft = TH1_LEN(nLeft);
21112137
}
21122138
}
21132139
if( rc==TH_OK && pExpr->pRight ){
21142140
rc = exprEval(interp, pExpr->pRight);
21152141
if( rc==TH_OK ){
21162142
zRight = Th_TakeResult(interp, &nRight);
2143
+ nRight = TH1_LEN(nRight);
21172144
}
21182145
}
21192146
21202147
/* Convert arguments to their required forms. */
21212148
if( rc==TH_OK ){
@@ -2456,10 +2483,12 @@
24562483
int nToken = 0;
24572484
Expr **apToken = 0;
24582485
24592486
if( nExpr<0 ){
24602487
nExpr = th_strlen(zExpr);
2488
+ }else{
2489
+ nExpr = TH1_LEN(nExpr);
24612490
}
24622491
24632492
/* Parse the expression to a list of tokens. */
24642493
rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
24652494
@@ -2567,10 +2596,12 @@
25672596
Th_HashEntry *pRet;
25682597
Th_HashEntry **ppRet;
25692598
25702599
if( nKey<0 ){
25712600
nKey = th_strlen(zKey);
2601
+ }else{
2602
+ nKey = TH1_LEN(nKey);
25722603
}
25732604
25742605
for(i=0; i<nKey; i++){
25752606
iKey = (iKey<<3) ^ iKey ^ zKey[i];
25762607
}
@@ -2800,10 +2831,12 @@
28002831
int base = 10;
28012832
int (*isdigit)(char) = th_isdigit;
28022833
28032834
if( n<0 ){
28042835
n = th_strlen(z);
2836
+ }else{
2837
+ n = TH1_LEN(n);
28052838
}
28062839
28072840
if( n>1 && (z[0]=='-' || z[0]=='+') ){
28082841
i = 1;
28092842
}
@@ -2859,11 +2892,11 @@
28592892
const char *z,
28602893
int n,
28612894
double *pfOut
28622895
){
28632896
if( !sqlite3IsNumber((const char *)z, 0) ){
2864
- Th_ErrorMessage(interp, "expected number, got: \"", z, n);
2897
+ Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
28652898
return TH_ERROR;
28662899
}
28672900
28682901
sqlite3AtoF((const char *)z, pfOut);
28692902
return TH_OK;
28702903
--- 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;
@@ -1777,15 +1794,19 @@
1777 int hasSpecialChar = 0; /* Whitespace or {}[]'" */
1778 int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
1779 int nBrace = 0;
1780
1781 output.zBuf = *pzList;
1782 output.nBuf = *pnList;
1783 output.nBufAlloc = output.nBuf;
 
 
1784
1785 if( nElem<0 ){
1786 nElem = th_strlen(zElem);
 
 
1787 }
1788 if( output.nBuf>0 ){
1789 thBufferAddChar(interp, &output, ' ');
1790 }
1791
@@ -1834,24 +1855,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 /*
@@ -2106,16 +2131,18 @@
2106 /* Evaluate left and right arguments, if they exist. */
2107 if( pExpr->pLeft ){
2108 rc = exprEval(interp, pExpr->pLeft);
2109 if( rc==TH_OK ){
2110 zLeft = Th_TakeResult(interp, &nLeft);
 
2111 }
2112 }
2113 if( rc==TH_OK && pExpr->pRight ){
2114 rc = exprEval(interp, pExpr->pRight);
2115 if( rc==TH_OK ){
2116 zRight = Th_TakeResult(interp, &nRight);
 
2117 }
2118 }
2119
2120 /* Convert arguments to their required forms. */
2121 if( rc==TH_OK ){
@@ -2456,10 +2483,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 +2596,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 +2831,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 +2892,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;
@@ -1777,15 +1794,19 @@
1794 int hasSpecialChar = 0; /* Whitespace or {}[]'" */
1795 int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
1796 int nBrace = 0;
1797
1798 output.zBuf = *pzList;
1799 output.nBuf = TH1_LEN(*pnList);
1800 output.nBufAlloc = output.nBuf;
1801 output.bTaint = 0;
1802 TH1_XFER_TAINT(output.bTaint, *pnList);
1803
1804 if( nElem<0 ){
1805 nElem = th_strlen(zElem);
1806 }else{
1807 nElem = TH1_LEN(nElem);
1808 }
1809 if( output.nBuf>0 ){
1810 thBufferAddChar(interp, &output, ' ');
1811 }
1812
@@ -1834,24 +1855,28 @@
1855 int *pnStr, /* IN/OUT: Current length of *pzStr */
1856 const char *zElem, /* Data to append */
1857 int nElem /* Length of nElem */
1858 ){
1859 char *zNew;
1860 long long int nNew;
1861 int nn;
1862
1863 if( nElem<0 ){
1864 nn = th_strlen(zElem);
1865 }else{
1866 nn = TH1_LEN(nElem);
1867 }
1868
1869 nNew = TH1_LEN(*pnStr) + nn;
1870 TH1_SIZECHECK(nNew);
1871 zNew = Th_Malloc(interp, nNew);
1872 th_memcpy(zNew, *pzStr, *pnStr);
1873 th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
1874
1875 Th_Free(interp, *pzStr);
1876 *pzStr = zNew;
1877 *pnStr = (int)nNew;
1878
1879 return TH_OK;
1880 }
1881
1882 /*
@@ -2106,16 +2131,18 @@
2131 /* Evaluate left and right arguments, if they exist. */
2132 if( pExpr->pLeft ){
2133 rc = exprEval(interp, pExpr->pLeft);
2134 if( rc==TH_OK ){
2135 zLeft = Th_TakeResult(interp, &nLeft);
2136 nLeft = TH1_LEN(nLeft);
2137 }
2138 }
2139 if( rc==TH_OK && pExpr->pRight ){
2140 rc = exprEval(interp, pExpr->pRight);
2141 if( rc==TH_OK ){
2142 zRight = Th_TakeResult(interp, &nRight);
2143 nRight = TH1_LEN(nRight);
2144 }
2145 }
2146
2147 /* Convert arguments to their required forms. */
2148 if( rc==TH_OK ){
@@ -2456,10 +2483,12 @@
2483 int nToken = 0;
2484 Expr **apToken = 0;
2485
2486 if( nExpr<0 ){
2487 nExpr = th_strlen(zExpr);
2488 }else{
2489 nExpr = TH1_LEN(nExpr);
2490 }
2491
2492 /* Parse the expression to a list of tokens. */
2493 rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
2494
@@ -2567,10 +2596,12 @@
2596 Th_HashEntry *pRet;
2597 Th_HashEntry **ppRet;
2598
2599 if( nKey<0 ){
2600 nKey = th_strlen(zKey);
2601 }else{
2602 nKey = TH1_LEN(nKey);
2603 }
2604
2605 for(i=0; i<nKey; i++){
2606 iKey = (iKey<<3) ^ iKey ^ zKey[i];
2607 }
@@ -2800,10 +2831,12 @@
2831 int base = 10;
2832 int (*isdigit)(char) = th_isdigit;
2833
2834 if( n<0 ){
2835 n = th_strlen(z);
2836 }else{
2837 n = TH1_LEN(n);
2838 }
2839
2840 if( n>1 && (z[0]=='-' || z[0]=='+') ){
2841 i = 1;
2842 }
@@ -2859,11 +2892,11 @@
2892 const char *z,
2893 int n,
2894 double *pfOut
2895 ){
2896 if( !sqlite3IsNumber((const char *)z, 0) ){
2897 Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
2898 return TH_ERROR;
2899 }
2900
2901 sqlite3AtoF((const char *)z, pfOut);
2902 return TH_OK;
2903
+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
+92 -53
--- 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
}
@@ -180,20 +180,24 @@
180180
int nVar;
181181
char **azValue = 0;
182182
int *anValue;
183183
int nValue;
184184
int ii, jj;
185
+ int bTaint = 0;
185186
186187
if( argc!=4 ){
187188
return Th_WrongNumArgs(interp, "foreach varlist list script");
188189
}
189190
rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
190191
if( rc ) return rc;
192
+ TH1_XFER_TAINT(bTaint, argl[2]);
191193
rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
192194
for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
193195
for(jj=0; jj<nVar; jj++){
194
- Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
196
+ int x = anValue[ii+jj];
197
+ TH1_XFER_TAINT(x, bTaint);
198
+ Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
195199
}
196200
rc = eval_loopbody(interp, argv[3], argl[3]);
197201
}
198202
if( rc==TH_BREAK ) rc = TH_OK;
199203
Th_Free(interp, azVar);
@@ -215,15 +219,18 @@
215219
int *argl
216220
){
217221
char *zList = 0;
218222
int nList = 0;
219223
int i;
224
+ int bTaint = 0;
220225
221226
for(i=1; i<argc; i++){
227
+ TH1_XFER_TAINT(bTaint,argl[i]);
222228
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
223229
}
224230
231
+ TH1_XFER_TAINT(nList, bTaint);
225232
Th_SetResult(interp, zList, nList);
226233
Th_Free(interp, zList);
227234
228235
return TH_OK;
229236
}
@@ -244,23 +251,27 @@
244251
int *argl
245252
){
246253
char *zList = 0;
247254
int nList = 0;
248255
int i, rc;
256
+ int bTaint = 0;
249257
250258
if( argc<2 ){
251259
return Th_WrongNumArgs(interp, "lappend var ...");
252260
}
253261
rc = Th_GetVar(interp, argv[1], argl[1]);
254262
if( rc==TH_OK ){
255263
zList = Th_TakeResult(interp, &nList);
256264
}
257265
266
+ TH1_XFER_TAINT(bTaint, nList);
258267
for(i=2; i<argc; i++){
268
+ TH1_XFER_TAINT(bTaint, argl[i]);
259269
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
260270
}
261271
272
+ TH1_XFER_TAINT(nList, bTaint);
262273
Th_SetVar(interp, argv[1], argl[1], zList, nList);
263274
Th_SetResult(interp, zList, nList);
264275
Th_Free(interp, zList);
265276
266277
return TH_OK;
@@ -283,23 +294,27 @@
283294
int rc;
284295
285296
char **azElem;
286297
int *anElem;
287298
int nCount;
299
+ int bTaint = 0;
288300
289301
if( argc!=3 ){
290302
return Th_WrongNumArgs(interp, "lindex list index");
291303
}
292304
293305
if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
294306
return TH_ERROR;
295307
}
296308
309
+ TH1_XFER_TAINT(bTaint, argl[1]);
297310
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
298311
if( rc==TH_OK ){
299312
if( iElem<nCount && iElem>=0 ){
300
- Th_SetResult(interp, azElem[iElem], anElem[iElem]);
313
+ int sz = anElem[iElem];
314
+ TH1_XFER_TAINT(sz, bTaint);
315
+ Th_SetResult(interp, azElem[iElem], sz);
301316
}else{
302317
Th_SetResult(interp, 0, 0);
303318
}
304319
Th_Free(interp, azElem);
305320
}
@@ -356,13 +371,14 @@
356371
return Th_WrongNumArgs(interp, "lsearch list string");
357372
}
358373
359374
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
360375
if( rc==TH_OK ){
376
+ int nn = TH1_LEN(argl[2]);
361377
Th_SetResultInt(interp, -1);
362378
for(i=0; i<nCount; i++){
363
- if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
379
+ if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
364380
Th_SetResultInt(interp, i);
365381
break;
366382
}
367383
}
368384
Th_Free(interp, azElem);
@@ -561,28 +577,31 @@
561577
int nUsage = 0; /* Number of bytes at zUsage */
562578
563579
if( argc!=4 ){
564580
return Th_WrongNumArgs(interp, "proc name arglist code");
565581
}
566
- if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
582
+ if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
583
+ &azParam, &anParam, &nParam) ){
567584
return TH_ERROR;
568585
}
569586
570587
/* Allocate the new ProcDefn structure. */
571588
nByte = sizeof(ProcDefn) + /* ProcDefn structure */
572589
(sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
573590
(sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
574
- argl[3] + /* zProgram */
575
- argl[2]; /* Space for copies of parameter names and default values */
591
+ TH1_LEN(argl[3]) + /* zProgram */
592
+ TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
576593
p = (ProcDefn *)Th_Malloc(interp, nByte);
577594
578595
/* If the last parameter in the parameter list is "args", then set the
579596
** ProcDefn.hasArgs flag. The "args" parameter does not require an
580597
** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
581598
*/
582599
if( nParam>0 ){
583
- if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
600
+ if( TH1_LEN(anParam[nParam-1])==4
601
+ && 0==memcmp(azParam[nParam-1], "args", 4)
602
+ ){
584603
p->hasArgs = 1;
585604
nParam--;
586605
}
587606
}
588607
@@ -590,12 +609,12 @@
590609
p->azParam = (char **)&p[1];
591610
p->anParam = (int *)&p->azParam[nParam];
592611
p->azDefault = (char **)&p->anParam[nParam];
593612
p->anDefault = (int *)&p->azDefault[nParam];
594613
p->zProgram = (char *)&p->anDefault[nParam];
595
- memcpy(p->zProgram, argv[3], argl[3]);
596
- p->nProgram = argl[3];
614
+ memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
615
+ p->nProgram = TH1_LEN(argl[3]);
597616
zSpace = &p->zProgram[p->nProgram];
598617
599618
for(i=0; i<nParam; i++){
600619
char **az;
601620
int *an;
@@ -672,11 +691,12 @@
672691
int *argl
673692
){
674693
if( argc!=3 ){
675694
return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
676695
}
677
- return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
696
+ return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
697
+ argv[2], TH1_LEN(argl[2]));
678698
}
679699
680700
/*
681701
** TH Syntax:
682702
**
@@ -746,13 +766,13 @@
746766
if( argc!=4 ){
747767
return Th_WrongNumArgs(interp, "string compare str1 str2");
748768
}
749769
750770
zLeft = argv[2];
751
- nLeft = argl[2];
771
+ nLeft = TH1_LEN(argl[2]);
752772
zRight = argv[3];
753
- nRight = argl[3];
773
+ nRight = TH1_LEN(argl[3]);
754774
755775
for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
756776
iRes = zLeft[i]-zRight[i];
757777
}
758778
if( iRes==0 ){
@@ -779,12 +799,12 @@
779799
780800
if( argc!=4 ){
781801
return Th_WrongNumArgs(interp, "string first needle haystack");
782802
}
783803
784
- nNeedle = argl[2];
785
- nHaystack = argl[3];
804
+ nNeedle = TH1_LEN(argl[2]);
805
+ nHaystack = TH1_LEN(argl[3]);
786806
787807
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
788808
const char *zNeedle = argv[2];
789809
const char *zHaystack = argv[3];
790810
int i;
@@ -812,20 +832,22 @@
812832
813833
if( argc!=4 ){
814834
return Th_WrongNumArgs(interp, "string index string index");
815835
}
816836
817
- if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
818
- iIndex = argl[2]-1;
837
+ if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
838
+ iIndex = TH1_LEN(argl[2])-1;
819839
}else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
820840
Th_ErrorMessage(
821841
interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
822842
return TH_ERROR;
823843
}
824844
825
- if( iIndex>=0 && iIndex<argl[2] ){
826
- return Th_SetResult(interp, &argv[2][iIndex], 1);
845
+ if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
846
+ int sz = 1;
847
+ TH1_XFER_TAINT(sz, argl[2]);
848
+ return Th_SetResult(interp, &argv[2][iIndex], sz);
827849
}else{
828850
return Th_SetResult(interp, 0, 0);
829851
}
830852
}
831853
@@ -838,41 +860,44 @@
838860
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
839861
){
840862
if( argc!=4 ){
841863
return Th_WrongNumArgs(interp, "string is class string");
842864
}
843
- if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
865
+ if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
844866
int i;
845867
int iRes = 1;
846868
847
- for(i=0; i<argl[3]; i++){
869
+ for(i=0; i<TH1_LEN(argl[3]); i++){
848870
if( !th_isalnum(argv[3][i]) ){
849871
iRes = 0;
850872
}
851873
}
852874
853875
return Th_SetResultInt(interp, iRes);
854
- }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
876
+ }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
855877
double fVal;
856878
if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
857879
return Th_SetResultInt(interp, 1);
858880
}
859881
return Th_SetResultInt(interp, 0);
860
- }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
882
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
861883
int iVal;
862884
if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
863885
return Th_SetResultInt(interp, 1);
864886
}
865887
return Th_SetResultInt(interp, 0);
866
- }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
888
+ }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
867889
if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
868890
return Th_SetResultInt(interp, 1);
869891
}
870892
return Th_SetResultInt(interp, 0);
893
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
894
+ return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
871895
}else{
872896
Th_ErrorMessage(interp,
873
- "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
897
+ "Expected alnum, double, integer, list, or tainted, got:",
898
+ argv[2], TH1_LEN(argl[2]));
874899
return TH_ERROR;
875900
}
876901
}
877902
878903
/*
@@ -889,12 +914,12 @@
889914
890915
if( argc!=4 ){
891916
return Th_WrongNumArgs(interp, "string last needle haystack");
892917
}
893918
894
- nNeedle = argl[2];
895
- nHaystack = argl[3];
919
+ nNeedle = TH1_LEN(argl[2]);
920
+ nHaystack = TH1_LEN(argl[3]);
896921
897922
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
898923
const char *zNeedle = argv[2];
899924
const char *zHaystack = argv[3];
900925
int i;
@@ -919,11 +944,11 @@
919944
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
920945
){
921946
if( argc!=3 ){
922947
return Th_WrongNumArgs(interp, "string length string");
923948
}
924
- return Th_SetResultInt(interp, argl[2]);
949
+ return Th_SetResultInt(interp, TH1_LEN(argl[2]));
925950
}
926951
927952
/*
928953
** TH Syntax:
929954
**
@@ -938,12 +963,12 @@
938963
char *zPat, *zStr;
939964
int rc;
940965
if( argc!=4 ){
941966
return Th_WrongNumArgs(interp, "string match pattern string");
942967
}
943
- zPat = fossil_strndup(argv[2],argl[2]);
944
- zStr = fossil_strndup(argv[3],argl[3]);
968
+ zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
969
+ zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
945970
rc = sqlite3_strglob(zPat,zStr);
946971
fossil_free(zPat);
947972
fossil_free(zStr);
948973
return Th_SetResultInt(interp, !rc);
949974
}
@@ -956,31 +981,34 @@
956981
static int string_range_command(
957982
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
958983
){
959984
int iStart;
960985
int iEnd;
986
+ int sz;
961987
962988
if( argc!=5 ){
963989
return Th_WrongNumArgs(interp, "string range string first last");
964990
}
965991
966
- if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
967
- iEnd = argl[2];
992
+ if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
993
+ iEnd = TH1_LEN(argl[2]);
968994
}else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
969995
Th_ErrorMessage(
970
- interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
996
+ interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
971997
return TH_ERROR;
972998
}
973999
if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
9741000
return TH_ERROR;
9751001
}
9761002
9771003
if( iStart<0 ) iStart = 0;
978
- if( iEnd>=argl[2] ) iEnd = argl[2]-1;
1004
+ if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
9791005
if( iStart>iEnd ) iEnd = iStart-1;
1006
+ sz = iEnd - iStart + 1;
1007
+ TH1_XFER_TAINT(sz, argl[2]);
9801008
981
- return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
1009
+ return Th_SetResult(interp, &argv[2][iStart], sz);
9821010
}
9831011
9841012
/*
9851013
** TH Syntax:
9861014
**
@@ -989,27 +1017,33 @@
9891017
static int string_repeat_command(
9901018
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
9911019
){
9921020
int n;
9931021
int i;
994
- int nByte;
1022
+ int sz;
1023
+ long long int nByte;
9951024
char *zByte;
9961025
9971026
if( argc!=4 ){
9981027
return Th_WrongNumArgs(interp, "string repeat string n");
9991028
}
10001029
if( Th_ToInt(interp, argv[3], argl[3], &n) ){
10011030
return TH_ERROR;
10021031
}
10031032
1004
- nByte = argl[2] * n;
1033
+ nByte = n;
1034
+ sz = TH1_LEN(argl[2]);
1035
+ nByte *= sz;
1036
+ TH1_SIZECHECK(nByte+1);
10051037
zByte = Th_Malloc(interp, nByte+1);
1006
- for(i=0; i<nByte; i+=argl[2]){
1007
- memcpy(&zByte[i], argv[2], argl[2]);
1038
+ for(i=0; i<nByte; i+=sz){
1039
+ memcpy(&zByte[i], argv[2], sz);
10081040
}
10091041
1010
- Th_SetResult(interp, zByte, nByte);
1042
+ n = nByte;
1043
+ TH1_XFER_TAINT(n, argl[2]);
1044
+ Th_SetResult(interp, zByte, n);
10111045
Th_Free(interp, zByte);
10121046
return TH_OK;
10131047
}
10141048
10151049
/*
@@ -1027,17 +1061,18 @@
10271061
10281062
if( argc!=3 ){
10291063
return Th_WrongNumArgs(interp, "string trim string");
10301064
}
10311065
z = argv[2];
1032
- n = argl[2];
1033
- if( argl[1]<5 || argv[1][4]=='l' ){
1066
+ n = TH1_LEN(argl[2]);
1067
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
10341068
while( n && th_isspace(z[0]) ){ z++; n--; }
10351069
}
1036
- if( argl[1]<5 || argv[1][4]=='r' ){
1070
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
10371071
while( n && th_isspace(z[n-1]) ){ n--; }
10381072
}
1073
+ TH1_XFER_TAINT(n, argl[2]);
10391074
Th_SetResult(interp, z, n);
10401075
return TH_OK;
10411076
}
10421077
10431078
/*
@@ -1051,11 +1086,11 @@
10511086
int rc;
10521087
10531088
if( argc!=3 ){
10541089
return Th_WrongNumArgs(interp, "info exists var");
10551090
}
1056
- rc = Th_ExistsVar(interp, argv[2], argl[2]);
1091
+ rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
10571092
Th_SetResultInt(interp, rc);
10581093
return TH_OK;
10591094
}
10601095
10611096
/*
@@ -1117,11 +1152,11 @@
11171152
int rc;
11181153
11191154
if( argc!=3 ){
11201155
return Th_WrongNumArgs(interp, "array exists var");
11211156
}
1122
- rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
1157
+ rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
11231158
Th_SetResultInt(interp, rc);
11241159
return TH_OK;
11251160
}
11261161
11271162
/*
@@ -1137,11 +1172,11 @@
11371172
int nElem = 0;
11381173
11391174
if( argc!=3 ){
11401175
return Th_WrongNumArgs(interp, "array names varname");
11411176
}
1142
- rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
1177
+ rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
11431178
if( rc!=TH_OK ){
11441179
return rc;
11451180
}
11461181
Th_SetResult(interp, zElem, nElem);
11471182
if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1196,11 @@
11611196
int *argl
11621197
){
11631198
if( argc!=2 ){
11641199
return Th_WrongNumArgs(interp, "unset var");
11651200
}
1166
- return Th_UnsetVar(interp, argv[1], argl[1]);
1201
+ return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
11671202
}
11681203
11691204
int Th_CallSubCommand(
11701205
Th_Interp *interp,
11711206
void *ctx,
@@ -1176,19 +1211,22 @@
11761211
){
11771212
if( argc>1 ){
11781213
int i;
11791214
for(i=0; aSub[i].zName; i++){
11801215
const char *zName = aSub[i].zName;
1181
- if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
1216
+ if( th_strlen(zName)==TH1_LEN(argl[1])
1217
+ && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
11821218
return aSub[i].xProc(interp, ctx, argc, argv, argl);
11831219
}
11841220
}
11851221
}
11861222
if(argc<2){
1187
- Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
1223
+ Th_ErrorMessage(interp, "Expected sub-command for",
1224
+ argv[0], TH1_LEN(argl[0]));
11881225
}else{
1189
- Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
1226
+ Th_ErrorMessage(interp, "Expected sub-command, got:",
1227
+ argv[1], TH1_LEN(argl[1]));
11901228
}
11911229
return TH_ERROR;
11921230
}
11931231
11941232
/*
@@ -1319,11 +1357,11 @@
13191357
int iFrame = -1;
13201358
13211359
if( argc!=2 && argc!=3 ){
13221360
return Th_WrongNumArgs(interp, "uplevel ?level? script...");
13231361
}
1324
- if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
1362
+ if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13251363
return TH_ERROR;
13261364
}
13271365
return Th_Eval(interp, iFrame, argv[argc-1], -1);
13281366
}
13291367
@@ -1342,19 +1380,20 @@
13421380
int iVar = 1;
13431381
int iFrame = -1;
13441382
int rc = TH_OK;
13451383
int i;
13461384
1347
- if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
1385
+ if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13481386
iVar++;
13491387
}
13501388
if( argc==iVar || (argc-iVar)%2 ){
13511389
return Th_WrongNumArgs(interp,
13521390
"upvar frame othervar myvar ?othervar myvar...?");
13531391
}
13541392
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]);
1393
+ rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1394
+ iFrame, argv[i], TH1_LEN(argl[i]));
13561395
}
13571396
return rc;
13581397
}
13591398
13601399
/*
13611400
--- 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 }
@@ -180,20 +180,24 @@
180 int nVar;
181 char **azValue = 0;
182 int *anValue;
183 int nValue;
184 int ii, jj;
 
185
186 if( argc!=4 ){
187 return Th_WrongNumArgs(interp, "foreach varlist list script");
188 }
189 rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
190 if( rc ) return rc;
 
191 rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
192 for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
193 for(jj=0; jj<nVar; jj++){
194 Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
 
 
195 }
196 rc = eval_loopbody(interp, argv[3], argl[3]);
197 }
198 if( rc==TH_BREAK ) rc = TH_OK;
199 Th_Free(interp, azVar);
@@ -215,15 +219,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,23 +251,27 @@
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 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;
@@ -283,23 +294,27 @@
283 int rc;
284
285 char **azElem;
286 int *anElem;
287 int nCount;
 
288
289 if( argc!=3 ){
290 return Th_WrongNumArgs(interp, "lindex list index");
291 }
292
293 if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
294 return TH_ERROR;
295 }
296
 
297 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
298 if( rc==TH_OK ){
299 if( iElem<nCount && iElem>=0 ){
300 Th_SetResult(interp, azElem[iElem], anElem[iElem]);
 
 
301 }else{
302 Th_SetResult(interp, 0, 0);
303 }
304 Th_Free(interp, azElem);
305 }
@@ -356,13 +371,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,28 +577,31 @@
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.
581 */
582 if( nParam>0 ){
583 if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
 
 
584 p->hasArgs = 1;
585 nParam--;
586 }
587 }
588
@@ -590,12 +609,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 +691,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 +766,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 +799,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,20 +832,22 @@
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 }
831
@@ -838,41 +860,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 +914,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 +944,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 +963,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 }
@@ -956,31 +981,34 @@
956 static int string_range_command(
957 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
958 ){
959 int iStart;
960 int iEnd;
 
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
984 /*
985 ** TH Syntax:
986 **
@@ -989,27 +1017,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,17 +1061,18 @@
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 }
1042
1043 /*
@@ -1051,11 +1086,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 +1152,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 +1172,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 +1196,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 +1211,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 +1357,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 +1380,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 }
@@ -180,20 +180,24 @@
180 int nVar;
181 char **azValue = 0;
182 int *anValue;
183 int nValue;
184 int ii, jj;
185 int bTaint = 0;
186
187 if( argc!=4 ){
188 return Th_WrongNumArgs(interp, "foreach varlist list script");
189 }
190 rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
191 if( rc ) return rc;
192 TH1_XFER_TAINT(bTaint, argl[2]);
193 rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
194 for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
195 for(jj=0; jj<nVar; jj++){
196 int x = anValue[ii+jj];
197 TH1_XFER_TAINT(x, bTaint);
198 Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
199 }
200 rc = eval_loopbody(interp, argv[3], argl[3]);
201 }
202 if( rc==TH_BREAK ) rc = TH_OK;
203 Th_Free(interp, azVar);
@@ -215,15 +219,18 @@
219 int *argl
220 ){
221 char *zList = 0;
222 int nList = 0;
223 int i;
224 int bTaint = 0;
225
226 for(i=1; i<argc; i++){
227 TH1_XFER_TAINT(bTaint,argl[i]);
228 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
229 }
230
231 TH1_XFER_TAINT(nList, bTaint);
232 Th_SetResult(interp, zList, nList);
233 Th_Free(interp, zList);
234
235 return TH_OK;
236 }
@@ -244,23 +251,27 @@
251 int *argl
252 ){
253 char *zList = 0;
254 int nList = 0;
255 int i, rc;
256 int bTaint = 0;
257
258 if( argc<2 ){
259 return Th_WrongNumArgs(interp, "lappend var ...");
260 }
261 rc = Th_GetVar(interp, argv[1], argl[1]);
262 if( rc==TH_OK ){
263 zList = Th_TakeResult(interp, &nList);
264 }
265
266 TH1_XFER_TAINT(bTaint, nList);
267 for(i=2; i<argc; i++){
268 TH1_XFER_TAINT(bTaint, argl[i]);
269 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
270 }
271
272 TH1_XFER_TAINT(nList, bTaint);
273 Th_SetVar(interp, argv[1], argl[1], zList, nList);
274 Th_SetResult(interp, zList, nList);
275 Th_Free(interp, zList);
276
277 return TH_OK;
@@ -283,23 +294,27 @@
294 int rc;
295
296 char **azElem;
297 int *anElem;
298 int nCount;
299 int bTaint = 0;
300
301 if( argc!=3 ){
302 return Th_WrongNumArgs(interp, "lindex list index");
303 }
304
305 if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
306 return TH_ERROR;
307 }
308
309 TH1_XFER_TAINT(bTaint, argl[1]);
310 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
311 if( rc==TH_OK ){
312 if( iElem<nCount && iElem>=0 ){
313 int sz = anElem[iElem];
314 TH1_XFER_TAINT(sz, bTaint);
315 Th_SetResult(interp, azElem[iElem], sz);
316 }else{
317 Th_SetResult(interp, 0, 0);
318 }
319 Th_Free(interp, azElem);
320 }
@@ -356,13 +371,14 @@
371 return Th_WrongNumArgs(interp, "lsearch list string");
372 }
373
374 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
375 if( rc==TH_OK ){
376 int nn = TH1_LEN(argl[2]);
377 Th_SetResultInt(interp, -1);
378 for(i=0; i<nCount; i++){
379 if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
380 Th_SetResultInt(interp, i);
381 break;
382 }
383 }
384 Th_Free(interp, azElem);
@@ -561,28 +577,31 @@
577 int nUsage = 0; /* Number of bytes at zUsage */
578
579 if( argc!=4 ){
580 return Th_WrongNumArgs(interp, "proc name arglist code");
581 }
582 if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
583 &azParam, &anParam, &nParam) ){
584 return TH_ERROR;
585 }
586
587 /* Allocate the new ProcDefn structure. */
588 nByte = sizeof(ProcDefn) + /* ProcDefn structure */
589 (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
590 (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
591 TH1_LEN(argl[3]) + /* zProgram */
592 TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
593 p = (ProcDefn *)Th_Malloc(interp, nByte);
594
595 /* If the last parameter in the parameter list is "args", then set the
596 ** ProcDefn.hasArgs flag. The "args" parameter does not require an
597 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
598 */
599 if( nParam>0 ){
600 if( TH1_LEN(anParam[nParam-1])==4
601 && 0==memcmp(azParam[nParam-1], "args", 4)
602 ){
603 p->hasArgs = 1;
604 nParam--;
605 }
606 }
607
@@ -590,12 +609,12 @@
609 p->azParam = (char **)&p[1];
610 p->anParam = (int *)&p->azParam[nParam];
611 p->azDefault = (char **)&p->anParam[nParam];
612 p->anDefault = (int *)&p->azDefault[nParam];
613 p->zProgram = (char *)&p->anDefault[nParam];
614 memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
615 p->nProgram = TH1_LEN(argl[3]);
616 zSpace = &p->zProgram[p->nProgram];
617
618 for(i=0; i<nParam; i++){
619 char **az;
620 int *an;
@@ -672,11 +691,12 @@
691 int *argl
692 ){
693 if( argc!=3 ){
694 return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
695 }
696 return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
697 argv[2], TH1_LEN(argl[2]));
698 }
699
700 /*
701 ** TH Syntax:
702 **
@@ -746,13 +766,13 @@
766 if( argc!=4 ){
767 return Th_WrongNumArgs(interp, "string compare str1 str2");
768 }
769
770 zLeft = argv[2];
771 nLeft = TH1_LEN(argl[2]);
772 zRight = argv[3];
773 nRight = TH1_LEN(argl[3]);
774
775 for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
776 iRes = zLeft[i]-zRight[i];
777 }
778 if( iRes==0 ){
@@ -779,12 +799,12 @@
799
800 if( argc!=4 ){
801 return Th_WrongNumArgs(interp, "string first needle haystack");
802 }
803
804 nNeedle = TH1_LEN(argl[2]);
805 nHaystack = TH1_LEN(argl[3]);
806
807 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
808 const char *zNeedle = argv[2];
809 const char *zHaystack = argv[3];
810 int i;
@@ -812,20 +832,22 @@
832
833 if( argc!=4 ){
834 return Th_WrongNumArgs(interp, "string index string index");
835 }
836
837 if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
838 iIndex = TH1_LEN(argl[2])-1;
839 }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
840 Th_ErrorMessage(
841 interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
842 return TH_ERROR;
843 }
844
845 if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
846 int sz = 1;
847 TH1_XFER_TAINT(sz, argl[2]);
848 return Th_SetResult(interp, &argv[2][iIndex], sz);
849 }else{
850 return Th_SetResult(interp, 0, 0);
851 }
852 }
853
@@ -838,41 +860,44 @@
860 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
861 ){
862 if( argc!=4 ){
863 return Th_WrongNumArgs(interp, "string is class string");
864 }
865 if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
866 int i;
867 int iRes = 1;
868
869 for(i=0; i<TH1_LEN(argl[3]); i++){
870 if( !th_isalnum(argv[3][i]) ){
871 iRes = 0;
872 }
873 }
874
875 return Th_SetResultInt(interp, iRes);
876 }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
877 double fVal;
878 if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
879 return Th_SetResultInt(interp, 1);
880 }
881 return Th_SetResultInt(interp, 0);
882 }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
883 int iVal;
884 if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
885 return Th_SetResultInt(interp, 1);
886 }
887 return Th_SetResultInt(interp, 0);
888 }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
889 if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
890 return Th_SetResultInt(interp, 1);
891 }
892 return Th_SetResultInt(interp, 0);
893 }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
894 return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
895 }else{
896 Th_ErrorMessage(interp,
897 "Expected alnum, double, integer, list, or tainted, got:",
898 argv[2], TH1_LEN(argl[2]));
899 return TH_ERROR;
900 }
901 }
902
903 /*
@@ -889,12 +914,12 @@
914
915 if( argc!=4 ){
916 return Th_WrongNumArgs(interp, "string last needle haystack");
917 }
918
919 nNeedle = TH1_LEN(argl[2]);
920 nHaystack = TH1_LEN(argl[3]);
921
922 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
923 const char *zNeedle = argv[2];
924 const char *zHaystack = argv[3];
925 int i;
@@ -919,11 +944,11 @@
944 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
945 ){
946 if( argc!=3 ){
947 return Th_WrongNumArgs(interp, "string length string");
948 }
949 return Th_SetResultInt(interp, TH1_LEN(argl[2]));
950 }
951
952 /*
953 ** TH Syntax:
954 **
@@ -938,12 +963,12 @@
963 char *zPat, *zStr;
964 int rc;
965 if( argc!=4 ){
966 return Th_WrongNumArgs(interp, "string match pattern string");
967 }
968 zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
969 zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
970 rc = sqlite3_strglob(zPat,zStr);
971 fossil_free(zPat);
972 fossil_free(zStr);
973 return Th_SetResultInt(interp, !rc);
974 }
@@ -956,31 +981,34 @@
981 static int string_range_command(
982 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
983 ){
984 int iStart;
985 int iEnd;
986 int sz;
987
988 if( argc!=5 ){
989 return Th_WrongNumArgs(interp, "string range string first last");
990 }
991
992 if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
993 iEnd = TH1_LEN(argl[2]);
994 }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
995 Th_ErrorMessage(
996 interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
997 return TH_ERROR;
998 }
999 if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
1000 return TH_ERROR;
1001 }
1002
1003 if( iStart<0 ) iStart = 0;
1004 if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
1005 if( iStart>iEnd ) iEnd = iStart-1;
1006 sz = iEnd - iStart + 1;
1007 TH1_XFER_TAINT(sz, argl[2]);
1008
1009 return Th_SetResult(interp, &argv[2][iStart], sz);
1010 }
1011
1012 /*
1013 ** TH Syntax:
1014 **
@@ -989,27 +1017,33 @@
1017 static int string_repeat_command(
1018 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
1019 ){
1020 int n;
1021 int i;
1022 int sz;
1023 long long int nByte;
1024 char *zByte;
1025
1026 if( argc!=4 ){
1027 return Th_WrongNumArgs(interp, "string repeat string n");
1028 }
1029 if( Th_ToInt(interp, argv[3], argl[3], &n) ){
1030 return TH_ERROR;
1031 }
1032
1033 nByte = n;
1034 sz = TH1_LEN(argl[2]);
1035 nByte *= sz;
1036 TH1_SIZECHECK(nByte+1);
1037 zByte = Th_Malloc(interp, nByte+1);
1038 for(i=0; i<nByte; i+=sz){
1039 memcpy(&zByte[i], argv[2], sz);
1040 }
1041
1042 n = nByte;
1043 TH1_XFER_TAINT(n, argl[2]);
1044 Th_SetResult(interp, zByte, n);
1045 Th_Free(interp, zByte);
1046 return TH_OK;
1047 }
1048
1049 /*
@@ -1027,17 +1061,18 @@
1061
1062 if( argc!=3 ){
1063 return Th_WrongNumArgs(interp, "string trim string");
1064 }
1065 z = argv[2];
1066 n = TH1_LEN(argl[2]);
1067 if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
1068 while( n && th_isspace(z[0]) ){ z++; n--; }
1069 }
1070 if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
1071 while( n && th_isspace(z[n-1]) ){ n--; }
1072 }
1073 TH1_XFER_TAINT(n, argl[2]);
1074 Th_SetResult(interp, z, n);
1075 return TH_OK;
1076 }
1077
1078 /*
@@ -1051,11 +1086,11 @@
1086 int rc;
1087
1088 if( argc!=3 ){
1089 return Th_WrongNumArgs(interp, "info exists var");
1090 }
1091 rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
1092 Th_SetResultInt(interp, rc);
1093 return TH_OK;
1094 }
1095
1096 /*
@@ -1117,11 +1152,11 @@
1152 int rc;
1153
1154 if( argc!=3 ){
1155 return Th_WrongNumArgs(interp, "array exists var");
1156 }
1157 rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
1158 Th_SetResultInt(interp, rc);
1159 return TH_OK;
1160 }
1161
1162 /*
@@ -1137,11 +1172,11 @@
1172 int nElem = 0;
1173
1174 if( argc!=3 ){
1175 return Th_WrongNumArgs(interp, "array names varname");
1176 }
1177 rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
1178 if( rc!=TH_OK ){
1179 return rc;
1180 }
1181 Th_SetResult(interp, zElem, nElem);
1182 if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1196,11 @@
1196 int *argl
1197 ){
1198 if( argc!=2 ){
1199 return Th_WrongNumArgs(interp, "unset var");
1200 }
1201 return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
1202 }
1203
1204 int Th_CallSubCommand(
1205 Th_Interp *interp,
1206 void *ctx,
@@ -1176,19 +1211,22 @@
1211 ){
1212 if( argc>1 ){
1213 int i;
1214 for(i=0; aSub[i].zName; i++){
1215 const char *zName = aSub[i].zName;
1216 if( th_strlen(zName)==TH1_LEN(argl[1])
1217 && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
1218 return aSub[i].xProc(interp, ctx, argc, argv, argl);
1219 }
1220 }
1221 }
1222 if(argc<2){
1223 Th_ErrorMessage(interp, "Expected sub-command for",
1224 argv[0], TH1_LEN(argl[0]));
1225 }else{
1226 Th_ErrorMessage(interp, "Expected sub-command, got:",
1227 argv[1], TH1_LEN(argl[1]));
1228 }
1229 return TH_ERROR;
1230 }
1231
1232 /*
@@ -1319,11 +1357,11 @@
1357 int iFrame = -1;
1358
1359 if( argc!=2 && argc!=3 ){
1360 return Th_WrongNumArgs(interp, "uplevel ?level? script...");
1361 }
1362 if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1363 return TH_ERROR;
1364 }
1365 return Th_Eval(interp, iFrame, argv[argc-1], -1);
1366 }
1367
@@ -1342,19 +1380,20 @@
1380 int iVar = 1;
1381 int iFrame = -1;
1382 int rc = TH_OK;
1383 int i;
1384
1385 if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1386 iVar++;
1387 }
1388 if( argc==iVar || (argc-iVar)%2 ){
1389 return Th_WrongNumArgs(interp,
1390 "upvar frame othervar myvar ?othervar myvar...?");
1391 }
1392 for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1393 rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1394 iFrame, argv[i], TH1_LEN(argl[i]));
1395 }
1396 return rc;
1397 }
1398
1399 /*
1400
+209 -89
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
3131
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
3232
#define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
3333
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
3434
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
3535
#define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36
-#define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37
- /* output. */
38
-#define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
36
+#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
3937
4038
/*
4139
** Useful and/or "well-known" combinations of flag values.
4240
*/
4341
#define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
262260
){
263261
char *zOut;
264262
if( argc!=2 ){
265263
return Th_WrongNumArgs(interp, "httpize STRING");
266264
}
267
- zOut = httpize((char*)argv[1], argl[1]);
265
+ zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
268266
Th_SetResult(interp, zOut, -1);
269267
free(zOut);
270268
return TH_OK;
271269
}
272270
@@ -291,51 +289,12 @@
291289
if( argc<2 || argc>3 ){
292290
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293291
}
294292
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295293
if( g.thTrace ){
296
- Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
297
- }
298
- return rc;
299
-}
300
-
301
-/*
302
-** TH1 command: enable_htmlify ?BOOLEAN?
303
-**
304
-** Enable or disable the HTML escaping done by all output which
305
-** originates from TH1 (via sendText()).
306
-**
307
-** If passed no arguments it instead returns 0 or 1 to indicate the
308
-** current state.
309
-*/
310
-static int enableHtmlifyCmd(
311
- Th_Interp *interp,
312
- void *p,
313
- int argc,
314
- const char **argv,
315
- int *argl
316
-){
317
- int rc = 0, buul;
318
- if( argc>3 ){
319
- return Th_WrongNumArgs(interp,
320
- "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321
- }
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;
333
- }else{
334
- g.th1Flags |= TH_INIT_NO_ENCODE;
335
- }
336
- }
294
+ Th_Trace("enable_output {%.*s} -> %d<br>\n",
295
+ TH1_LEN(argl[1]),argv[1],enableOutput);
337296
}
338297
return rc;
339298
}
340299
341300
/*
@@ -375,25 +334,25 @@
375334
376335
/*
377336
** Send text to the appropriate output: If pOut is not NULL, it is
378337
** appended there, else to the console or to the CGI reply buffer.
379338
** Escape all characters with special meaning to HTML if the encode
380
-** parameter is true, with the exception that that flag is ignored if
381
-** g.th1Flags has the TH_INIT_NO_ENCODE flag.
339
+** parameter is true.
382340
**
383341
** If pOut is NULL and the global pThOut is not then that blob
384342
** is used for output.
385343
*/
386
-static void sendText(Blob * pOut, const char *z, int n, int encode){
344
+static void sendText(Blob *pOut, const char *z, int n, int encode){
387345
if(0==pOut && pThOut!=0){
388346
pOut = pThOut;
389347
}
390
- if(TH_INIT_NO_ENCODE & g.th1Flags){
391
- encode = 0;
392
- }
393348
if( enableOutput && n ){
394
- if( n<0 ) n = strlen(z);
349
+ if( n<0 ){
350
+ n = strlen(z);
351
+ }else{
352
+ n = TH1_LEN(n);
353
+ }
395354
if( encode ){
396355
z = htmlize(z, n);
397356
n = strlen(z);
398357
}
399358
if(pOut!=0){
@@ -525,14 +484,22 @@
525484
void *pConvert,
526485
int argc,
527486
const char **argv,
528487
int *argl
529488
){
489
+ int encode = *(unsigned int*)pConvert;
490
+ int n;
530491
if( argc!=2 ){
531492
return Th_WrongNumArgs(interp, "puts STRING");
532493
}
533
- sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
494
+ n = argl[1];
495
+ if( encode==0 && n>0 && TH1_TAINTED(n) ){
496
+ if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497
+ return TH_ERROR;
498
+ }
499
+ }
500
+ sendText(0,(char*)argv[1], TH1_LEN(n), encode);
534501
return TH_OK;
535502
}
536503
537504
/*
538505
** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
557524
}
558525
if( argc==3 ){
559526
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560527
return TH_ERROR;
561528
}
529
+ }
530
+ if( TH1_TAINTED(argl[1])
531
+ && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532
+ ){
533
+ return TH_ERROR;
562534
}
563535
if( withMethod ){
564536
cgi_redirect_with_method(argv[1]);
565537
}else{
566538
cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
660632
int nValue = 0;
661633
if( argc!=2 ){
662634
return Th_WrongNumArgs(interp, "markdown STRING");
663635
}
664636
blob_zero(&src);
665
- blob_init(&src, (char*)argv[1], argl[1]);
637
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
666638
blob_zero(&title); blob_zero(&body);
667639
markdown_to_html(&src, &title, &body);
668640
Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669641
Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670642
Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
690662
if( argc!=2 ){
691663
return Th_WrongNumArgs(interp, "wiki STRING");
692664
}
693665
if( enableOutput ){
694666
Blob src;
695
- blob_init(&src, (char*)argv[1], argl[1]);
667
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
696668
wiki_convert(&src, 0, flags);
697669
blob_reset(&src);
698670
}
699671
return TH_OK;
700672
}
@@ -735,11 +707,11 @@
735707
){
736708
char *zOut;
737709
if( argc!=2 ){
738710
return Th_WrongNumArgs(interp, "htmlize STRING");
739711
}
740
- zOut = htmlize((char*)argv[1], argl[1]);
712
+ zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
741713
Th_SetResult(interp, zOut, -1);
742714
free(zOut);
743715
return TH_OK;
744716
}
745717
@@ -757,11 +729,11 @@
757729
){
758730
char *zOut;
759731
if( argc!=2 ){
760732
return Th_WrongNumArgs(interp, "encode64 STRING");
761733
}
762
- zOut = encode64((char*)argv[1], argl[1]);
734
+ zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
763735
Th_SetResult(interp, zOut, -1);
764736
free(zOut);
765737
return TH_OK;
766738
}
767739
@@ -778,11 +750,11 @@
778750
int argc,
779751
const char **argv,
780752
int *argl
781753
){
782754
char *zOut;
783
- if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
755
+ if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
784756
zOut = db_text("??", "SELECT datetime('now',toLocal())");
785757
}else{
786758
zOut = db_text("??", "SELECT datetime('now')");
787759
}
788760
Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
810782
if( argc<2 ){
811783
return Th_WrongNumArgs(interp, "hascap STRING ...");
812784
}
813785
for(i=1; rc==1 && i<argc; i++){
814786
if( g.thTrace ){
815
- Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
787
+ Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
816788
}
817
- rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
789
+ rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
818790
}
819791
if( g.thTrace ){
820792
Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821793
Th_Free(interp, zCapList);
822794
}
@@ -858,11 +830,11 @@
858830
int i;
859831
860832
if( argc!=2 ){
861833
return Th_WrongNumArgs(interp, "capexpr EXPR");
862834
}
863
- rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
835
+ rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
864836
if( rc ) return rc;
865837
rc = 0;
866838
for(i=0; i<nCap; i++){
867839
if( azCap[i][0]=='!' ){
868840
rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
921893
if( argc<2 ){
922894
return Th_WrongNumArgs(interp, "hascap STRING ...");
923895
}
924896
for(i=1; i<argc && rc; i++){
925897
int match = 0;
926
- for(j=0; j<argl[i]; j++){
898
+ int nn = TH1_LEN(argl[i]);
899
+ for(j=0; j<nn; j++){
927900
switch( argv[i][j] ){
928901
case 'c': match |= searchCap & SRCH_CKIN; break;
929902
case 'd': match |= searchCap & SRCH_DOC; break;
930903
case 't': match |= searchCap & SRCH_TKT; break;
931904
case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
932905
}
933906
}
934907
if( !match ) rc = 0;
935908
}
936909
if( g.thTrace ){
937
- Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
910
+ Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
938911
}
939912
Th_SetResultInt(interp, rc);
940913
return TH_OK;
941914
}
942915
@@ -1051,11 +1024,11 @@
10511024
#endif
10521025
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
10531026
rc = 1;
10541027
}
10551028
if( g.thTrace ){
1056
- Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1029
+ Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
10571030
}
10581031
Th_SetResultInt(interp, rc);
10591032
return TH_OK;
10601033
}
10611034
@@ -1104,18 +1077,20 @@
11041077
const char **argv,
11051078
int *argl
11061079
){
11071080
int rc = 0;
11081081
int i;
1082
+ int nn;
11091083
if( argc!=2 ){
11101084
return Th_WrongNumArgs(interp, "anycap STRING");
11111085
}
1112
- for(i=0; rc==0 && i<argl[1]; i++){
1086
+ nn = TH1_LEN(argl[1]);
1087
+ for(i=0; rc==0 && i<nn; i++){
11131088
rc = login_has_capability((char*)&argv[1][i],1,0);
11141089
}
11151090
if( g.thTrace ){
1116
- Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1091
+ Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
11171092
}
11181093
Th_SetResultInt(interp, rc);
11191094
return TH_OK;
11201095
}
11211096
@@ -1140,22 +1115,23 @@
11401115
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
11411116
}
11421117
if( enableOutput ){
11431118
int height;
11441119
Blob name;
1145
- int nValue;
1120
+ int nValue = 0;
11461121
const char *zValue;
11471122
char *z, *zH;
11481123
int nElem;
11491124
int *aszElem;
11501125
char **azElem;
11511126
int i;
11521127
11531128
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]);
1129
+ Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130
+ blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
11561131
zValue = Th_Fetch(blob_str(&name), &nValue);
1132
+ nValue = TH1_LEN(nValue);
11571133
zH = htmlize(blob_buffer(&name), blob_size(&name));
11581134
z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
11591135
free(zH);
11601136
sendText(0,z, -1, 0);
11611137
free(z);
@@ -1247,11 +1223,11 @@
12471223
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
12481224
}
12491225
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
12501226
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
12511227
z = argv[1];
1252
- size = argl[1];
1228
+ size = TH1_LEN(argl[1]);
12531229
for(n=1, i=0; i<size; i++){
12541230
if( z[i]=='\n' ){
12551231
n++;
12561232
if( n>=iMax ) break;
12571233
}
@@ -1407,11 +1383,12 @@
14071383
return TH_OK;
14081384
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
14091385
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
14101386
return TH_OK;
14111387
}else{
1412
- Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
1388
+ Th_ErrorMessage(interp, "unsupported global state:",
1389
+ argv[1], TH1_LEN(argl[1]));
14131390
return TH_ERROR;
14141391
}
14151392
}
14161393
14171394
/*
@@ -1426,17 +1403,21 @@
14261403
int argc,
14271404
const char **argv,
14281405
int *argl
14291406
){
14301407
const char *zDefault = 0;
1408
+ const char *zVal;
1409
+ int sz;
14311410
if( argc!=2 && argc!=3 ){
14321411
return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
14331412
}
14341413
if( argc==3 ){
14351414
zDefault = argv[2];
14361415
}
1437
- Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
1416
+ zVal = cgi_parameter(argv[1], zDefault);
1417
+ sz = th_strlen(zVal);
1418
+ Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
14381419
return TH_OK;
14391420
}
14401421
14411422
/*
14421423
** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
18481829
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
18491830
Th_SetResult(interp, zUTime, -1);
18501831
return TH_OK;
18511832
}
18521833
1834
+/*
1835
+** TH1 command: taint STRING
1836
+**
1837
+** Return a copy of STRING that is marked as tainted.
1838
+*/
1839
+static int taintCmd(
1840
+ Th_Interp *interp,
1841
+ void *p,
1842
+ int argc,
1843
+ const char **argv,
1844
+ int *argl
1845
+){
1846
+ if( argc!=2 ){
1847
+ return Th_WrongNumArgs(interp, "STRING");
1848
+ }
1849
+ Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850
+ return TH_OK;
1851
+}
1852
+
1853
+/*
1854
+** TH1 command: untaint STRING
1855
+**
1856
+** Return a copy of STRING that is marked as untainted.
1857
+*/
1858
+static int untaintCmd(
1859
+ Th_Interp *interp,
1860
+ void *p,
1861
+ int argc,
1862
+ const char **argv,
1863
+ int *argl
1864
+){
1865
+ if( argc!=2 ){
1866
+ return Th_WrongNumArgs(interp, "STRING");
1867
+ }
1868
+ Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869
+ return TH_OK;
1870
+}
18531871
18541872
/*
18551873
** TH1 command: randhex N
18561874
**
18571875
** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
19231941
int res = TH_OK;
19241942
int nVar;
19251943
char *zErr = 0;
19261944
int noComplain = 0;
19271945
1928
- if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
1946
+ if( argc>3 && TH1_LEN(argl[1])==11
1947
+ && strncmp(argv[1], "-nocomplain", 11)==0
1948
+ ){
19291949
argc--;
19301950
argv++;
19311951
argl++;
19321952
noComplain = 1;
19331953
}
@@ -1939,15 +1959,22 @@
19391959
Th_ErrorMessage(interp, "database is not open", 0, 0);
19401960
return TH_ERROR;
19411961
}
19421962
zSql = argv[1];
19431963
nSql = argl[1];
1964
+ if( TH1_TAINTED(nSql) ){
1965
+ if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966
+ return TH_ERROR;
1967
+ }
1968
+ nSql = TH1_LEN(nSql);
1969
+ }
1970
+
19441971
while( res==TH_OK && nSql>0 ){
19451972
zErr = 0;
19461973
report_restrict_sql(&zErr);
19471974
g.dbIgnoreErrors++;
1948
- rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1975
+ rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
19491976
g.dbIgnoreErrors--;
19501977
report_unrestrict_sql();
19511978
if( rc!=0 || zErr!=0 ){
19521979
if( noComplain ) return TH_OK;
19531980
Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
19641991
int szVar = zVar ? th_strlen(zVar) : 0;
19651992
if( szVar>1 && zVar[0]=='$'
19661993
&& Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
19671994
int nVal;
19681995
const char *zVal = Th_GetResult(interp, &nVal);
1969
- sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1996
+ sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
19701997
}
19711998
}
19721999
while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
19732000
int nCol = sqlite3_column_count(pStmt);
19742001
for(i=0; i<nCol; i++){
19752002
const char *zCol = sqlite3_column_name(pStmt, i);
19762003
int szCol = th_strlen(zCol);
19772004
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
19782005
int szVal = sqlite3_column_bytes(pStmt, i);
1979
- Th_SetVar(interp, zCol, szCol, zVal, szVal);
2006
+ Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
19802007
}
19812008
if( g.thTrace ){
1982
- Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
2009
+ Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
19832010
}
1984
- res = Th_Eval(interp, 0, argv[2], argl[2]);
2011
+ res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
19852012
if( g.thTrace ){
19862013
int nTrRes;
19872014
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
19882015
Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989
- Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
2016
+ Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
19902017
}
19912018
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
19922019
}
19932020
rc = sqlite3_finalize(pStmt);
19942021
if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
20382065
Th_SetResult(interp, 0, 0);
20392066
rc = TH_OK;
20402067
}
20412068
if( g.thTrace ){
20422069
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043
- argl[nArg], argv[nArg], rc);
2070
+ TH1_LEN(argl[nArg]), argv[nArg], rc);
20442071
}
20452072
return rc;
20462073
}
20472074
20482075
/*
@@ -2121,11 +2148,11 @@
21212148
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
21222149
}
21232150
zErr = re_compile(&pRe, argv[nArg], noCase);
21242151
if( !zErr ){
21252152
Th_SetResultInt(interp, re_match(pRe,
2126
- (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2153
+ (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
21272154
rc = TH_OK;
21282155
}else{
21292156
Th_SetResult(interp, zErr, -1);
21302157
rc = TH_ERROR;
21312158
}
@@ -2160,11 +2187,11 @@
21602187
UrlData urlData;
21612188
21622189
if( argc<2 || argc>5 ){
21632190
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
21642191
}
2165
- if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2192
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
21662193
fAsynchronous = 1; nArg++;
21672194
}
21682195
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
21692196
if( nArg+1!=argc && nArg+2!=argc ){
21702197
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
21892216
return TH_ERROR;
21902217
}
21912218
re_free(pRe);
21922219
blob_zero(&payload);
21932220
if( nArg+2==argc ){
2194
- blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2221
+ blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
21952222
zType = "POST";
21962223
}else{
21972224
zType = "GET";
21982225
}
21992226
if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
22682295
if( argc!=2 ){
22692296
return Th_WrongNumArgs(interp, "captureTh1 STRING");
22702297
}
22712298
pOrig = Th_SetOutputBlob(&out);
22722299
zStr = argv[1];
2273
- nStr = argl[1];
2300
+ nStr = TH1_LEN(argl[1]);
22742301
rc = Th_Eval(g.interp, 0, zStr, nStr);
22752302
Th_SetOutputBlob(pOrig);
22762303
if(0==rc){
22772304
Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
22782305
}
@@ -2356,11 +2383,10 @@
23562383
{"copybtn", copybtnCmd, 0},
23572384
{"date", dateCmd, 0},
23582385
{"decorate", wikiCmd, (void*)&aFlags[2]},
23592386
{"defHeader", defHeaderCmd, 0},
23602387
{"dir", dirCmd, 0},
2361
- {"enable_htmlify",enableHtmlifyCmd, 0},
23622388
{"enable_output", enableOutputCmd, 0},
23632389
{"encode64", encode64Cmd, 0},
23642390
{"getParameter", getParameterCmd, 0},
23652391
{"glob_match", globMatchCmd, 0},
23662392
{"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
23872413
{"setting", settingCmd, 0},
23882414
{"styleFooter", styleFooterCmd, 0},
23892415
{"styleHeader", styleHeaderCmd, 0},
23902416
{"styleScript", styleScriptCmd, 0},
23912417
{"submenu", submenuCmd, 0},
2418
+ {"taint", taintCmd, 0},
23922419
{"tclReady", tclReadyCmd, 0},
23932420
{"trace", traceCmd, 0},
23942421
{"stime", stimeCmd, 0},
2422
+ {"untaint", untaintCmd, 0},
23952423
{"unversioned", unversionedCmd, 0},
23962424
{"utime", utimeCmd, 0},
23972425
{"verifyCsrf", verifyCsrfCmd, 0},
23982426
{"verifyLogin", verifyLoginCmd, 0},
23992427
{"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
24942522
Th_Trace("set %h {%h}<br>\n", zName, zValue);
24952523
}
24962524
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
24972525
}
24982526
}
2527
+
2528
+/*
2529
+** Store a string value in a variable in the interpreter
2530
+** with the "taint" marking, so that TH1 knows that this
2531
+** variable contains content under the control of the remote
2532
+** user and presents a risk of XSS or SQL-injection attacks.
2533
+*/
2534
+void Th_StoreUnsafe(const char *zName, const char *zValue){
2535
+ Th_FossilInit(TH_INIT_DEFAULT);
2536
+ if( zValue ){
2537
+ if( g.thTrace ){
2538
+ Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539
+ }
2540
+ Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541
+ }
2542
+}
24992543
25002544
/*
25012545
** Appends an element to a TH1 list value. This function is called by the
25022546
** transfer subsystem; therefore, it must be very careful to avoid doing
25032547
** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
26802724
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
26812725
/*
26822726
** Make sure that the TH1 script error was not caused by a "missing"
26832727
** command hook handler as that is not actually an error condition.
26842728
*/
2729
+ nResult = TH1_LEN(nResult);
26852730
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
26862731
sendError(0,zResult, nResult, 0);
26872732
}else{
26882733
/*
26892734
** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
27672812
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
27682813
/*
27692814
** Make sure that the TH1 script error was not caused by a "missing"
27702815
** webpage hook handler as that is not actually an error condition.
27712816
*/
2817
+ nResult = TH1_LEN(nResult);
27722818
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
27732819
sendError(0,zResult, nResult, 1);
27742820
}else{
27752821
/*
27762822
** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
28942940
}
28952941
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
28962942
z += i+1+n;
28972943
i = 0;
28982944
zResult = (char*)Th_GetResult(g.interp, &n);
2899
- sendText(pOut,(char*)zResult, n, encode);
2945
+ if( !TH1_TAINTED(n)
2946
+ || encode
2947
+ || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948
+ ){
2949
+ sendText(pOut,(char*)zResult, n, encode);
2950
+ }
29002951
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
29012952
sendText(pOut,z, i, 0);
29022953
z += i+5;
29032954
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
29042955
if( g.thTrace ){
@@ -2907,11 +2958,11 @@
29072958
rc = Th_Eval(g.interp, 0, (const char*)z, i);
29082959
if( g.thTrace ){
29092960
int nTrRes;
29102961
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
29112962
Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912
- Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2963
+ Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
29132964
}
29142965
if( rc!=TH_OK ) break;
29152966
z += i;
29162967
if( z[0] ){ z += 6; }
29172968
i = 0;
@@ -2949,14 +3000,81 @@
29493000
** e.g. via the "render" script function binding, need to use the
29503001
** pThOut blob in order to avoid out-of-order output if
29513002
** Th_SetOutputBlob() has been called. If it has not been called,
29523003
** pThOut will be 0, which will redirect the output to CGI/stdout,
29533004
** 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.
3005
+ ** recursive calls.
29563006
*/;
29573007
}
3008
+
3009
+/*
3010
+** SETTING: vuln-report width=8 default=log
3011
+**
3012
+** This setting controls Fossil's behavior when it encounters a potential
3013
+** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014
+** scripts. Choices are:
3015
+**
3016
+** off Do nothing. Ignore the vulnerability.
3017
+**
3018
+** log Write a report of the problem into the error log.
3019
+**
3020
+** block Like "log" but also prevent the offending TH1 command
3021
+** from running.
3022
+**
3023
+** fatal Render an error message page instead of the requested
3024
+** page.
3025
+*/
3026
+
3027
+/*
3028
+** Report misuse of a tainted string in TH1.
3029
+**
3030
+** The behavior depends on the vuln-report setting. If "off", this routine
3031
+** is a no-op. Otherwise, right a message into the error log. If
3032
+** vuln-report is "log", that is all that happens. But for any other
3033
+** value of vuln-report, a fatal error is raised.
3034
+*/
3035
+int Th_ReportTaint(
3036
+ Th_Interp *interp, /* Report error here, if an error is reported */
3037
+ const char *zWhere, /* Where the tainted string appears */
3038
+ const char *zStr, /* The tainted string */
3039
+ int nStr /* Length of the tainted string */
3040
+){
3041
+ static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042
+ const char *zVulnType; /* Type of vulnerability */
3043
+
3044
+ if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045
+ if( is_false(zDisp) ) return 0;
3046
+ if( strstr(zWhere,"SQL")!=0 ){
3047
+ zVulnType = "SQL-injection";
3048
+ }else{
3049
+ zVulnType = "XSS";
3050
+ }
3051
+ nStr = TH1_LEN(nStr);
3052
+ fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053
+ zVulnType, zWhere, nStr, zStr);
3054
+ if( strcmp(zDisp,"log")==0 ){
3055
+ return 0;
3056
+ }
3057
+ if( strcmp(zDisp,"block")==0 ){
3058
+ char *z = mprintf("tainted %s: \"", zWhere);
3059
+ Th_ErrorMessage(interp, z, zStr, nStr);
3060
+ fossil_free(z);
3061
+ }else{
3062
+ char *z = mprintf("%#h", nStr, zStr);
3063
+ zDisp = "off";
3064
+ cgi_reset_content();
3065
+ style_submenu_enable(0);
3066
+ style_set_current_feature("error");
3067
+ style_header("Configuration Error");
3068
+ @ <p>Error in a TH1 configuration script:
3069
+ @ tainted %h(zWhere): "%z(z)"
3070
+ style_finish_page();
3071
+ cgi_reply();
3072
+ fossil_exit(1);
3073
+ }
3074
+ return 1;
3075
+}
29583076
29593077
/*
29603078
** COMMAND: test-th-render
29613079
**
29623080
** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
29923110
if( find_option("set-user-caps", 0, 0)!=0 ){
29933111
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
29943112
login_set_capabilities(zCap ? zCap : "sx", 0);
29953113
g.useLocalauth = 1;
29963114
}
3115
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
29973116
verify_all_options();
29983117
if( g.argc<3 ){
29993118
usage("FILE");
30003119
}
30013120
blob_zero(&in);
@@ -3044,10 +3163,11 @@
30443163
if( find_option("set-user-caps", 0, 0)!=0 ){
30453164
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
30463165
login_set_capabilities(zCap ? zCap : "sx", 0);
30473166
g.useLocalauth = 1;
30483167
}
3168
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
30493169
verify_all_options();
30503170
if( g.argc!=3 ){
30513171
usage("script");
30523172
}
30533173
if(file_isfile(g.argv[2], ExtFILE)){
30543174
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37 /* output. */
38 #define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
39
40 /*
41 ** Useful and/or "well-known" combinations of flag values.
42 */
43 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,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,51 +289,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 /*
302 ** TH1 command: enable_htmlify ?BOOLEAN?
303 **
304 ** Enable or disable the HTML escaping done by all output which
305 ** originates from TH1 (via sendText()).
306 **
307 ** If passed no arguments it instead returns 0 or 1 to indicate the
308 ** current state.
309 */
310 static int enableHtmlifyCmd(
311 Th_Interp *interp,
312 void *p,
313 int argc,
314 const char **argv,
315 int *argl
316 ){
317 int rc = 0, buul;
318 if( argc>3 ){
319 return Th_WrongNumArgs(interp,
320 "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321 }
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;
333 }else{
334 g.th1Flags |= TH_INIT_NO_ENCODE;
335 }
336 }
337 }
338 return rc;
339 }
340
341 /*
@@ -375,25 +334,25 @@
375
376 /*
377 ** Send text to the appropriate output: If pOut is not NULL, it is
378 ** appended there, else to the console or to the CGI reply buffer.
379 ** Escape all characters with special meaning to HTML if the encode
380 ** parameter is true, with the exception that that flag is ignored if
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 +484,22 @@
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 +524,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 +632,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 +662,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 +707,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 +729,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 +750,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 +782,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 +830,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 +893,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 +905,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 +1024,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 +1077,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 +1115,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 +1223,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 +1383,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 /*
@@ -1426,17 +1403,21 @@
1426 int argc,
1427 const char **argv,
1428 int *argl
1429 ){
1430 const char *zDefault = 0;
 
 
1431 if( argc!=2 && argc!=3 ){
1432 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1433 }
1434 if( argc==3 ){
1435 zDefault = argv[2];
1436 }
1437 Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
 
 
1438 return TH_OK;
1439 }
1440
1441 /*
1442 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,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 +1941,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 +1959,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 +1991,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 +2065,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 +2148,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 +2187,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 +2216,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 +2295,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 }
@@ -2356,11 +2383,10 @@
2356 {"copybtn", copybtnCmd, 0},
2357 {"date", dateCmd, 0},
2358 {"decorate", wikiCmd, (void*)&aFlags[2]},
2359 {"defHeader", defHeaderCmd, 0},
2360 {"dir", dirCmd, 0},
2361 {"enable_htmlify",enableHtmlifyCmd, 0},
2362 {"enable_output", enableOutputCmd, 0},
2363 {"encode64", encode64Cmd, 0},
2364 {"getParameter", getParameterCmd, 0},
2365 {"glob_match", globMatchCmd, 0},
2366 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,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 +2522,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 +2724,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 +2812,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
@@ -2894,11 +2940,16 @@
2894 }
2895 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2896 z += i+1+n;
2897 i = 0;
2898 zResult = (char*)Th_GetResult(g.interp, &n);
2899 sendText(pOut,(char*)zResult, n, encode);
 
 
 
 
 
2900 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2901 sendText(pOut,z, i, 0);
2902 z += i+5;
2903 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2904 if( g.thTrace ){
@@ -2907,11 +2958,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;
@@ -2949,14 +3000,81 @@
2949 ** e.g. via the "render" script function binding, need to use the
2950 ** pThOut blob in order to avoid out-of-order output if
2951 ** Th_SetOutputBlob() has been called. If it has not been called,
2952 ** pThOut will be 0, which will redirect the output to CGI/stdout,
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 +3110,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 +3163,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
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
 
 
37
38 /*
39 ** Useful and/or "well-known" combinations of flag values.
40 */
41 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
260 ){
261 char *zOut;
262 if( argc!=2 ){
263 return Th_WrongNumArgs(interp, "httpize STRING");
264 }
265 zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
266 Th_SetResult(interp, zOut, -1);
267 free(zOut);
268 return TH_OK;
269 }
270
@@ -291,51 +289,12 @@
289 if( argc<2 || argc>3 ){
290 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
291 }
292 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
293 if( g.thTrace ){
294 Th_Trace("enable_output {%.*s} -> %d<br>\n",
295 TH1_LEN(argl[1]),argv[1],enableOutput);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296 }
297 return rc;
298 }
299
300 /*
@@ -375,25 +334,25 @@
334
335 /*
336 ** Send text to the appropriate output: If pOut is not NULL, it is
337 ** appended there, else to the console or to the CGI reply buffer.
338 ** Escape all characters with special meaning to HTML if the encode
339 ** parameter is true.
 
340 **
341 ** If pOut is NULL and the global pThOut is not then that blob
342 ** is used for output.
343 */
344 static void sendText(Blob *pOut, const char *z, int n, int encode){
345 if(0==pOut && pThOut!=0){
346 pOut = pThOut;
347 }
 
 
 
348 if( enableOutput && n ){
349 if( n<0 ){
350 n = strlen(z);
351 }else{
352 n = TH1_LEN(n);
353 }
354 if( encode ){
355 z = htmlize(z, n);
356 n = strlen(z);
357 }
358 if(pOut!=0){
@@ -525,14 +484,22 @@
484 void *pConvert,
485 int argc,
486 const char **argv,
487 int *argl
488 ){
489 int encode = *(unsigned int*)pConvert;
490 int n;
491 if( argc!=2 ){
492 return Th_WrongNumArgs(interp, "puts STRING");
493 }
494 n = argl[1];
495 if( encode==0 && n>0 && TH1_TAINTED(n) ){
496 if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497 return TH_ERROR;
498 }
499 }
500 sendText(0,(char*)argv[1], TH1_LEN(n), encode);
501 return TH_OK;
502 }
503
504 /*
505 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
524 }
525 if( argc==3 ){
526 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
527 return TH_ERROR;
528 }
529 }
530 if( TH1_TAINTED(argl[1])
531 && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532 ){
533 return TH_ERROR;
534 }
535 if( withMethod ){
536 cgi_redirect_with_method(argv[1]);
537 }else{
538 cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
632 int nValue = 0;
633 if( argc!=2 ){
634 return Th_WrongNumArgs(interp, "markdown STRING");
635 }
636 blob_zero(&src);
637 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
638 blob_zero(&title); blob_zero(&body);
639 markdown_to_html(&src, &title, &body);
640 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
641 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
642 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
662 if( argc!=2 ){
663 return Th_WrongNumArgs(interp, "wiki STRING");
664 }
665 if( enableOutput ){
666 Blob src;
667 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
668 wiki_convert(&src, 0, flags);
669 blob_reset(&src);
670 }
671 return TH_OK;
672 }
@@ -735,11 +707,11 @@
707 ){
708 char *zOut;
709 if( argc!=2 ){
710 return Th_WrongNumArgs(interp, "htmlize STRING");
711 }
712 zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
713 Th_SetResult(interp, zOut, -1);
714 free(zOut);
715 return TH_OK;
716 }
717
@@ -757,11 +729,11 @@
729 ){
730 char *zOut;
731 if( argc!=2 ){
732 return Th_WrongNumArgs(interp, "encode64 STRING");
733 }
734 zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
735 Th_SetResult(interp, zOut, -1);
736 free(zOut);
737 return TH_OK;
738 }
739
@@ -778,11 +750,11 @@
750 int argc,
751 const char **argv,
752 int *argl
753 ){
754 char *zOut;
755 if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
756 zOut = db_text("??", "SELECT datetime('now',toLocal())");
757 }else{
758 zOut = db_text("??", "SELECT datetime('now')");
759 }
760 Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
782 if( argc<2 ){
783 return Th_WrongNumArgs(interp, "hascap STRING ...");
784 }
785 for(i=1; rc==1 && i<argc; i++){
786 if( g.thTrace ){
787 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
788 }
789 rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
790 }
791 if( g.thTrace ){
792 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
793 Th_Free(interp, zCapList);
794 }
@@ -858,11 +830,11 @@
830 int i;
831
832 if( argc!=2 ){
833 return Th_WrongNumArgs(interp, "capexpr EXPR");
834 }
835 rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
836 if( rc ) return rc;
837 rc = 0;
838 for(i=0; i<nCap; i++){
839 if( azCap[i][0]=='!' ){
840 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
893 if( argc<2 ){
894 return Th_WrongNumArgs(interp, "hascap STRING ...");
895 }
896 for(i=1; i<argc && rc; i++){
897 int match = 0;
898 int nn = TH1_LEN(argl[i]);
899 for(j=0; j<nn; j++){
900 switch( argv[i][j] ){
901 case 'c': match |= searchCap & SRCH_CKIN; break;
902 case 'd': match |= searchCap & SRCH_DOC; break;
903 case 't': match |= searchCap & SRCH_TKT; break;
904 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
905 }
906 }
907 if( !match ) rc = 0;
908 }
909 if( g.thTrace ){
910 Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
911 }
912 Th_SetResultInt(interp, rc);
913 return TH_OK;
914 }
915
@@ -1051,11 +1024,11 @@
1024 #endif
1025 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1026 rc = 1;
1027 }
1028 if( g.thTrace ){
1029 Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
1030 }
1031 Th_SetResultInt(interp, rc);
1032 return TH_OK;
1033 }
1034
@@ -1104,18 +1077,20 @@
1077 const char **argv,
1078 int *argl
1079 ){
1080 int rc = 0;
1081 int i;
1082 int nn;
1083 if( argc!=2 ){
1084 return Th_WrongNumArgs(interp, "anycap STRING");
1085 }
1086 nn = TH1_LEN(argl[1]);
1087 for(i=0; rc==0 && i<nn; i++){
1088 rc = login_has_capability((char*)&argv[1][i],1,0);
1089 }
1090 if( g.thTrace ){
1091 Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
1092 }
1093 Th_SetResultInt(interp, rc);
1094 return TH_OK;
1095 }
1096
@@ -1140,22 +1115,23 @@
1115 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1116 }
1117 if( enableOutput ){
1118 int height;
1119 Blob name;
1120 int nValue = 0;
1121 const char *zValue;
1122 char *z, *zH;
1123 int nElem;
1124 int *aszElem;
1125 char **azElem;
1126 int i;
1127
1128 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1129 Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130 blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
1131 zValue = Th_Fetch(blob_str(&name), &nValue);
1132 nValue = TH1_LEN(nValue);
1133 zH = htmlize(blob_buffer(&name), blob_size(&name));
1134 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1135 free(zH);
1136 sendText(0,z, -1, 0);
1137 free(z);
@@ -1247,11 +1223,11 @@
1223 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1224 }
1225 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1226 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1227 z = argv[1];
1228 size = TH1_LEN(argl[1]);
1229 for(n=1, i=0; i<size; i++){
1230 if( z[i]=='\n' ){
1231 n++;
1232 if( n>=iMax ) break;
1233 }
@@ -1407,11 +1383,12 @@
1383 return TH_OK;
1384 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1385 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1386 return TH_OK;
1387 }else{
1388 Th_ErrorMessage(interp, "unsupported global state:",
1389 argv[1], TH1_LEN(argl[1]));
1390 return TH_ERROR;
1391 }
1392 }
1393
1394 /*
@@ -1426,17 +1403,21 @@
1403 int argc,
1404 const char **argv,
1405 int *argl
1406 ){
1407 const char *zDefault = 0;
1408 const char *zVal;
1409 int sz;
1410 if( argc!=2 && argc!=3 ){
1411 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1412 }
1413 if( argc==3 ){
1414 zDefault = argv[2];
1415 }
1416 zVal = cgi_parameter(argv[1], zDefault);
1417 sz = th_strlen(zVal);
1418 Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
1419 return TH_OK;
1420 }
1421
1422 /*
1423 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
1829 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1830 Th_SetResult(interp, zUTime, -1);
1831 return TH_OK;
1832 }
1833
1834 /*
1835 ** TH1 command: taint STRING
1836 **
1837 ** Return a copy of STRING that is marked as tainted.
1838 */
1839 static int taintCmd(
1840 Th_Interp *interp,
1841 void *p,
1842 int argc,
1843 const char **argv,
1844 int *argl
1845 ){
1846 if( argc!=2 ){
1847 return Th_WrongNumArgs(interp, "STRING");
1848 }
1849 Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850 return TH_OK;
1851 }
1852
1853 /*
1854 ** TH1 command: untaint STRING
1855 **
1856 ** Return a copy of STRING that is marked as untainted.
1857 */
1858 static int untaintCmd(
1859 Th_Interp *interp,
1860 void *p,
1861 int argc,
1862 const char **argv,
1863 int *argl
1864 ){
1865 if( argc!=2 ){
1866 return Th_WrongNumArgs(interp, "STRING");
1867 }
1868 Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869 return TH_OK;
1870 }
1871
1872 /*
1873 ** TH1 command: randhex N
1874 **
1875 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
1941 int res = TH_OK;
1942 int nVar;
1943 char *zErr = 0;
1944 int noComplain = 0;
1945
1946 if( argc>3 && TH1_LEN(argl[1])==11
1947 && strncmp(argv[1], "-nocomplain", 11)==0
1948 ){
1949 argc--;
1950 argv++;
1951 argl++;
1952 noComplain = 1;
1953 }
@@ -1939,15 +1959,22 @@
1959 Th_ErrorMessage(interp, "database is not open", 0, 0);
1960 return TH_ERROR;
1961 }
1962 zSql = argv[1];
1963 nSql = argl[1];
1964 if( TH1_TAINTED(nSql) ){
1965 if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966 return TH_ERROR;
1967 }
1968 nSql = TH1_LEN(nSql);
1969 }
1970
1971 while( res==TH_OK && nSql>0 ){
1972 zErr = 0;
1973 report_restrict_sql(&zErr);
1974 g.dbIgnoreErrors++;
1975 rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
1976 g.dbIgnoreErrors--;
1977 report_unrestrict_sql();
1978 if( rc!=0 || zErr!=0 ){
1979 if( noComplain ) return TH_OK;
1980 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
1991 int szVar = zVar ? th_strlen(zVar) : 0;
1992 if( szVar>1 && zVar[0]=='$'
1993 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1994 int nVal;
1995 const char *zVal = Th_GetResult(interp, &nVal);
1996 sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
1997 }
1998 }
1999 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
2000 int nCol = sqlite3_column_count(pStmt);
2001 for(i=0; i<nCol; i++){
2002 const char *zCol = sqlite3_column_name(pStmt, i);
2003 int szCol = th_strlen(zCol);
2004 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
2005 int szVal = sqlite3_column_bytes(pStmt, i);
2006 Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
2007 }
2008 if( g.thTrace ){
2009 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
2010 }
2011 res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
2012 if( g.thTrace ){
2013 int nTrRes;
2014 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2015 Th_Trace("[query_eval] => %h {%#h}<br>\n",
2016 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
2017 }
2018 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
2019 }
2020 rc = sqlite3_finalize(pStmt);
2021 if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
2065 Th_SetResult(interp, 0, 0);
2066 rc = TH_OK;
2067 }
2068 if( g.thTrace ){
2069 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2070 TH1_LEN(argl[nArg]), argv[nArg], rc);
2071 }
2072 return rc;
2073 }
2074
2075 /*
@@ -2121,11 +2148,11 @@
2148 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2149 }
2150 zErr = re_compile(&pRe, argv[nArg], noCase);
2151 if( !zErr ){
2152 Th_SetResultInt(interp, re_match(pRe,
2153 (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
2154 rc = TH_OK;
2155 }else{
2156 Th_SetResult(interp, zErr, -1);
2157 rc = TH_ERROR;
2158 }
@@ -2160,11 +2187,11 @@
2187 UrlData urlData;
2188
2189 if( argc<2 || argc>5 ){
2190 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2191 }
2192 if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
2193 fAsynchronous = 1; nArg++;
2194 }
2195 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2196 if( nArg+1!=argc && nArg+2!=argc ){
2197 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
2216 return TH_ERROR;
2217 }
2218 re_free(pRe);
2219 blob_zero(&payload);
2220 if( nArg+2==argc ){
2221 blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
2222 zType = "POST";
2223 }else{
2224 zType = "GET";
2225 }
2226 if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
2295 if( argc!=2 ){
2296 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2297 }
2298 pOrig = Th_SetOutputBlob(&out);
2299 zStr = argv[1];
2300 nStr = TH1_LEN(argl[1]);
2301 rc = Th_Eval(g.interp, 0, zStr, nStr);
2302 Th_SetOutputBlob(pOrig);
2303 if(0==rc){
2304 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2305 }
@@ -2356,11 +2383,10 @@
2383 {"copybtn", copybtnCmd, 0},
2384 {"date", dateCmd, 0},
2385 {"decorate", wikiCmd, (void*)&aFlags[2]},
2386 {"defHeader", defHeaderCmd, 0},
2387 {"dir", dirCmd, 0},
 
2388 {"enable_output", enableOutputCmd, 0},
2389 {"encode64", encode64Cmd, 0},
2390 {"getParameter", getParameterCmd, 0},
2391 {"glob_match", globMatchCmd, 0},
2392 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
2413 {"setting", settingCmd, 0},
2414 {"styleFooter", styleFooterCmd, 0},
2415 {"styleHeader", styleHeaderCmd, 0},
2416 {"styleScript", styleScriptCmd, 0},
2417 {"submenu", submenuCmd, 0},
2418 {"taint", taintCmd, 0},
2419 {"tclReady", tclReadyCmd, 0},
2420 {"trace", traceCmd, 0},
2421 {"stime", stimeCmd, 0},
2422 {"untaint", untaintCmd, 0},
2423 {"unversioned", unversionedCmd, 0},
2424 {"utime", utimeCmd, 0},
2425 {"verifyCsrf", verifyCsrfCmd, 0},
2426 {"verifyLogin", verifyLoginCmd, 0},
2427 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
2522 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2523 }
2524 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2525 }
2526 }
2527
2528 /*
2529 ** Store a string value in a variable in the interpreter
2530 ** with the "taint" marking, so that TH1 knows that this
2531 ** variable contains content under the control of the remote
2532 ** user and presents a risk of XSS or SQL-injection attacks.
2533 */
2534 void Th_StoreUnsafe(const char *zName, const char *zValue){
2535 Th_FossilInit(TH_INIT_DEFAULT);
2536 if( zValue ){
2537 if( g.thTrace ){
2538 Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539 }
2540 Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541 }
2542 }
2543
2544 /*
2545 ** Appends an element to a TH1 list value. This function is called by the
2546 ** transfer subsystem; therefore, it must be very careful to avoid doing
2547 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
2724 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2725 /*
2726 ** Make sure that the TH1 script error was not caused by a "missing"
2727 ** command hook handler as that is not actually an error condition.
2728 */
2729 nResult = TH1_LEN(nResult);
2730 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2731 sendError(0,zResult, nResult, 0);
2732 }else{
2733 /*
2734 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
2812 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2813 /*
2814 ** Make sure that the TH1 script error was not caused by a "missing"
2815 ** webpage hook handler as that is not actually an error condition.
2816 */
2817 nResult = TH1_LEN(nResult);
2818 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2819 sendError(0,zResult, nResult, 1);
2820 }else{
2821 /*
2822 ** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
2940 }
2941 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2942 z += i+1+n;
2943 i = 0;
2944 zResult = (char*)Th_GetResult(g.interp, &n);
2945 if( !TH1_TAINTED(n)
2946 || encode
2947 || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948 ){
2949 sendText(pOut,(char*)zResult, n, encode);
2950 }
2951 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2952 sendText(pOut,z, i, 0);
2953 z += i+5;
2954 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2955 if( g.thTrace ){
@@ -2907,11 +2958,11 @@
2958 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2959 if( g.thTrace ){
2960 int nTrRes;
2961 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2962 Th_Trace("[render_eval] => %h {%#h}<br>\n",
2963 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
2964 }
2965 if( rc!=TH_OK ) break;
2966 z += i;
2967 if( z[0] ){ z += 6; }
2968 i = 0;
@@ -2949,14 +3000,81 @@
3000 ** e.g. via the "render" script function binding, need to use the
3001 ** pThOut blob in order to avoid out-of-order output if
3002 ** Th_SetOutputBlob() has been called. If it has not been called,
3003 ** pThOut will be 0, which will redirect the output to CGI/stdout,
3004 ** as appropriate. We need to pass on g.th1Flags for the case of
3005 ** recursive calls.
 
3006 */;
3007 }
3008
3009 /*
3010 ** SETTING: vuln-report width=8 default=log
3011 **
3012 ** This setting controls Fossil's behavior when it encounters a potential
3013 ** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014 ** scripts. Choices are:
3015 **
3016 ** off Do nothing. Ignore the vulnerability.
3017 **
3018 ** log Write a report of the problem into the error log.
3019 **
3020 ** block Like "log" but also prevent the offending TH1 command
3021 ** from running.
3022 **
3023 ** fatal Render an error message page instead of the requested
3024 ** page.
3025 */
3026
3027 /*
3028 ** Report misuse of a tainted string in TH1.
3029 **
3030 ** The behavior depends on the vuln-report setting. If "off", this routine
3031 ** is a no-op. Otherwise, right a message into the error log. If
3032 ** vuln-report is "log", that is all that happens. But for any other
3033 ** value of vuln-report, a fatal error is raised.
3034 */
3035 int Th_ReportTaint(
3036 Th_Interp *interp, /* Report error here, if an error is reported */
3037 const char *zWhere, /* Where the tainted string appears */
3038 const char *zStr, /* The tainted string */
3039 int nStr /* Length of the tainted string */
3040 ){
3041 static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042 const char *zVulnType; /* Type of vulnerability */
3043
3044 if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045 if( is_false(zDisp) ) return 0;
3046 if( strstr(zWhere,"SQL")!=0 ){
3047 zVulnType = "SQL-injection";
3048 }else{
3049 zVulnType = "XSS";
3050 }
3051 nStr = TH1_LEN(nStr);
3052 fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053 zVulnType, zWhere, nStr, zStr);
3054 if( strcmp(zDisp,"log")==0 ){
3055 return 0;
3056 }
3057 if( strcmp(zDisp,"block")==0 ){
3058 char *z = mprintf("tainted %s: \"", zWhere);
3059 Th_ErrorMessage(interp, z, zStr, nStr);
3060 fossil_free(z);
3061 }else{
3062 char *z = mprintf("%#h", nStr, zStr);
3063 zDisp = "off";
3064 cgi_reset_content();
3065 style_submenu_enable(0);
3066 style_set_current_feature("error");
3067 style_header("Configuration Error");
3068 @ <p>Error in a TH1 configuration script:
3069 @ tainted %h(zWhere): "%z(z)"
3070 style_finish_page();
3071 cgi_reply();
3072 fossil_exit(1);
3073 }
3074 return 1;
3075 }
3076
3077 /*
3078 ** COMMAND: test-th-render
3079 **
3080 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
3110 if( find_option("set-user-caps", 0, 0)!=0 ){
3111 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3112 login_set_capabilities(zCap ? zCap : "sx", 0);
3113 g.useLocalauth = 1;
3114 }
3115 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3116 verify_all_options();
3117 if( g.argc<3 ){
3118 usage("FILE");
3119 }
3120 blob_zero(&in);
@@ -3044,10 +3163,11 @@
3163 if( find_option("set-user-caps", 0, 0)!=0 ){
3164 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3165 login_set_capabilities(zCap ? zCap : "sx", 0);
3166 g.useLocalauth = 1;
3167 }
3168 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3169 verify_all_options();
3170 if( g.argc!=3 ){
3171 usage("script");
3172 }
3173 if(file_isfile(g.argv[2], ExtFILE)){
3174
+209 -89
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
3131
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
3232
#define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
3333
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
3434
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
3535
#define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36
-#define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37
- /* output. */
38
-#define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
36
+#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
3937
4038
/*
4139
** Useful and/or "well-known" combinations of flag values.
4240
*/
4341
#define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
262260
){
263261
char *zOut;
264262
if( argc!=2 ){
265263
return Th_WrongNumArgs(interp, "httpize STRING");
266264
}
267
- zOut = httpize((char*)argv[1], argl[1]);
265
+ zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
268266
Th_SetResult(interp, zOut, -1);
269267
free(zOut);
270268
return TH_OK;
271269
}
272270
@@ -291,51 +289,12 @@
291289
if( argc<2 || argc>3 ){
292290
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293291
}
294292
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295293
if( g.thTrace ){
296
- Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
297
- }
298
- return rc;
299
-}
300
-
301
-/*
302
-** TH1 command: enable_htmlify ?BOOLEAN?
303
-**
304
-** Enable or disable the HTML escaping done by all output which
305
-** originates from TH1 (via sendText()).
306
-**
307
-** If passed no arguments it instead returns 0 or 1 to indicate the
308
-** current state.
309
-*/
310
-static int enableHtmlifyCmd(
311
- Th_Interp *interp,
312
- void *p,
313
- int argc,
314
- const char **argv,
315
- int *argl
316
-){
317
- int rc = 0, buul;
318
- if( argc>3 ){
319
- return Th_WrongNumArgs(interp,
320
- "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321
- }
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;
333
- }else{
334
- g.th1Flags |= TH_INIT_NO_ENCODE;
335
- }
336
- }
294
+ Th_Trace("enable_output {%.*s} -> %d<br>\n",
295
+ TH1_LEN(argl[1]),argv[1],enableOutput);
337296
}
338297
return rc;
339298
}
340299
341300
/*
@@ -375,25 +334,25 @@
375334
376335
/*
377336
** Send text to the appropriate output: If pOut is not NULL, it is
378337
** appended there, else to the console or to the CGI reply buffer.
379338
** Escape all characters with special meaning to HTML if the encode
380
-** parameter is true, with the exception that that flag is ignored if
381
-** g.th1Flags has the TH_INIT_NO_ENCODE flag.
339
+** parameter is true.
382340
**
383341
** If pOut is NULL and the global pThOut is not then that blob
384342
** is used for output.
385343
*/
386
-static void sendText(Blob * pOut, const char *z, int n, int encode){
344
+static void sendText(Blob *pOut, const char *z, int n, int encode){
387345
if(0==pOut && pThOut!=0){
388346
pOut = pThOut;
389347
}
390
- if(TH_INIT_NO_ENCODE & g.th1Flags){
391
- encode = 0;
392
- }
393348
if( enableOutput && n ){
394
- if( n<0 ) n = strlen(z);
349
+ if( n<0 ){
350
+ n = strlen(z);
351
+ }else{
352
+ n = TH1_LEN(n);
353
+ }
395354
if( encode ){
396355
z = htmlize(z, n);
397356
n = strlen(z);
398357
}
399358
if(pOut!=0){
@@ -525,14 +484,22 @@
525484
void *pConvert,
526485
int argc,
527486
const char **argv,
528487
int *argl
529488
){
489
+ int encode = *(unsigned int*)pConvert;
490
+ int n;
530491
if( argc!=2 ){
531492
return Th_WrongNumArgs(interp, "puts STRING");
532493
}
533
- sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
494
+ n = argl[1];
495
+ if( encode==0 && n>0 && TH1_TAINTED(n) ){
496
+ if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497
+ return TH_ERROR;
498
+ }
499
+ }
500
+ sendText(0,(char*)argv[1], TH1_LEN(n), encode);
534501
return TH_OK;
535502
}
536503
537504
/*
538505
** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
557524
}
558525
if( argc==3 ){
559526
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560527
return TH_ERROR;
561528
}
529
+ }
530
+ if( TH1_TAINTED(argl[1])
531
+ && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532
+ ){
533
+ return TH_ERROR;
562534
}
563535
if( withMethod ){
564536
cgi_redirect_with_method(argv[1]);
565537
}else{
566538
cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
660632
int nValue = 0;
661633
if( argc!=2 ){
662634
return Th_WrongNumArgs(interp, "markdown STRING");
663635
}
664636
blob_zero(&src);
665
- blob_init(&src, (char*)argv[1], argl[1]);
637
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
666638
blob_zero(&title); blob_zero(&body);
667639
markdown_to_html(&src, &title, &body);
668640
Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669641
Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670642
Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
690662
if( argc!=2 ){
691663
return Th_WrongNumArgs(interp, "wiki STRING");
692664
}
693665
if( enableOutput ){
694666
Blob src;
695
- blob_init(&src, (char*)argv[1], argl[1]);
667
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
696668
wiki_convert(&src, 0, flags);
697669
blob_reset(&src);
698670
}
699671
return TH_OK;
700672
}
@@ -735,11 +707,11 @@
735707
){
736708
char *zOut;
737709
if( argc!=2 ){
738710
return Th_WrongNumArgs(interp, "htmlize STRING");
739711
}
740
- zOut = htmlize((char*)argv[1], argl[1]);
712
+ zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
741713
Th_SetResult(interp, zOut, -1);
742714
free(zOut);
743715
return TH_OK;
744716
}
745717
@@ -757,11 +729,11 @@
757729
){
758730
char *zOut;
759731
if( argc!=2 ){
760732
return Th_WrongNumArgs(interp, "encode64 STRING");
761733
}
762
- zOut = encode64((char*)argv[1], argl[1]);
734
+ zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
763735
Th_SetResult(interp, zOut, -1);
764736
free(zOut);
765737
return TH_OK;
766738
}
767739
@@ -778,11 +750,11 @@
778750
int argc,
779751
const char **argv,
780752
int *argl
781753
){
782754
char *zOut;
783
- if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
755
+ if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
784756
zOut = db_text("??", "SELECT datetime('now',toLocal())");
785757
}else{
786758
zOut = db_text("??", "SELECT datetime('now')");
787759
}
788760
Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
810782
if( argc<2 ){
811783
return Th_WrongNumArgs(interp, "hascap STRING ...");
812784
}
813785
for(i=1; rc==1 && i<argc; i++){
814786
if( g.thTrace ){
815
- Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
787
+ Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
816788
}
817
- rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
789
+ rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
818790
}
819791
if( g.thTrace ){
820792
Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821793
Th_Free(interp, zCapList);
822794
}
@@ -858,11 +830,11 @@
858830
int i;
859831
860832
if( argc!=2 ){
861833
return Th_WrongNumArgs(interp, "capexpr EXPR");
862834
}
863
- rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
835
+ rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
864836
if( rc ) return rc;
865837
rc = 0;
866838
for(i=0; i<nCap; i++){
867839
if( azCap[i][0]=='!' ){
868840
rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
921893
if( argc<2 ){
922894
return Th_WrongNumArgs(interp, "hascap STRING ...");
923895
}
924896
for(i=1; i<argc && rc; i++){
925897
int match = 0;
926
- for(j=0; j<argl[i]; j++){
898
+ int nn = TH1_LEN(argl[i]);
899
+ for(j=0; j<nn; j++){
927900
switch( argv[i][j] ){
928901
case 'c': match |= searchCap & SRCH_CKIN; break;
929902
case 'd': match |= searchCap & SRCH_DOC; break;
930903
case 't': match |= searchCap & SRCH_TKT; break;
931904
case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
932905
}
933906
}
934907
if( !match ) rc = 0;
935908
}
936909
if( g.thTrace ){
937
- Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
910
+ Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
938911
}
939912
Th_SetResultInt(interp, rc);
940913
return TH_OK;
941914
}
942915
@@ -1051,11 +1024,11 @@
10511024
#endif
10521025
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
10531026
rc = 1;
10541027
}
10551028
if( g.thTrace ){
1056
- Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1029
+ Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
10571030
}
10581031
Th_SetResultInt(interp, rc);
10591032
return TH_OK;
10601033
}
10611034
@@ -1104,18 +1077,20 @@
11041077
const char **argv,
11051078
int *argl
11061079
){
11071080
int rc = 0;
11081081
int i;
1082
+ int nn;
11091083
if( argc!=2 ){
11101084
return Th_WrongNumArgs(interp, "anycap STRING");
11111085
}
1112
- for(i=0; rc==0 && i<argl[1]; i++){
1086
+ nn = TH1_LEN(argl[1]);
1087
+ for(i=0; rc==0 && i<nn; i++){
11131088
rc = login_has_capability((char*)&argv[1][i],1,0);
11141089
}
11151090
if( g.thTrace ){
1116
- Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1091
+ Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
11171092
}
11181093
Th_SetResultInt(interp, rc);
11191094
return TH_OK;
11201095
}
11211096
@@ -1140,22 +1115,23 @@
11401115
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
11411116
}
11421117
if( enableOutput ){
11431118
int height;
11441119
Blob name;
1145
- int nValue;
1120
+ int nValue = 0;
11461121
const char *zValue;
11471122
char *z, *zH;
11481123
int nElem;
11491124
int *aszElem;
11501125
char **azElem;
11511126
int i;
11521127
11531128
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]);
1129
+ Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130
+ blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
11561131
zValue = Th_Fetch(blob_str(&name), &nValue);
1132
+ nValue = TH1_LEN(nValue);
11571133
zH = htmlize(blob_buffer(&name), blob_size(&name));
11581134
z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
11591135
free(zH);
11601136
sendText(0,z, -1, 0);
11611137
free(z);
@@ -1247,11 +1223,11 @@
12471223
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
12481224
}
12491225
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
12501226
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
12511227
z = argv[1];
1252
- size = argl[1];
1228
+ size = TH1_LEN(argl[1]);
12531229
for(n=1, i=0; i<size; i++){
12541230
if( z[i]=='\n' ){
12551231
n++;
12561232
if( n>=iMax ) break;
12571233
}
@@ -1407,11 +1383,12 @@
14071383
return TH_OK;
14081384
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
14091385
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
14101386
return TH_OK;
14111387
}else{
1412
- Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
1388
+ Th_ErrorMessage(interp, "unsupported global state:",
1389
+ argv[1], TH1_LEN(argl[1]));
14131390
return TH_ERROR;
14141391
}
14151392
}
14161393
14171394
/*
@@ -1426,17 +1403,21 @@
14261403
int argc,
14271404
const char **argv,
14281405
int *argl
14291406
){
14301407
const char *zDefault = 0;
1408
+ const char *zVal;
1409
+ int sz;
14311410
if( argc!=2 && argc!=3 ){
14321411
return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
14331412
}
14341413
if( argc==3 ){
14351414
zDefault = argv[2];
14361415
}
1437
- Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
1416
+ zVal = cgi_parameter(argv[1], zDefault);
1417
+ sz = th_strlen(zVal);
1418
+ Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
14381419
return TH_OK;
14391420
}
14401421
14411422
/*
14421423
** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
18481829
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
18491830
Th_SetResult(interp, zUTime, -1);
18501831
return TH_OK;
18511832
}
18521833
1834
+/*
1835
+** TH1 command: taint STRING
1836
+**
1837
+** Return a copy of STRING that is marked as tainted.
1838
+*/
1839
+static int taintCmd(
1840
+ Th_Interp *interp,
1841
+ void *p,
1842
+ int argc,
1843
+ const char **argv,
1844
+ int *argl
1845
+){
1846
+ if( argc!=2 ){
1847
+ return Th_WrongNumArgs(interp, "STRING");
1848
+ }
1849
+ Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850
+ return TH_OK;
1851
+}
1852
+
1853
+/*
1854
+** TH1 command: untaint STRING
1855
+**
1856
+** Return a copy of STRING that is marked as untainted.
1857
+*/
1858
+static int untaintCmd(
1859
+ Th_Interp *interp,
1860
+ void *p,
1861
+ int argc,
1862
+ const char **argv,
1863
+ int *argl
1864
+){
1865
+ if( argc!=2 ){
1866
+ return Th_WrongNumArgs(interp, "STRING");
1867
+ }
1868
+ Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869
+ return TH_OK;
1870
+}
18531871
18541872
/*
18551873
** TH1 command: randhex N
18561874
**
18571875
** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
19231941
int res = TH_OK;
19241942
int nVar;
19251943
char *zErr = 0;
19261944
int noComplain = 0;
19271945
1928
- if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
1946
+ if( argc>3 && TH1_LEN(argl[1])==11
1947
+ && strncmp(argv[1], "-nocomplain", 11)==0
1948
+ ){
19291949
argc--;
19301950
argv++;
19311951
argl++;
19321952
noComplain = 1;
19331953
}
@@ -1939,15 +1959,22 @@
19391959
Th_ErrorMessage(interp, "database is not open", 0, 0);
19401960
return TH_ERROR;
19411961
}
19421962
zSql = argv[1];
19431963
nSql = argl[1];
1964
+ if( TH1_TAINTED(nSql) ){
1965
+ if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966
+ return TH_ERROR;
1967
+ }
1968
+ nSql = TH1_LEN(nSql);
1969
+ }
1970
+
19441971
while( res==TH_OK && nSql>0 ){
19451972
zErr = 0;
19461973
report_restrict_sql(&zErr);
19471974
g.dbIgnoreErrors++;
1948
- rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1975
+ rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
19491976
g.dbIgnoreErrors--;
19501977
report_unrestrict_sql();
19511978
if( rc!=0 || zErr!=0 ){
19521979
if( noComplain ) return TH_OK;
19531980
Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
19641991
int szVar = zVar ? th_strlen(zVar) : 0;
19651992
if( szVar>1 && zVar[0]=='$'
19661993
&& Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
19671994
int nVal;
19681995
const char *zVal = Th_GetResult(interp, &nVal);
1969
- sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1996
+ sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
19701997
}
19711998
}
19721999
while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
19732000
int nCol = sqlite3_column_count(pStmt);
19742001
for(i=0; i<nCol; i++){
19752002
const char *zCol = sqlite3_column_name(pStmt, i);
19762003
int szCol = th_strlen(zCol);
19772004
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
19782005
int szVal = sqlite3_column_bytes(pStmt, i);
1979
- Th_SetVar(interp, zCol, szCol, zVal, szVal);
2006
+ Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
19802007
}
19812008
if( g.thTrace ){
1982
- Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
2009
+ Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
19832010
}
1984
- res = Th_Eval(interp, 0, argv[2], argl[2]);
2011
+ res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
19852012
if( g.thTrace ){
19862013
int nTrRes;
19872014
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
19882015
Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989
- Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
2016
+ Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
19902017
}
19912018
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
19922019
}
19932020
rc = sqlite3_finalize(pStmt);
19942021
if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
20382065
Th_SetResult(interp, 0, 0);
20392066
rc = TH_OK;
20402067
}
20412068
if( g.thTrace ){
20422069
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043
- argl[nArg], argv[nArg], rc);
2070
+ TH1_LEN(argl[nArg]), argv[nArg], rc);
20442071
}
20452072
return rc;
20462073
}
20472074
20482075
/*
@@ -2121,11 +2148,11 @@
21212148
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
21222149
}
21232150
zErr = re_compile(&pRe, argv[nArg], noCase);
21242151
if( !zErr ){
21252152
Th_SetResultInt(interp, re_match(pRe,
2126
- (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2153
+ (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
21272154
rc = TH_OK;
21282155
}else{
21292156
Th_SetResult(interp, zErr, -1);
21302157
rc = TH_ERROR;
21312158
}
@@ -2160,11 +2187,11 @@
21602187
UrlData urlData;
21612188
21622189
if( argc<2 || argc>5 ){
21632190
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
21642191
}
2165
- if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2192
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
21662193
fAsynchronous = 1; nArg++;
21672194
}
21682195
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
21692196
if( nArg+1!=argc && nArg+2!=argc ){
21702197
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
21892216
return TH_ERROR;
21902217
}
21912218
re_free(pRe);
21922219
blob_zero(&payload);
21932220
if( nArg+2==argc ){
2194
- blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2221
+ blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
21952222
zType = "POST";
21962223
}else{
21972224
zType = "GET";
21982225
}
21992226
if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
22682295
if( argc!=2 ){
22692296
return Th_WrongNumArgs(interp, "captureTh1 STRING");
22702297
}
22712298
pOrig = Th_SetOutputBlob(&out);
22722299
zStr = argv[1];
2273
- nStr = argl[1];
2300
+ nStr = TH1_LEN(argl[1]);
22742301
rc = Th_Eval(g.interp, 0, zStr, nStr);
22752302
Th_SetOutputBlob(pOrig);
22762303
if(0==rc){
22772304
Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
22782305
}
@@ -2356,11 +2383,10 @@
23562383
{"copybtn", copybtnCmd, 0},
23572384
{"date", dateCmd, 0},
23582385
{"decorate", wikiCmd, (void*)&aFlags[2]},
23592386
{"defHeader", defHeaderCmd, 0},
23602387
{"dir", dirCmd, 0},
2361
- {"enable_htmlify",enableHtmlifyCmd, 0},
23622388
{"enable_output", enableOutputCmd, 0},
23632389
{"encode64", encode64Cmd, 0},
23642390
{"getParameter", getParameterCmd, 0},
23652391
{"glob_match", globMatchCmd, 0},
23662392
{"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
23872413
{"setting", settingCmd, 0},
23882414
{"styleFooter", styleFooterCmd, 0},
23892415
{"styleHeader", styleHeaderCmd, 0},
23902416
{"styleScript", styleScriptCmd, 0},
23912417
{"submenu", submenuCmd, 0},
2418
+ {"taint", taintCmd, 0},
23922419
{"tclReady", tclReadyCmd, 0},
23932420
{"trace", traceCmd, 0},
23942421
{"stime", stimeCmd, 0},
2422
+ {"untaint", untaintCmd, 0},
23952423
{"unversioned", unversionedCmd, 0},
23962424
{"utime", utimeCmd, 0},
23972425
{"verifyCsrf", verifyCsrfCmd, 0},
23982426
{"verifyLogin", verifyLoginCmd, 0},
23992427
{"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
24942522
Th_Trace("set %h {%h}<br>\n", zName, zValue);
24952523
}
24962524
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
24972525
}
24982526
}
2527
+
2528
+/*
2529
+** Store a string value in a variable in the interpreter
2530
+** with the "taint" marking, so that TH1 knows that this
2531
+** variable contains content under the control of the remote
2532
+** user and presents a risk of XSS or SQL-injection attacks.
2533
+*/
2534
+void Th_StoreUnsafe(const char *zName, const char *zValue){
2535
+ Th_FossilInit(TH_INIT_DEFAULT);
2536
+ if( zValue ){
2537
+ if( g.thTrace ){
2538
+ Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539
+ }
2540
+ Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541
+ }
2542
+}
24992543
25002544
/*
25012545
** Appends an element to a TH1 list value. This function is called by the
25022546
** transfer subsystem; therefore, it must be very careful to avoid doing
25032547
** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
26802724
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
26812725
/*
26822726
** Make sure that the TH1 script error was not caused by a "missing"
26832727
** command hook handler as that is not actually an error condition.
26842728
*/
2729
+ nResult = TH1_LEN(nResult);
26852730
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
26862731
sendError(0,zResult, nResult, 0);
26872732
}else{
26882733
/*
26892734
** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
27672812
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
27682813
/*
27692814
** Make sure that the TH1 script error was not caused by a "missing"
27702815
** webpage hook handler as that is not actually an error condition.
27712816
*/
2817
+ nResult = TH1_LEN(nResult);
27722818
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
27732819
sendError(0,zResult, nResult, 1);
27742820
}else{
27752821
/*
27762822
** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
28942940
}
28952941
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
28962942
z += i+1+n;
28972943
i = 0;
28982944
zResult = (char*)Th_GetResult(g.interp, &n);
2899
- sendText(pOut,(char*)zResult, n, encode);
2945
+ if( !TH1_TAINTED(n)
2946
+ || encode
2947
+ || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948
+ ){
2949
+ sendText(pOut,(char*)zResult, n, encode);
2950
+ }
29002951
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
29012952
sendText(pOut,z, i, 0);
29022953
z += i+5;
29032954
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
29042955
if( g.thTrace ){
@@ -2907,11 +2958,11 @@
29072958
rc = Th_Eval(g.interp, 0, (const char*)z, i);
29082959
if( g.thTrace ){
29092960
int nTrRes;
29102961
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
29112962
Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912
- Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2963
+ Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
29132964
}
29142965
if( rc!=TH_OK ) break;
29152966
z += i;
29162967
if( z[0] ){ z += 6; }
29172968
i = 0;
@@ -2949,14 +3000,81 @@
29493000
** e.g. via the "render" script function binding, need to use the
29503001
** pThOut blob in order to avoid out-of-order output if
29513002
** Th_SetOutputBlob() has been called. If it has not been called,
29523003
** pThOut will be 0, which will redirect the output to CGI/stdout,
29533004
** 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.
3005
+ ** recursive calls.
29563006
*/;
29573007
}
3008
+
3009
+/*
3010
+** SETTING: vuln-report width=8 default=log
3011
+**
3012
+** This setting controls Fossil's behavior when it encounters a potential
3013
+** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014
+** scripts. Choices are:
3015
+**
3016
+** off Do nothing. Ignore the vulnerability.
3017
+**
3018
+** log Write a report of the problem into the error log.
3019
+**
3020
+** block Like "log" but also prevent the offending TH1 command
3021
+** from running.
3022
+**
3023
+** fatal Render an error message page instead of the requested
3024
+** page.
3025
+*/
3026
+
3027
+/*
3028
+** Report misuse of a tainted string in TH1.
3029
+**
3030
+** The behavior depends on the vuln-report setting. If "off", this routine
3031
+** is a no-op. Otherwise, right a message into the error log. If
3032
+** vuln-report is "log", that is all that happens. But for any other
3033
+** value of vuln-report, a fatal error is raised.
3034
+*/
3035
+int Th_ReportTaint(
3036
+ Th_Interp *interp, /* Report error here, if an error is reported */
3037
+ const char *zWhere, /* Where the tainted string appears */
3038
+ const char *zStr, /* The tainted string */
3039
+ int nStr /* Length of the tainted string */
3040
+){
3041
+ static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042
+ const char *zVulnType; /* Type of vulnerability */
3043
+
3044
+ if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045
+ if( is_false(zDisp) ) return 0;
3046
+ if( strstr(zWhere,"SQL")!=0 ){
3047
+ zVulnType = "SQL-injection";
3048
+ }else{
3049
+ zVulnType = "XSS";
3050
+ }
3051
+ nStr = TH1_LEN(nStr);
3052
+ fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053
+ zVulnType, zWhere, nStr, zStr);
3054
+ if( strcmp(zDisp,"log")==0 ){
3055
+ return 0;
3056
+ }
3057
+ if( strcmp(zDisp,"block")==0 ){
3058
+ char *z = mprintf("tainted %s: \"", zWhere);
3059
+ Th_ErrorMessage(interp, z, zStr, nStr);
3060
+ fossil_free(z);
3061
+ }else{
3062
+ char *z = mprintf("%#h", nStr, zStr);
3063
+ zDisp = "off";
3064
+ cgi_reset_content();
3065
+ style_submenu_enable(0);
3066
+ style_set_current_feature("error");
3067
+ style_header("Configuration Error");
3068
+ @ <p>Error in a TH1 configuration script:
3069
+ @ tainted %h(zWhere): "%z(z)"
3070
+ style_finish_page();
3071
+ cgi_reply();
3072
+ fossil_exit(1);
3073
+ }
3074
+ return 1;
3075
+}
29583076
29593077
/*
29603078
** COMMAND: test-th-render
29613079
**
29623080
** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
29923110
if( find_option("set-user-caps", 0, 0)!=0 ){
29933111
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
29943112
login_set_capabilities(zCap ? zCap : "sx", 0);
29953113
g.useLocalauth = 1;
29963114
}
3115
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
29973116
verify_all_options();
29983117
if( g.argc<3 ){
29993118
usage("FILE");
30003119
}
30013120
blob_zero(&in);
@@ -3044,10 +3163,11 @@
30443163
if( find_option("set-user-caps", 0, 0)!=0 ){
30453164
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
30463165
login_set_capabilities(zCap ? zCap : "sx", 0);
30473166
g.useLocalauth = 1;
30483167
}
3168
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
30493169
verify_all_options();
30503170
if( g.argc!=3 ){
30513171
usage("script");
30523172
}
30533173
if(file_isfile(g.argv[2], ExtFILE)){
30543174
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37 /* output. */
38 #define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
39
40 /*
41 ** Useful and/or "well-known" combinations of flag values.
42 */
43 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,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,51 +289,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 /*
302 ** TH1 command: enable_htmlify ?BOOLEAN?
303 **
304 ** Enable or disable the HTML escaping done by all output which
305 ** originates from TH1 (via sendText()).
306 **
307 ** If passed no arguments it instead returns 0 or 1 to indicate the
308 ** current state.
309 */
310 static int enableHtmlifyCmd(
311 Th_Interp *interp,
312 void *p,
313 int argc,
314 const char **argv,
315 int *argl
316 ){
317 int rc = 0, buul;
318 if( argc>3 ){
319 return Th_WrongNumArgs(interp,
320 "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321 }
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;
333 }else{
334 g.th1Flags |= TH_INIT_NO_ENCODE;
335 }
336 }
337 }
338 return rc;
339 }
340
341 /*
@@ -375,25 +334,25 @@
375
376 /*
377 ** Send text to the appropriate output: If pOut is not NULL, it is
378 ** appended there, else to the console or to the CGI reply buffer.
379 ** Escape all characters with special meaning to HTML if the encode
380 ** parameter is true, with the exception that that flag is ignored if
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 +484,22 @@
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 +524,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 +632,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 +662,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 +707,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 +729,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 +750,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 +782,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 +830,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 +893,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 +905,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 +1024,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 +1077,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 +1115,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 +1223,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 +1383,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 /*
@@ -1426,17 +1403,21 @@
1426 int argc,
1427 const char **argv,
1428 int *argl
1429 ){
1430 const char *zDefault = 0;
 
 
1431 if( argc!=2 && argc!=3 ){
1432 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1433 }
1434 if( argc==3 ){
1435 zDefault = argv[2];
1436 }
1437 Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
 
 
1438 return TH_OK;
1439 }
1440
1441 /*
1442 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,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 +1941,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 +1959,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 +1991,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 +2065,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 +2148,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 +2187,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 +2216,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 +2295,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 }
@@ -2356,11 +2383,10 @@
2356 {"copybtn", copybtnCmd, 0},
2357 {"date", dateCmd, 0},
2358 {"decorate", wikiCmd, (void*)&aFlags[2]},
2359 {"defHeader", defHeaderCmd, 0},
2360 {"dir", dirCmd, 0},
2361 {"enable_htmlify",enableHtmlifyCmd, 0},
2362 {"enable_output", enableOutputCmd, 0},
2363 {"encode64", encode64Cmd, 0},
2364 {"getParameter", getParameterCmd, 0},
2365 {"glob_match", globMatchCmd, 0},
2366 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,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 +2522,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 +2724,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 +2812,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
@@ -2894,11 +2940,16 @@
2894 }
2895 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2896 z += i+1+n;
2897 i = 0;
2898 zResult = (char*)Th_GetResult(g.interp, &n);
2899 sendText(pOut,(char*)zResult, n, encode);
 
 
 
 
 
2900 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2901 sendText(pOut,z, i, 0);
2902 z += i+5;
2903 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2904 if( g.thTrace ){
@@ -2907,11 +2958,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;
@@ -2949,14 +3000,81 @@
2949 ** e.g. via the "render" script function binding, need to use the
2950 ** pThOut blob in order to avoid out-of-order output if
2951 ** Th_SetOutputBlob() has been called. If it has not been called,
2952 ** pThOut will be 0, which will redirect the output to CGI/stdout,
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 +3110,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 +3163,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
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
 
 
37
38 /*
39 ** Useful and/or "well-known" combinations of flag values.
40 */
41 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
260 ){
261 char *zOut;
262 if( argc!=2 ){
263 return Th_WrongNumArgs(interp, "httpize STRING");
264 }
265 zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
266 Th_SetResult(interp, zOut, -1);
267 free(zOut);
268 return TH_OK;
269 }
270
@@ -291,51 +289,12 @@
289 if( argc<2 || argc>3 ){
290 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
291 }
292 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
293 if( g.thTrace ){
294 Th_Trace("enable_output {%.*s} -> %d<br>\n",
295 TH1_LEN(argl[1]),argv[1],enableOutput);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296 }
297 return rc;
298 }
299
300 /*
@@ -375,25 +334,25 @@
334
335 /*
336 ** Send text to the appropriate output: If pOut is not NULL, it is
337 ** appended there, else to the console or to the CGI reply buffer.
338 ** Escape all characters with special meaning to HTML if the encode
339 ** parameter is true.
 
340 **
341 ** If pOut is NULL and the global pThOut is not then that blob
342 ** is used for output.
343 */
344 static void sendText(Blob *pOut, const char *z, int n, int encode){
345 if(0==pOut && pThOut!=0){
346 pOut = pThOut;
347 }
 
 
 
348 if( enableOutput && n ){
349 if( n<0 ){
350 n = strlen(z);
351 }else{
352 n = TH1_LEN(n);
353 }
354 if( encode ){
355 z = htmlize(z, n);
356 n = strlen(z);
357 }
358 if(pOut!=0){
@@ -525,14 +484,22 @@
484 void *pConvert,
485 int argc,
486 const char **argv,
487 int *argl
488 ){
489 int encode = *(unsigned int*)pConvert;
490 int n;
491 if( argc!=2 ){
492 return Th_WrongNumArgs(interp, "puts STRING");
493 }
494 n = argl[1];
495 if( encode==0 && n>0 && TH1_TAINTED(n) ){
496 if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497 return TH_ERROR;
498 }
499 }
500 sendText(0,(char*)argv[1], TH1_LEN(n), encode);
501 return TH_OK;
502 }
503
504 /*
505 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
524 }
525 if( argc==3 ){
526 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
527 return TH_ERROR;
528 }
529 }
530 if( TH1_TAINTED(argl[1])
531 && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532 ){
533 return TH_ERROR;
534 }
535 if( withMethod ){
536 cgi_redirect_with_method(argv[1]);
537 }else{
538 cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
632 int nValue = 0;
633 if( argc!=2 ){
634 return Th_WrongNumArgs(interp, "markdown STRING");
635 }
636 blob_zero(&src);
637 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
638 blob_zero(&title); blob_zero(&body);
639 markdown_to_html(&src, &title, &body);
640 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
641 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
642 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
662 if( argc!=2 ){
663 return Th_WrongNumArgs(interp, "wiki STRING");
664 }
665 if( enableOutput ){
666 Blob src;
667 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
668 wiki_convert(&src, 0, flags);
669 blob_reset(&src);
670 }
671 return TH_OK;
672 }
@@ -735,11 +707,11 @@
707 ){
708 char *zOut;
709 if( argc!=2 ){
710 return Th_WrongNumArgs(interp, "htmlize STRING");
711 }
712 zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
713 Th_SetResult(interp, zOut, -1);
714 free(zOut);
715 return TH_OK;
716 }
717
@@ -757,11 +729,11 @@
729 ){
730 char *zOut;
731 if( argc!=2 ){
732 return Th_WrongNumArgs(interp, "encode64 STRING");
733 }
734 zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
735 Th_SetResult(interp, zOut, -1);
736 free(zOut);
737 return TH_OK;
738 }
739
@@ -778,11 +750,11 @@
750 int argc,
751 const char **argv,
752 int *argl
753 ){
754 char *zOut;
755 if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
756 zOut = db_text("??", "SELECT datetime('now',toLocal())");
757 }else{
758 zOut = db_text("??", "SELECT datetime('now')");
759 }
760 Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
782 if( argc<2 ){
783 return Th_WrongNumArgs(interp, "hascap STRING ...");
784 }
785 for(i=1; rc==1 && i<argc; i++){
786 if( g.thTrace ){
787 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
788 }
789 rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
790 }
791 if( g.thTrace ){
792 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
793 Th_Free(interp, zCapList);
794 }
@@ -858,11 +830,11 @@
830 int i;
831
832 if( argc!=2 ){
833 return Th_WrongNumArgs(interp, "capexpr EXPR");
834 }
835 rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
836 if( rc ) return rc;
837 rc = 0;
838 for(i=0; i<nCap; i++){
839 if( azCap[i][0]=='!' ){
840 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
893 if( argc<2 ){
894 return Th_WrongNumArgs(interp, "hascap STRING ...");
895 }
896 for(i=1; i<argc && rc; i++){
897 int match = 0;
898 int nn = TH1_LEN(argl[i]);
899 for(j=0; j<nn; j++){
900 switch( argv[i][j] ){
901 case 'c': match |= searchCap & SRCH_CKIN; break;
902 case 'd': match |= searchCap & SRCH_DOC; break;
903 case 't': match |= searchCap & SRCH_TKT; break;
904 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
905 }
906 }
907 if( !match ) rc = 0;
908 }
909 if( g.thTrace ){
910 Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
911 }
912 Th_SetResultInt(interp, rc);
913 return TH_OK;
914 }
915
@@ -1051,11 +1024,11 @@
1024 #endif
1025 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1026 rc = 1;
1027 }
1028 if( g.thTrace ){
1029 Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
1030 }
1031 Th_SetResultInt(interp, rc);
1032 return TH_OK;
1033 }
1034
@@ -1104,18 +1077,20 @@
1077 const char **argv,
1078 int *argl
1079 ){
1080 int rc = 0;
1081 int i;
1082 int nn;
1083 if( argc!=2 ){
1084 return Th_WrongNumArgs(interp, "anycap STRING");
1085 }
1086 nn = TH1_LEN(argl[1]);
1087 for(i=0; rc==0 && i<nn; i++){
1088 rc = login_has_capability((char*)&argv[1][i],1,0);
1089 }
1090 if( g.thTrace ){
1091 Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
1092 }
1093 Th_SetResultInt(interp, rc);
1094 return TH_OK;
1095 }
1096
@@ -1140,22 +1115,23 @@
1115 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1116 }
1117 if( enableOutput ){
1118 int height;
1119 Blob name;
1120 int nValue = 0;
1121 const char *zValue;
1122 char *z, *zH;
1123 int nElem;
1124 int *aszElem;
1125 char **azElem;
1126 int i;
1127
1128 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1129 Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130 blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
1131 zValue = Th_Fetch(blob_str(&name), &nValue);
1132 nValue = TH1_LEN(nValue);
1133 zH = htmlize(blob_buffer(&name), blob_size(&name));
1134 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1135 free(zH);
1136 sendText(0,z, -1, 0);
1137 free(z);
@@ -1247,11 +1223,11 @@
1223 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1224 }
1225 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1226 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1227 z = argv[1];
1228 size = TH1_LEN(argl[1]);
1229 for(n=1, i=0; i<size; i++){
1230 if( z[i]=='\n' ){
1231 n++;
1232 if( n>=iMax ) break;
1233 }
@@ -1407,11 +1383,12 @@
1383 return TH_OK;
1384 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1385 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1386 return TH_OK;
1387 }else{
1388 Th_ErrorMessage(interp, "unsupported global state:",
1389 argv[1], TH1_LEN(argl[1]));
1390 return TH_ERROR;
1391 }
1392 }
1393
1394 /*
@@ -1426,17 +1403,21 @@
1403 int argc,
1404 const char **argv,
1405 int *argl
1406 ){
1407 const char *zDefault = 0;
1408 const char *zVal;
1409 int sz;
1410 if( argc!=2 && argc!=3 ){
1411 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1412 }
1413 if( argc==3 ){
1414 zDefault = argv[2];
1415 }
1416 zVal = cgi_parameter(argv[1], zDefault);
1417 sz = th_strlen(zVal);
1418 Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
1419 return TH_OK;
1420 }
1421
1422 /*
1423 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
1829 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1830 Th_SetResult(interp, zUTime, -1);
1831 return TH_OK;
1832 }
1833
1834 /*
1835 ** TH1 command: taint STRING
1836 **
1837 ** Return a copy of STRING that is marked as tainted.
1838 */
1839 static int taintCmd(
1840 Th_Interp *interp,
1841 void *p,
1842 int argc,
1843 const char **argv,
1844 int *argl
1845 ){
1846 if( argc!=2 ){
1847 return Th_WrongNumArgs(interp, "STRING");
1848 }
1849 Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850 return TH_OK;
1851 }
1852
1853 /*
1854 ** TH1 command: untaint STRING
1855 **
1856 ** Return a copy of STRING that is marked as untainted.
1857 */
1858 static int untaintCmd(
1859 Th_Interp *interp,
1860 void *p,
1861 int argc,
1862 const char **argv,
1863 int *argl
1864 ){
1865 if( argc!=2 ){
1866 return Th_WrongNumArgs(interp, "STRING");
1867 }
1868 Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869 return TH_OK;
1870 }
1871
1872 /*
1873 ** TH1 command: randhex N
1874 **
1875 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
1941 int res = TH_OK;
1942 int nVar;
1943 char *zErr = 0;
1944 int noComplain = 0;
1945
1946 if( argc>3 && TH1_LEN(argl[1])==11
1947 && strncmp(argv[1], "-nocomplain", 11)==0
1948 ){
1949 argc--;
1950 argv++;
1951 argl++;
1952 noComplain = 1;
1953 }
@@ -1939,15 +1959,22 @@
1959 Th_ErrorMessage(interp, "database is not open", 0, 0);
1960 return TH_ERROR;
1961 }
1962 zSql = argv[1];
1963 nSql = argl[1];
1964 if( TH1_TAINTED(nSql) ){
1965 if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966 return TH_ERROR;
1967 }
1968 nSql = TH1_LEN(nSql);
1969 }
1970
1971 while( res==TH_OK && nSql>0 ){
1972 zErr = 0;
1973 report_restrict_sql(&zErr);
1974 g.dbIgnoreErrors++;
1975 rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
1976 g.dbIgnoreErrors--;
1977 report_unrestrict_sql();
1978 if( rc!=0 || zErr!=0 ){
1979 if( noComplain ) return TH_OK;
1980 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
1991 int szVar = zVar ? th_strlen(zVar) : 0;
1992 if( szVar>1 && zVar[0]=='$'
1993 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1994 int nVal;
1995 const char *zVal = Th_GetResult(interp, &nVal);
1996 sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
1997 }
1998 }
1999 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
2000 int nCol = sqlite3_column_count(pStmt);
2001 for(i=0; i<nCol; i++){
2002 const char *zCol = sqlite3_column_name(pStmt, i);
2003 int szCol = th_strlen(zCol);
2004 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
2005 int szVal = sqlite3_column_bytes(pStmt, i);
2006 Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
2007 }
2008 if( g.thTrace ){
2009 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
2010 }
2011 res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
2012 if( g.thTrace ){
2013 int nTrRes;
2014 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2015 Th_Trace("[query_eval] => %h {%#h}<br>\n",
2016 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
2017 }
2018 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
2019 }
2020 rc = sqlite3_finalize(pStmt);
2021 if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
2065 Th_SetResult(interp, 0, 0);
2066 rc = TH_OK;
2067 }
2068 if( g.thTrace ){
2069 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2070 TH1_LEN(argl[nArg]), argv[nArg], rc);
2071 }
2072 return rc;
2073 }
2074
2075 /*
@@ -2121,11 +2148,11 @@
2148 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2149 }
2150 zErr = re_compile(&pRe, argv[nArg], noCase);
2151 if( !zErr ){
2152 Th_SetResultInt(interp, re_match(pRe,
2153 (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
2154 rc = TH_OK;
2155 }else{
2156 Th_SetResult(interp, zErr, -1);
2157 rc = TH_ERROR;
2158 }
@@ -2160,11 +2187,11 @@
2187 UrlData urlData;
2188
2189 if( argc<2 || argc>5 ){
2190 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2191 }
2192 if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
2193 fAsynchronous = 1; nArg++;
2194 }
2195 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2196 if( nArg+1!=argc && nArg+2!=argc ){
2197 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
2216 return TH_ERROR;
2217 }
2218 re_free(pRe);
2219 blob_zero(&payload);
2220 if( nArg+2==argc ){
2221 blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
2222 zType = "POST";
2223 }else{
2224 zType = "GET";
2225 }
2226 if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
2295 if( argc!=2 ){
2296 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2297 }
2298 pOrig = Th_SetOutputBlob(&out);
2299 zStr = argv[1];
2300 nStr = TH1_LEN(argl[1]);
2301 rc = Th_Eval(g.interp, 0, zStr, nStr);
2302 Th_SetOutputBlob(pOrig);
2303 if(0==rc){
2304 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2305 }
@@ -2356,11 +2383,10 @@
2383 {"copybtn", copybtnCmd, 0},
2384 {"date", dateCmd, 0},
2385 {"decorate", wikiCmd, (void*)&aFlags[2]},
2386 {"defHeader", defHeaderCmd, 0},
2387 {"dir", dirCmd, 0},
 
2388 {"enable_output", enableOutputCmd, 0},
2389 {"encode64", encode64Cmd, 0},
2390 {"getParameter", getParameterCmd, 0},
2391 {"glob_match", globMatchCmd, 0},
2392 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
2413 {"setting", settingCmd, 0},
2414 {"styleFooter", styleFooterCmd, 0},
2415 {"styleHeader", styleHeaderCmd, 0},
2416 {"styleScript", styleScriptCmd, 0},
2417 {"submenu", submenuCmd, 0},
2418 {"taint", taintCmd, 0},
2419 {"tclReady", tclReadyCmd, 0},
2420 {"trace", traceCmd, 0},
2421 {"stime", stimeCmd, 0},
2422 {"untaint", untaintCmd, 0},
2423 {"unversioned", unversionedCmd, 0},
2424 {"utime", utimeCmd, 0},
2425 {"verifyCsrf", verifyCsrfCmd, 0},
2426 {"verifyLogin", verifyLoginCmd, 0},
2427 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
2522 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2523 }
2524 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2525 }
2526 }
2527
2528 /*
2529 ** Store a string value in a variable in the interpreter
2530 ** with the "taint" marking, so that TH1 knows that this
2531 ** variable contains content under the control of the remote
2532 ** user and presents a risk of XSS or SQL-injection attacks.
2533 */
2534 void Th_StoreUnsafe(const char *zName, const char *zValue){
2535 Th_FossilInit(TH_INIT_DEFAULT);
2536 if( zValue ){
2537 if( g.thTrace ){
2538 Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539 }
2540 Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541 }
2542 }
2543
2544 /*
2545 ** Appends an element to a TH1 list value. This function is called by the
2546 ** transfer subsystem; therefore, it must be very careful to avoid doing
2547 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
2724 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2725 /*
2726 ** Make sure that the TH1 script error was not caused by a "missing"
2727 ** command hook handler as that is not actually an error condition.
2728 */
2729 nResult = TH1_LEN(nResult);
2730 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2731 sendError(0,zResult, nResult, 0);
2732 }else{
2733 /*
2734 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
2812 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2813 /*
2814 ** Make sure that the TH1 script error was not caused by a "missing"
2815 ** webpage hook handler as that is not actually an error condition.
2816 */
2817 nResult = TH1_LEN(nResult);
2818 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2819 sendError(0,zResult, nResult, 1);
2820 }else{
2821 /*
2822 ** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
2940 }
2941 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2942 z += i+1+n;
2943 i = 0;
2944 zResult = (char*)Th_GetResult(g.interp, &n);
2945 if( !TH1_TAINTED(n)
2946 || encode
2947 || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948 ){
2949 sendText(pOut,(char*)zResult, n, encode);
2950 }
2951 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2952 sendText(pOut,z, i, 0);
2953 z += i+5;
2954 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2955 if( g.thTrace ){
@@ -2907,11 +2958,11 @@
2958 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2959 if( g.thTrace ){
2960 int nTrRes;
2961 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2962 Th_Trace("[render_eval] => %h {%#h}<br>\n",
2963 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
2964 }
2965 if( rc!=TH_OK ) break;
2966 z += i;
2967 if( z[0] ){ z += 6; }
2968 i = 0;
@@ -2949,14 +3000,81 @@
3000 ** e.g. via the "render" script function binding, need to use the
3001 ** pThOut blob in order to avoid out-of-order output if
3002 ** Th_SetOutputBlob() has been called. If it has not been called,
3003 ** pThOut will be 0, which will redirect the output to CGI/stdout,
3004 ** as appropriate. We need to pass on g.th1Flags for the case of
3005 ** recursive calls.
 
3006 */;
3007 }
3008
3009 /*
3010 ** SETTING: vuln-report width=8 default=log
3011 **
3012 ** This setting controls Fossil's behavior when it encounters a potential
3013 ** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014 ** scripts. Choices are:
3015 **
3016 ** off Do nothing. Ignore the vulnerability.
3017 **
3018 ** log Write a report of the problem into the error log.
3019 **
3020 ** block Like "log" but also prevent the offending TH1 command
3021 ** from running.
3022 **
3023 ** fatal Render an error message page instead of the requested
3024 ** page.
3025 */
3026
3027 /*
3028 ** Report misuse of a tainted string in TH1.
3029 **
3030 ** The behavior depends on the vuln-report setting. If "off", this routine
3031 ** is a no-op. Otherwise, right a message into the error log. If
3032 ** vuln-report is "log", that is all that happens. But for any other
3033 ** value of vuln-report, a fatal error is raised.
3034 */
3035 int Th_ReportTaint(
3036 Th_Interp *interp, /* Report error here, if an error is reported */
3037 const char *zWhere, /* Where the tainted string appears */
3038 const char *zStr, /* The tainted string */
3039 int nStr /* Length of the tainted string */
3040 ){
3041 static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042 const char *zVulnType; /* Type of vulnerability */
3043
3044 if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045 if( is_false(zDisp) ) return 0;
3046 if( strstr(zWhere,"SQL")!=0 ){
3047 zVulnType = "SQL-injection";
3048 }else{
3049 zVulnType = "XSS";
3050 }
3051 nStr = TH1_LEN(nStr);
3052 fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053 zVulnType, zWhere, nStr, zStr);
3054 if( strcmp(zDisp,"log")==0 ){
3055 return 0;
3056 }
3057 if( strcmp(zDisp,"block")==0 ){
3058 char *z = mprintf("tainted %s: \"", zWhere);
3059 Th_ErrorMessage(interp, z, zStr, nStr);
3060 fossil_free(z);
3061 }else{
3062 char *z = mprintf("%#h", nStr, zStr);
3063 zDisp = "off";
3064 cgi_reset_content();
3065 style_submenu_enable(0);
3066 style_set_current_feature("error");
3067 style_header("Configuration Error");
3068 @ <p>Error in a TH1 configuration script:
3069 @ tainted %h(zWhere): "%z(z)"
3070 style_finish_page();
3071 cgi_reply();
3072 fossil_exit(1);
3073 }
3074 return 1;
3075 }
3076
3077 /*
3078 ** COMMAND: test-th-render
3079 **
3080 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
3110 if( find_option("set-user-caps", 0, 0)!=0 ){
3111 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3112 login_set_capabilities(zCap ? zCap : "sx", 0);
3113 g.useLocalauth = 1;
3114 }
3115 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3116 verify_all_options();
3117 if( g.argc<3 ){
3118 usage("FILE");
3119 }
3120 blob_zero(&in);
@@ -3044,10 +3163,11 @@
3163 if( find_option("set-user-caps", 0, 0)!=0 ){
3164 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3165 login_set_capabilities(zCap ? zCap : "sx", 0);
3166 g.useLocalauth = 1;
3167 }
3168 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3169 verify_all_options();
3170 if( g.argc!=3 ){
3171 usage("script");
3172 }
3173 if(file_isfile(g.argv[2], ExtFILE)){
3174
+27 -89
--- 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]); \
@@ -183,11 +183,11 @@
183183
** the only Tcl API functions that MUST be called prior to being able to call
184184
** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
185185
** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
186186
** and Tcl_Finalize function types are also required.
187187
*/
188
-typedef void (tcl_FindExecutableProc) (const char *);
188
+typedef const char *(tcl_FindExecutableProc) (const char *);
189189
typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
190190
typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
191191
typedef void (tcl_FinalizeProc) (void);
192192
193193
/*
@@ -321,27 +321,10 @@
321321
** by the caller. This must be declared here because quite a few functions in
322322
** this file need to use it before it can be defined.
323323
*/
324324
static int createTclInterp(Th_Interp *interp, void *pContext);
325325
326
-/*
327
-** Returns the TH1 return code corresponding to the specified Tcl
328
-** return code.
329
-*/
330
-static int getTh1ReturnCode(
331
- int rc /* The Tcl return code value to convert. */
332
-){
333
- switch( rc ){
334
- case /*0*/ TCL_OK: return /*0*/ TH_OK;
335
- case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR;
336
- case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN;
337
- case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK;
338
- case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
339
- default /*?*/: return /*?*/ rc;
340
- }
341
-}
342
-
343326
/*
344327
** Returns the Tcl return code corresponding to the specified TH1
345328
** return code.
346329
*/
347330
static int getTclReturnCode(
@@ -387,10 +370,12 @@
387370
static char *getTclResult(
388371
Tcl_Interp *pInterp,
389372
int *pN
390373
){
391374
Tcl_Obj *resultPtr;
375
+ Tcl_Size n;
376
+ char *zRes;
392377
393378
if( !pInterp ){ /* This should not happen. */
394379
if( pN ) *pN = 0;
395380
return 0;
396381
}
@@ -397,11 +382,13 @@
397382
resultPtr = Tcl_GetObjResult(pInterp);
398383
if( !resultPtr ){ /* This should not happen either? */
399384
if( pN ) *pN = 0;
400385
return 0;
401386
}
402
- return Tcl_GetStringFromObj(resultPtr, pN);
387
+ zRes = Tcl_GetStringFromObj(resultPtr, &n);
388
+ *pN = (int)n;
389
+ return zRes;
403390
}
404391
405392
/*
406393
** Tcl context information used by TH1. This structure definition has been
407394
** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +403,12 @@
416403
tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
417404
Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
418405
int useObjProc; /* Non-zero if an objProc can be called directly. */
419406
int useTip285; /* Non-zero if TIP #285 is available. */
420407
const char *setup; /* The optional Tcl setup script. */
421
- tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */
422
- void *pPreContext; /* Optional, provided to xPreEval(). */
423
- tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
424
- void *pPostContext; /* Optional, provided to xPostEval(). */
425408
};
426409
427
-/*
428
-** This function calls the configured xPreEval or xPostEval functions, if any.
429
-** May have arbitrary side-effects. This function returns the result of the
430
-** called notification function or the value of the rc argument if there is no
431
-** notification function configured.
432
-*/
433
-static int notifyPreOrPostEval(
434
- int bIsPost,
435
- Th_Interp *interp,
436
- void *ctx,
437
- int argc,
438
- const char **argv,
439
- int *argl,
440
- int rc
441
-){
442
- struct TclContext *tclContext = (struct TclContext *)ctx;
443
- tcl_NotifyProc *xNotifyProc;
444
-
445
- if( !tclContext ){
446
- Th_ErrorMessage(interp,
447
- "invalid Tcl context", (const char *)"", 0);
448
- return TH_ERROR;
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
-
459410
/*
460411
** TH1 command: tclEval arg ?arg ...?
461412
**
462413
** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
463414
** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +436,13 @@
485436
tclInterp = GET_CTX_TCL_INTERP(ctx);
486437
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487438
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488439
return TH_ERROR;
489440
}
490
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
491
- if( rc!=TH_OK ){
492
- return rc;
493
- }
494441
Tcl_Preserve((ClientData)tclInterp);
495442
if( argc==2 ){
496
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
443
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
497444
Tcl_IncrRefCount(objPtr);
498445
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499446
Tcl_DecrRefCount(objPtr); objPtr = 0;
500447
}else{
501448
USE_ARGV_TO_OBJV();
@@ -507,12 +454,10 @@
507454
FREE_ARGV_TO_OBJV();
508455
}
509456
zResult = getTclResult(tclInterp, &nResult);
510457
Th_SetResult(interp, zResult, nResult);
511458
Tcl_Release((ClientData)tclInterp);
512
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
513
- getTh1ReturnCode(rc));
514459
return rc;
515460
}
516461
517462
/*
518463
** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +490,13 @@
545490
tclInterp = GET_CTX_TCL_INTERP(ctx);
546491
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547492
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548493
return TH_ERROR;
549494
}
550
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
551
- if( rc!=TH_OK ){
552
- return rc;
553
- }
554495
Tcl_Preserve((ClientData)tclInterp);
555496
if( argc==2 ){
556
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
497
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
557498
Tcl_IncrRefCount(objPtr);
558499
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559500
Tcl_DecrRefCount(objPtr); objPtr = 0;
560501
}else{
561502
USE_ARGV_TO_OBJV();
@@ -565,21 +506,21 @@
565506
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
566507
Tcl_DecrRefCount(objPtr); objPtr = 0;
567508
FREE_ARGV_TO_OBJV();
568509
}
569510
if( rc==TCL_OK ){
570
- zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
511
+ Tcl_Size szResult = 0;
512
+ zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
513
+ nResult = (int)szResult;
571514
}else{
572515
zResult = getTclResult(tclInterp, &nResult);
573516
}
574
- Th_SetResult(interp, zResult, nResult);
517
+ Th_SetResult(interp, zResult, (int)nResult);
575518
if( rc==TCL_OK ){
576519
Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577520
}
578521
Tcl_Release((ClientData)tclInterp);
579
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
580
- getTh1ReturnCode(rc));
581522
return rc;
582523
}
583524
584525
/*
585526
** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +551,16 @@
610551
tclInterp = GET_CTX_TCL_INTERP(ctx);
611552
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612553
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613554
return TH_ERROR;
614555
}
615
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
616
- if( rc!=TH_OK ){
617
- return rc;
618
- }
619556
Tcl_Preserve((ClientData)tclInterp);
620557
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621558
if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622559
Tcl_Command command;
623560
Tcl_CmdInfo cmdInfo;
624
- Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
561
+ Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
625562
Tcl_IncrRefCount(objPtr);
626563
command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627564
if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628565
Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629566
Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,12 +586,10 @@
649586
FREE_ARGV_TO_OBJV();
650587
}
651588
zResult = getTclResult(tclInterp, &nResult);
652589
Th_SetResult(interp, zResult, nResult);
653590
Tcl_Release((ClientData)tclInterp);
654
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
655
- getTh1ReturnCode(rc));
656591
return rc;
657592
}
658593
659594
/*
660595
** TH1 command: tclIsSafe
@@ -767,10 +702,11 @@
767702
int objc,
768703
Tcl_Obj *const objv[]
769704
){
770705
Th_Interp *th1Interp;
771706
int nArg;
707
+ Tcl_Size szArg;
772708
const char *arg;
773709
int rc;
774710
775711
if( objc!=2 ){
776712
Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +715,15 @@
779715
th1Interp = (Th_Interp *)clientData;
780716
if( !th1Interp ){
781717
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
782718
return TCL_ERROR;
783719
}
784
- arg = Tcl_GetStringFromObj(objv[1], &nArg);
720
+ arg = Tcl_GetStringFromObj(objv[1], &szArg);
721
+ nArg = (int)szArg;
785722
rc = Th_Eval(th1Interp, 0, arg, nArg);
786723
arg = Th_GetResult(th1Interp, &nArg);
787
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
724
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
788725
return getTclReturnCode(rc);
789726
}
790727
791728
/*
792729
** Tcl command: th1Expr arg
@@ -800,10 +737,11 @@
800737
int objc,
801738
Tcl_Obj *const objv[]
802739
){
803740
Th_Interp *th1Interp;
804741
int nArg;
742
+ Tcl_Size szArg;
805743
const char *arg;
806744
int rc;
807745
808746
if( objc!=2 ){
809747
Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +750,14 @@
812750
th1Interp = (Th_Interp *)clientData;
813751
if( !th1Interp ){
814752
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
815753
return TCL_ERROR;
816754
}
817
- arg = Tcl_GetStringFromObj(objv[1], &nArg);
818
- rc = Th_Expr(th1Interp, arg, nArg);
755
+ arg = Tcl_GetStringFromObj(objv[1], &szArg);
756
+ rc = Th_Expr(th1Interp, arg, (int)szArg);
819757
arg = Th_GetResult(th1Interp, &nArg);
820
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
758
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
821759
return getTclReturnCode(rc);
822760
}
823761
824762
/*
825763
** Array of Tcl integration commands. Used when adding or removing the Tcl
826764
--- 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]); \
@@ -183,11 +183,11 @@
183 ** the only Tcl API functions that MUST be called prior to being able to call
184 ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
185 ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
186 ** and Tcl_Finalize function types are also required.
187 */
188 typedef void (tcl_FindExecutableProc) (const char *);
189 typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
190 typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
191 typedef void (tcl_FinalizeProc) (void);
192
193 /*
@@ -321,27 +321,10 @@
321 ** by the caller. This must be declared here because quite a few functions in
322 ** this file need to use it before it can be defined.
323 */
324 static int createTclInterp(Th_Interp *interp, void *pContext);
325
326 /*
327 ** Returns the TH1 return code corresponding to the specified Tcl
328 ** return code.
329 */
330 static int getTh1ReturnCode(
331 int rc /* The Tcl return code value to convert. */
332 ){
333 switch( rc ){
334 case /*0*/ TCL_OK: return /*0*/ TH_OK;
335 case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR;
336 case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN;
337 case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK;
338 case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
339 default /*?*/: return /*?*/ rc;
340 }
341 }
342
343 /*
344 ** Returns the Tcl return code corresponding to the specified TH1
345 ** return code.
346 */
347 static int getTclReturnCode(
@@ -387,10 +370,12 @@
387 static char *getTclResult(
388 Tcl_Interp *pInterp,
389 int *pN
390 ){
391 Tcl_Obj *resultPtr;
 
 
392
393 if( !pInterp ){ /* This should not happen. */
394 if( pN ) *pN = 0;
395 return 0;
396 }
@@ -397,11 +382,13 @@
397 resultPtr = Tcl_GetObjResult(pInterp);
398 if( !resultPtr ){ /* This should not happen either? */
399 if( pN ) *pN = 0;
400 return 0;
401 }
402 return Tcl_GetStringFromObj(resultPtr, pN);
 
 
403 }
404
405 /*
406 ** Tcl context information used by TH1. This structure definition has been
407 ** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +403,12 @@
416 tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
417 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
418 int useObjProc; /* Non-zero if an objProc can be called directly. */
419 int useTip285; /* Non-zero if TIP #285 is available. */
420 const char *setup; /* The optional Tcl setup script. */
421 tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */
422 void *pPreContext; /* Optional, provided to xPreEval(). */
423 tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
424 void *pPostContext; /* Optional, provided to xPostEval(). */
425 };
426
427 /*
428 ** This function calls the configured xPreEval or xPostEval functions, if any.
429 ** May have arbitrary side-effects. This function returns the result of the
430 ** called notification function or the value of the rc argument if there is no
431 ** notification function configured.
432 */
433 static int notifyPreOrPostEval(
434 int bIsPost,
435 Th_Interp *interp,
436 void *ctx,
437 int argc,
438 const char **argv,
439 int *argl,
440 int rc
441 ){
442 struct TclContext *tclContext = (struct TclContext *)ctx;
443 tcl_NotifyProc *xNotifyProc;
444
445 if( !tclContext ){
446 Th_ErrorMessage(interp,
447 "invalid Tcl context", (const char *)"", 0);
448 return TH_ERROR;
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 /*
460 ** TH1 command: tclEval arg ?arg ...?
461 **
462 ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
463 ** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +436,13 @@
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,12 +454,10 @@
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 /*
518 ** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +490,13 @@
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();
@@ -565,21 +506,21 @@
565 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
566 Tcl_DecrRefCount(objPtr); objPtr = 0;
567 FREE_ARGV_TO_OBJV();
568 }
569 if( rc==TCL_OK ){
570 zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
 
 
571 }else{
572 zResult = getTclResult(tclInterp, &nResult);
573 }
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 /*
585 ** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +551,16 @@
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,12 +586,10 @@
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 /*
660 ** TH1 command: tclIsSafe
@@ -767,10 +702,11 @@
767 int objc,
768 Tcl_Obj *const objv[]
769 ){
770 Th_Interp *th1Interp;
771 int nArg;
 
772 const char *arg;
773 int rc;
774
775 if( objc!=2 ){
776 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +715,15 @@
779 th1Interp = (Th_Interp *)clientData;
780 if( !th1Interp ){
781 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
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
@@ -800,10 +737,11 @@
800 int objc,
801 Tcl_Obj *const objv[]
802 ){
803 Th_Interp *th1Interp;
804 int nArg;
 
805 const char *arg;
806 int rc;
807
808 if( objc!=2 ){
809 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +750,14 @@
812 th1Interp = (Th_Interp *)clientData;
813 if( !th1Interp ){
814 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
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]); \
@@ -183,11 +183,11 @@
183 ** the only Tcl API functions that MUST be called prior to being able to call
184 ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
185 ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
186 ** and Tcl_Finalize function types are also required.
187 */
188 typedef const char *(tcl_FindExecutableProc) (const char *);
189 typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
190 typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
191 typedef void (tcl_FinalizeProc) (void);
192
193 /*
@@ -321,27 +321,10 @@
321 ** by the caller. This must be declared here because quite a few functions in
322 ** this file need to use it before it can be defined.
323 */
324 static int createTclInterp(Th_Interp *interp, void *pContext);
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326 /*
327 ** Returns the Tcl return code corresponding to the specified TH1
328 ** return code.
329 */
330 static int getTclReturnCode(
@@ -387,10 +370,12 @@
370 static char *getTclResult(
371 Tcl_Interp *pInterp,
372 int *pN
373 ){
374 Tcl_Obj *resultPtr;
375 Tcl_Size n;
376 char *zRes;
377
378 if( !pInterp ){ /* This should not happen. */
379 if( pN ) *pN = 0;
380 return 0;
381 }
@@ -397,11 +382,13 @@
382 resultPtr = Tcl_GetObjResult(pInterp);
383 if( !resultPtr ){ /* This should not happen either? */
384 if( pN ) *pN = 0;
385 return 0;
386 }
387 zRes = Tcl_GetStringFromObj(resultPtr, &n);
388 *pN = (int)n;
389 return zRes;
390 }
391
392 /*
393 ** Tcl context information used by TH1. This structure definition has been
394 ** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +403,12 @@
403 tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
404 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
405 int useObjProc; /* Non-zero if an objProc can be called directly. */
406 int useTip285; /* Non-zero if TIP #285 is available. */
407 const char *setup; /* The optional Tcl setup script. */
 
 
 
 
408 };
409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410 /*
411 ** TH1 command: tclEval arg ?arg ...?
412 **
413 ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
414 ** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +436,13 @@
436 tclInterp = GET_CTX_TCL_INTERP(ctx);
437 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
438 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
439 return TH_ERROR;
440 }
 
 
 
 
441 Tcl_Preserve((ClientData)tclInterp);
442 if( argc==2 ){
443 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
444 Tcl_IncrRefCount(objPtr);
445 rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
446 Tcl_DecrRefCount(objPtr); objPtr = 0;
447 }else{
448 USE_ARGV_TO_OBJV();
@@ -507,12 +454,10 @@
454 FREE_ARGV_TO_OBJV();
455 }
456 zResult = getTclResult(tclInterp, &nResult);
457 Th_SetResult(interp, zResult, nResult);
458 Tcl_Release((ClientData)tclInterp);
 
 
459 return rc;
460 }
461
462 /*
463 ** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +490,13 @@
490 tclInterp = GET_CTX_TCL_INTERP(ctx);
491 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
492 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
493 return TH_ERROR;
494 }
 
 
 
 
495 Tcl_Preserve((ClientData)tclInterp);
496 if( argc==2 ){
497 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
498 Tcl_IncrRefCount(objPtr);
499 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
500 Tcl_DecrRefCount(objPtr); objPtr = 0;
501 }else{
502 USE_ARGV_TO_OBJV();
@@ -565,21 +506,21 @@
506 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
507 Tcl_DecrRefCount(objPtr); objPtr = 0;
508 FREE_ARGV_TO_OBJV();
509 }
510 if( rc==TCL_OK ){
511 Tcl_Size szResult = 0;
512 zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
513 nResult = (int)szResult;
514 }else{
515 zResult = getTclResult(tclInterp, &nResult);
516 }
517 Th_SetResult(interp, zResult, (int)nResult);
518 if( rc==TCL_OK ){
519 Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
520 }
521 Tcl_Release((ClientData)tclInterp);
 
 
522 return rc;
523 }
524
525 /*
526 ** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +551,16 @@
551 tclInterp = GET_CTX_TCL_INTERP(ctx);
552 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
553 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
554 return TH_ERROR;
555 }
 
 
 
 
556 Tcl_Preserve((ClientData)tclInterp);
557 #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
558 if( GET_CTX_TCL_USEOBJPROC(ctx) ){
559 Tcl_Command command;
560 Tcl_CmdInfo cmdInfo;
561 Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
562 Tcl_IncrRefCount(objPtr);
563 command = Tcl_GetCommandFromObj(tclInterp, objPtr);
564 if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
565 Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
566 Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,12 +586,10 @@
586 FREE_ARGV_TO_OBJV();
587 }
588 zResult = getTclResult(tclInterp, &nResult);
589 Th_SetResult(interp, zResult, nResult);
590 Tcl_Release((ClientData)tclInterp);
 
 
591 return rc;
592 }
593
594 /*
595 ** TH1 command: tclIsSafe
@@ -767,10 +702,11 @@
702 int objc,
703 Tcl_Obj *const objv[]
704 ){
705 Th_Interp *th1Interp;
706 int nArg;
707 Tcl_Size szArg;
708 const char *arg;
709 int rc;
710
711 if( objc!=2 ){
712 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +715,15 @@
715 th1Interp = (Th_Interp *)clientData;
716 if( !th1Interp ){
717 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
718 return TCL_ERROR;
719 }
720 arg = Tcl_GetStringFromObj(objv[1], &szArg);
721 nArg = (int)szArg;
722 rc = Th_Eval(th1Interp, 0, arg, nArg);
723 arg = Th_GetResult(th1Interp, &nArg);
724 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
725 return getTclReturnCode(rc);
726 }
727
728 /*
729 ** Tcl command: th1Expr arg
@@ -800,10 +737,11 @@
737 int objc,
738 Tcl_Obj *const objv[]
739 ){
740 Th_Interp *th1Interp;
741 int nArg;
742 Tcl_Size szArg;
743 const char *arg;
744 int rc;
745
746 if( objc!=2 ){
747 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +750,14 @@
750 th1Interp = (Th_Interp *)clientData;
751 if( !th1Interp ){
752 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
753 return TCL_ERROR;
754 }
755 arg = Tcl_GetStringFromObj(objv[1], &szArg);
756 rc = Th_Expr(th1Interp, arg, (int)szArg);
757 arg = Th_GetResult(th1Interp, &nArg);
758 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
759 return getTclReturnCode(rc);
760 }
761
762 /*
763 ** Array of Tcl integration commands. Used when adding or removing the Tcl
764
+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
+14 -10
--- src/tkt.c
+++ src/tkt.c
@@ -188,17 +188,19 @@
188188
*/
189189
static void initializeVariablesFromDb(void){
190190
const char *zName;
191191
Stmt q;
192192
int i, n, size, j;
193
+ const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";
193194
194195
zName = PD("name","-none-");
195196
db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
196
- "datetime(tkt_ctime,toLocal()) AS tkt_datetime_creation, "
197
+ "datetime(%s,toLocal()) AS tkt_datetime_creation, "
197198
"julianday('now') - tkt_mtime, "
198
- "julianday('now') - tkt_ctime, *"
199
+ "julianday('now') - %s, *"
199200
" FROM ticket WHERE tkt_uuid GLOB '%q*'",
201
+ zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
200202
zName);
201203
if( db_step(&q)==SQLITE_ROW ){
202204
n = db_column_count(&q);
203205
for(i=0; i<n; i++){
204206
const char *zVal = db_column_text(&q, i);
@@ -210,10 +212,11 @@
210212
zVal = zRevealed = db_reveal(zVal);
211213
}
212214
if( (j = fieldId(zName))>=0 ){
213215
aField[j].zValue = mprintf("%s", zVal);
214216
}else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
217
+ /* TICKET table columns that begin with "tkt_" are always safe */
215218
Th_Store(zName, zVal);
216219
}
217220
free(zRevealed);
218221
}
219222
Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
@@ -220,11 +223,11 @@
220223
Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
221224
}
222225
db_finalize(&q);
223226
for(i=0; i<nField; i++){
224227
if( Th_Fetch(aField[i].zName, &size)==0 ){
225
- Th_Store(aField[i].zName, aField[i].zValue);
228
+ Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
226229
}
227230
}
228231
}
229232
230233
/*
@@ -233,11 +236,11 @@
233236
static void initializeVariablesFromCGI(void){
234237
int i;
235238
const char *z;
236239
237240
for(i=0; (z = cgi_parameter_name(i))!=0; i++){
238
- Th_Store(z, P(z));
241
+ Th_StoreUnsafe(z, P(z));
239242
}
240243
}
241244
242245
/*
243246
** Information about a single J-card
@@ -818,15 +821,15 @@
818821
if( argc!=3 ){
819822
return Th_WrongNumArgs(interp, "append_field FIELD STRING");
820823
}
821824
if( g.thTrace ){
822825
Th_Trace("append_field %#h {%#h}<br>\n",
823
- argl[1], argv[1], argl[2], argv[2]);
826
+ TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
824827
}
825828
for(idx=0; idx<nField; idx++){
826
- if( memcmp(aField[idx].zName, argv[1], argl[1])==0
827
- && aField[idx].zName[argl[1]]==0 ){
829
+ if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
830
+ && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
828831
break;
829832
}
830833
}
831834
if( idx>=nField ){
832835
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -938,10 +941,11 @@
938941
const char *zValue;
939942
int nValue;
940943
if( aField[i].zAppend ) continue;
941944
zValue = Th_Fetch(aField[i].zName, &nValue);
942945
if( zValue ){
946
+ nValue = TH1_LEN(nValue);
943947
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
944948
if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
945949
|| memcmp(zValue, aField[i].zValue, nValue)!=0
946950
||(int)strlen(aField[i].zValue)!=nValue
947951
){
@@ -1040,16 +1044,16 @@
10401044
if( uid ){
10411045
char * zEmail =
10421046
db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
10431047
uid);
10441048
if( zEmail ){
1045
- Th_Store("private_contact", zEmail);
1049
+ Th_StoreUnsafe("private_contact", zEmail);
10461050
fossil_free(zEmail);
10471051
}
10481052
}
10491053
}
1050
- Th_Store("login", login_name());
1054
+ Th_StoreUnsafe("login", login_name());
10511055
Th_Store("date", db_text(0, "SELECT datetime('now')"));
10521056
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
10531057
(void*)&zNewUuid, 0);
10541058
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
10551059
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1120,11 +1124,11 @@
11201124
initializeVariablesFromDb();
11211125
if( g.zPath[0]=='d' ) showAllFields();
11221126
form_begin(0, "%R/%s", g.zPath);
11231127
@ <input type="hidden" name="name" value="%s(zName)">
11241128
zScript = ticket_editpage_code();
1125
- Th_Store("login", login_name());
1129
+ Th_StoreUnsafe("login", login_name());
11261130
Th_Store("date", db_text(0, "SELECT datetime('now')"));
11271131
Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
11281132
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
11291133
if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
11301134
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
11311135
--- src/tkt.c
+++ src/tkt.c
@@ -188,17 +188,19 @@
188 */
189 static void initializeVariablesFromDb(void){
190 const char *zName;
191 Stmt q;
192 int i, n, size, j;
 
193
194 zName = PD("name","-none-");
195 db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
196 "datetime(tkt_ctime,toLocal()) AS tkt_datetime_creation, "
197 "julianday('now') - tkt_mtime, "
198 "julianday('now') - tkt_ctime, *"
199 " FROM ticket WHERE tkt_uuid GLOB '%q*'",
 
200 zName);
201 if( db_step(&q)==SQLITE_ROW ){
202 n = db_column_count(&q);
203 for(i=0; i<n; i++){
204 const char *zVal = db_column_text(&q, i);
@@ -210,10 +212,11 @@
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,11 +223,11 @@
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 +236,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 +821,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 +941,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 +1044,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 +1124,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
@@ -188,17 +188,19 @@
188 */
189 static void initializeVariablesFromDb(void){
190 const char *zName;
191 Stmt q;
192 int i, n, size, j;
193 const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";
194
195 zName = PD("name","-none-");
196 db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
197 "datetime(%s,toLocal()) AS tkt_datetime_creation, "
198 "julianday('now') - tkt_mtime, "
199 "julianday('now') - %s, *"
200 " FROM ticket WHERE tkt_uuid GLOB '%q*'",
201 zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
202 zName);
203 if( db_step(&q)==SQLITE_ROW ){
204 n = db_column_count(&q);
205 for(i=0; i<n; i++){
206 const char *zVal = db_column_text(&q, i);
@@ -210,10 +212,11 @@
212 zVal = zRevealed = db_reveal(zVal);
213 }
214 if( (j = fieldId(zName))>=0 ){
215 aField[j].zValue = mprintf("%s", zVal);
216 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
217 /* TICKET table columns that begin with "tkt_" are always safe */
218 Th_Store(zName, zVal);
219 }
220 free(zRevealed);
221 }
222 Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
@@ -220,11 +223,11 @@
223 Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
224 }
225 db_finalize(&q);
226 for(i=0; i<nField; i++){
227 if( Th_Fetch(aField[i].zName, &size)==0 ){
228 Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
229 }
230 }
231 }
232
233 /*
@@ -233,11 +236,11 @@
236 static void initializeVariablesFromCGI(void){
237 int i;
238 const char *z;
239
240 for(i=0; (z = cgi_parameter_name(i))!=0; i++){
241 Th_StoreUnsafe(z, P(z));
242 }
243 }
244
245 /*
246 ** Information about a single J-card
@@ -818,15 +821,15 @@
821 if( argc!=3 ){
822 return Th_WrongNumArgs(interp, "append_field FIELD STRING");
823 }
824 if( g.thTrace ){
825 Th_Trace("append_field %#h {%#h}<br>\n",
826 TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
827 }
828 for(idx=0; idx<nField; idx++){
829 if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
830 && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
831 break;
832 }
833 }
834 if( idx>=nField ){
835 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -938,10 +941,11 @@
941 const char *zValue;
942 int nValue;
943 if( aField[i].zAppend ) continue;
944 zValue = Th_Fetch(aField[i].zName, &nValue);
945 if( zValue ){
946 nValue = TH1_LEN(nValue);
947 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
948 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
949 || memcmp(zValue, aField[i].zValue, nValue)!=0
950 ||(int)strlen(aField[i].zValue)!=nValue
951 ){
@@ -1040,16 +1044,16 @@
1044 if( uid ){
1045 char * zEmail =
1046 db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
1047 uid);
1048 if( zEmail ){
1049 Th_StoreUnsafe("private_contact", zEmail);
1050 fossil_free(zEmail);
1051 }
1052 }
1053 }
1054 Th_StoreUnsafe("login", login_name());
1055 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1056 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
1057 (void*)&zNewUuid, 0);
1058 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
1059 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1120,11 +1124,11 @@
1124 initializeVariablesFromDb();
1125 if( g.zPath[0]=='d' ) showAllFields();
1126 form_begin(0, "%R/%s", g.zPath);
1127 @ <input type="hidden" name="name" value="%s(zName)">
1128 zScript = ticket_editpage_code();
1129 Th_StoreUnsafe("login", login_name());
1130 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1131 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
1132 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
1133 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
1134 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
1135
+15 -15
--- 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>
@@ -555,18 +555,18 @@
555555
@ set urlfoundin [httpize $foundin]
556556
@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
557557
@ if [regexp $tagpattern $foundin] {
558558
@ query {SELECT count(*) AS match FROM tag
559559
@ WHERE tagname=concat('sym-',$foundin)} {
560
-@ if {$match} {set versionlink "/timeline?t=$urlfoundin"}
560
+@ if {$match} {set versionlink "timeline?t=$urlfoundin"}
561561
@ }
562562
@ }
563563
@ set hashpattern {^[0-9a-f]+$}
564564
@ if [regexp $hashpattern $foundin] {
565565
@ set pattern $foundin*
566566
@ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
567
-@ if {$match} {set versionlink "/info/$urlfoundin"}
567
+@ if {$match} {set versionlink "info/$urlfoundin"}
568568
@ }
569569
@ }
570570
@ if {$versionlink eq ""} {
571571
@ puts $foundin
572572
@ } else {
@@ -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>
@@ -555,18 +555,18 @@
555 @ set urlfoundin [httpize $foundin]
556 @ set tagpattern {^[-0-9A-Za-z_\\.]+$}
557 @ if [regexp $tagpattern $foundin] {
558 @ query {SELECT count(*) AS match FROM tag
559 @ WHERE tagname=concat('sym-',$foundin)} {
560 @ if {$match} {set versionlink "/timeline?t=$urlfoundin"}
561 @ }
562 @ }
563 @ set hashpattern {^[0-9a-f]+$}
564 @ if [regexp $hashpattern $foundin] {
565 @ set pattern $foundin*
566 @ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
567 @ if {$match} {set versionlink "/info/$urlfoundin"}
568 @ }
569 @ }
570 @ if {$versionlink eq ""} {
571 @ puts $foundin
572 @ } else {
@@ -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>
@@ -555,18 +555,18 @@
555 @ set urlfoundin [httpize $foundin]
556 @ set tagpattern {^[-0-9A-Za-z_\\.]+$}
557 @ if [regexp $tagpattern $foundin] {
558 @ query {SELECT count(*) AS match FROM tag
559 @ WHERE tagname=concat('sym-',$foundin)} {
560 @ if {$match} {set versionlink "timeline?t=$urlfoundin"}
561 @ }
562 @ }
563 @ set hashpattern {^[0-9a-f]+$}
564 @ if [regexp $hashpattern $foundin] {
565 @ set pattern $foundin*
566 @ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
567 @ if {$match} {set versionlink "info/$urlfoundin"}
568 @ }
569 @ }
570 @ if {$versionlink eq ""} {
571 @ puts $foundin
572 @ } else {
@@ -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
+79 -41
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326326
**
327327
** > fossil user contact USERNAME ?CONTACT-INFO?
328328
**
329329
** Query or set contact information for user USERNAME
330330
**
331
-** > fossil user default ?USERNAME?
331
+** > fossil user default ?OPTIONS? ?USERNAME?
332332
**
333333
** Query or set the default user. The default user is the
334
-** user for command-line interaction.
334
+** user for command-line interaction. If USERNAME is an
335
+** empty string, then the default user is unset from the
336
+** repository and will subsequently be determined by the -U
337
+** command-line option or by environment variables
338
+** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
339
+** OPTIONS:
340
+**
341
+** -v|--verbose Show how the default user is computed
335342
**
336343
** > fossil user list | ls
337344
**
338345
** List all users known to the repository
339346
**
@@ -385,21 +392,50 @@
385392
&login, zPw, &caps, &contact
386393
);
387394
db_protect_pop();
388395
free(zPw);
389396
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
390
- if( g.argc==3 ){
391
- user_select();
392
- fossil_print("%s\n", g.zLogin);
393
- }else{
394
- if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
395
- fossil_fatal("no such user: %s", g.argv[3]);
396
- }
397
- if( g.localOpen ){
398
- db_lset("default-user", g.argv[3]);
399
- }else{
400
- db_set("default-user", g.argv[3], 0);
397
+ int eVerbose = find_option("verbose","v",0)!=0;
398
+ verify_all_options();
399
+ if( g.argc>3 ){
400
+ const char *zUser = g.argv[3];
401
+ if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
402
+ db_begin_transaction();
403
+ if( g.localOpen ){
404
+ db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
405
+ }
406
+ db_unset("default-user",0);
407
+ db_commit_transaction();
408
+ }else{
409
+ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
410
+ fossil_fatal("no such user: %s", g.argv[3]);
411
+ }
412
+ if( g.localOpen ){
413
+ db_lset("default-user", g.argv[3]);
414
+ }else{
415
+ db_set("default-user", g.argv[3], 0);
416
+ }
417
+ }
418
+ }
419
+ if( g.argc==3 || eVerbose ){
420
+ int eHow = user_select();
421
+ const char *zHow = "???";
422
+ switch( eHow ){
423
+ case 1: zHow = "-U option"; break;
424
+ case 2: zHow = "previously set"; break;
425
+ case 3: zHow = "local check-out"; break;
426
+ case 4: zHow = "repository"; break;
427
+ case 5: zHow = "FOSSIL_USER"; break;
428
+ case 6: zHow = "USER"; break;
429
+ case 7: zHow = "LOGNAME"; break;
430
+ case 8: zHow = "USERNAME"; break;
431
+ case 9: zHow = "URL"; break;
432
+ }
433
+ if( eVerbose ){
434
+ fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
435
+ }else{
436
+ fossil_print("%s\n", g.zLogin);
401437
}
402438
}
403439
}else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
404440
( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
405441
Stmt q;
@@ -496,52 +532,54 @@
496532
/*
497533
** Figure out what user is at the controls.
498534
**
499535
** (1) Use the --user and -U command-line options.
500536
**
501
-** (2) If the local database is open, check in VVAR.
502
-**
503
-** (3) Check the default user in the repository
504
-**
505
-** (4) Try the FOSSIL_USER environment variable.
506
-**
507
-** (5) Try the USER environment variable.
508
-**
509
-** (6) Try the LOGNAME environment variable.
510
-**
511
-** (7) Try the USERNAME environment variable.
512
-**
513
-** (8) Check if the user can be extracted from the remote URL.
537
+** (2) The name used for login (if there was a login).
538
+**
539
+** (3) If the local database is open, check in VVAR.
540
+**
541
+** (4) Check the default-user in the repository
542
+**
543
+** (5) Try the FOSSIL_USER environment variable.
544
+**
545
+** (6) Try the USER environment variable.
546
+**
547
+** (7) Try the LOGNAME environment variable.
548
+**
549
+** (8) Try the USERNAME environment variable.
550
+**
551
+** (9) Check if the user can be extracted from the remote URL.
514552
**
515553
** The user name is stored in g.zLogin. The uid is in g.userUid.
516554
*/
517
-void user_select(void){
555
+int user_select(void){
518556
UrlData url;
519
- if( g.userUid ) return;
557
+ if( g.userUid ) return 1;
520558
if( g.zLogin ){
521559
if( attempt_user(g.zLogin)==0 ){
522560
fossil_fatal("no such user: %s", g.zLogin);
523561
}else{
524
- return;
562
+ return 2;
525563
}
526564
}
527565
528
- if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
529
-
530
- if( attempt_user(db_get("default-user", 0)) ) return;
531
-
532
- if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;
533
-
534
- if( attempt_user(fossil_getenv("USER")) ) return;
535
-
536
- if( attempt_user(fossil_getenv("LOGNAME")) ) return;
537
-
538
- if( attempt_user(fossil_getenv("USERNAME")) ) return;
566
+ if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
567
+
568
+ if( attempt_user(db_get("default-user", 0)) ) return 4;
569
+
570
+ if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
571
+
572
+ if( attempt_user(fossil_getenv("USER")) ) return 6;
573
+
574
+ if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
575
+
576
+ if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
539577
540578
memset(&url, 0, sizeof(url));
541579
url_parse_local(0, URL_USE_CONFIG, &url);
542
- if( url.user && attempt_user(url.user) ) return;
580
+ if( url.user && attempt_user(url.user) ) return 9;
543581
544582
fossil_print(
545583
"Cannot figure out who you are! Consider using the --user\n"
546584
"command line option, setting your USER environment variable,\n"
547585
"or setting a default user with \"fossil user default USER\".\n"
548586
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326 **
327 ** > fossil user contact USERNAME ?CONTACT-INFO?
328 **
329 ** Query or set contact information for user USERNAME
330 **
331 ** > fossil user default ?USERNAME?
332 **
333 ** Query or set the default user. The default user is the
334 ** user for command-line interaction.
 
 
 
 
 
 
 
335 **
336 ** > fossil user list | ls
337 **
338 ** List all users known to the repository
339 **
@@ -385,21 +392,50 @@
385 &login, zPw, &caps, &contact
386 );
387 db_protect_pop();
388 free(zPw);
389 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
390 if( g.argc==3 ){
391 user_select();
392 fossil_print("%s\n", g.zLogin);
393 }else{
394 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
395 fossil_fatal("no such user: %s", g.argv[3]);
396 }
397 if( g.localOpen ){
398 db_lset("default-user", g.argv[3]);
399 }else{
400 db_set("default-user", g.argv[3], 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401 }
402 }
403 }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
404 ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
405 Stmt q;
@@ -496,52 +532,54 @@
496 /*
497 ** Figure out what user is at the controls.
498 **
499 ** (1) Use the --user and -U command-line options.
500 **
501 ** (2) If the local database is open, check in VVAR.
502 **
503 ** (3) Check the default user in the repository
504 **
505 ** (4) Try the FOSSIL_USER environment variable.
506 **
507 ** (5) Try the USER environment variable.
508 **
509 ** (6) Try the LOGNAME environment variable.
510 **
511 ** (7) Try the USERNAME environment variable.
512 **
513 ** (8) Check if the user can be extracted from the remote URL.
 
 
514 **
515 ** The user name is stored in g.zLogin. The uid is in g.userUid.
516 */
517 void user_select(void){
518 UrlData url;
519 if( g.userUid ) return;
520 if( g.zLogin ){
521 if( attempt_user(g.zLogin)==0 ){
522 fossil_fatal("no such user: %s", g.zLogin);
523 }else{
524 return;
525 }
526 }
527
528 if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
529
530 if( attempt_user(db_get("default-user", 0)) ) return;
531
532 if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;
533
534 if( attempt_user(fossil_getenv("USER")) ) return;
535
536 if( attempt_user(fossil_getenv("LOGNAME")) ) return;
537
538 if( attempt_user(fossil_getenv("USERNAME")) ) return;
539
540 memset(&url, 0, sizeof(url));
541 url_parse_local(0, URL_USE_CONFIG, &url);
542 if( url.user && attempt_user(url.user) ) return;
543
544 fossil_print(
545 "Cannot figure out who you are! Consider using the --user\n"
546 "command line option, setting your USER environment variable,\n"
547 "or setting a default user with \"fossil user default USER\".\n"
548
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326 **
327 ** > fossil user contact USERNAME ?CONTACT-INFO?
328 **
329 ** Query or set contact information for user USERNAME
330 **
331 ** > fossil user default ?OPTIONS? ?USERNAME?
332 **
333 ** Query or set the default user. The default user is the
334 ** user for command-line interaction. If USERNAME is an
335 ** empty string, then the default user is unset from the
336 ** repository and will subsequently be determined by the -U
337 ** command-line option or by environment variables
338 ** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
339 ** OPTIONS:
340 **
341 ** -v|--verbose Show how the default user is computed
342 **
343 ** > fossil user list | ls
344 **
345 ** List all users known to the repository
346 **
@@ -385,21 +392,50 @@
392 &login, zPw, &caps, &contact
393 );
394 db_protect_pop();
395 free(zPw);
396 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
397 int eVerbose = find_option("verbose","v",0)!=0;
398 verify_all_options();
399 if( g.argc>3 ){
400 const char *zUser = g.argv[3];
401 if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
402 db_begin_transaction();
403 if( g.localOpen ){
404 db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
405 }
406 db_unset("default-user",0);
407 db_commit_transaction();
408 }else{
409 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
410 fossil_fatal("no such user: %s", g.argv[3]);
411 }
412 if( g.localOpen ){
413 db_lset("default-user", g.argv[3]);
414 }else{
415 db_set("default-user", g.argv[3], 0);
416 }
417 }
418 }
419 if( g.argc==3 || eVerbose ){
420 int eHow = user_select();
421 const char *zHow = "???";
422 switch( eHow ){
423 case 1: zHow = "-U option"; break;
424 case 2: zHow = "previously set"; break;
425 case 3: zHow = "local check-out"; break;
426 case 4: zHow = "repository"; break;
427 case 5: zHow = "FOSSIL_USER"; break;
428 case 6: zHow = "USER"; break;
429 case 7: zHow = "LOGNAME"; break;
430 case 8: zHow = "USERNAME"; break;
431 case 9: zHow = "URL"; break;
432 }
433 if( eVerbose ){
434 fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
435 }else{
436 fossil_print("%s\n", g.zLogin);
437 }
438 }
439 }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
440 ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
441 Stmt q;
@@ -496,52 +532,54 @@
532 /*
533 ** Figure out what user is at the controls.
534 **
535 ** (1) Use the --user and -U command-line options.
536 **
537 ** (2) The name used for login (if there was a login).
538 **
539 ** (3) If the local database is open, check in VVAR.
540 **
541 ** (4) Check the default-user in the repository
542 **
543 ** (5) Try the FOSSIL_USER environment variable.
544 **
545 ** (6) Try the USER environment variable.
546 **
547 ** (7) Try the LOGNAME environment variable.
548 **
549 ** (8) Try the USERNAME environment variable.
550 **
551 ** (9) Check if the user can be extracted from the remote URL.
552 **
553 ** The user name is stored in g.zLogin. The uid is in g.userUid.
554 */
555 int user_select(void){
556 UrlData url;
557 if( g.userUid ) return 1;
558 if( g.zLogin ){
559 if( attempt_user(g.zLogin)==0 ){
560 fossil_fatal("no such user: %s", g.zLogin);
561 }else{
562 return 2;
563 }
564 }
565
566 if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
567
568 if( attempt_user(db_get("default-user", 0)) ) return 4;
569
570 if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
571
572 if( attempt_user(fossil_getenv("USER")) ) return 6;
573
574 if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
575
576 if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
577
578 memset(&url, 0, sizeof(url));
579 url_parse_local(0, URL_USE_CONFIG, &url);
580 if( url.user && attempt_user(url.user) ) return 9;
581
582 fossil_print(
583 "Cannot figure out who you are! Consider using the --user\n"
584 "command line option, setting your USER environment variable,\n"
585 "or setting a default user with \"fossil user default USER\".\n"
586
+20 -8
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
11161116
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
11171117
zName, mtime, zHash, sz);
11181118
}
11191119
db_finalize(&uvq);
11201120
}
1121
+
1122
+/*
1123
+** Return a string that contains supplemental information about a
1124
+** "not authorized" error. The string might be empty if no additional
1125
+** information is available.
1126
+*/
1127
+static char *whyNotAuth(void){
1128
+ if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
1129
+ return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
1130
+ }
1131
+ return "";
1132
+}
11211133
11221134
/*
11231135
** Called when there is an attempt to transfer private content to and
11241136
** from a server without authorization.
11251137
*/
11261138
static void server_private_xfer_not_authorized(void){
1127
- @ error not\sauthorized\sto\ssync\sprivate\scontent
1139
+ @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
11281140
}
11291141
11301142
/*
11311143
** Return the common TH1 code to evaluate prior to evaluating any other
11321144
** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
13161328
** Server accepts a file from the client.
13171329
*/
13181330
if( blob_eq(&xfer.aToken[0], "file") ){
13191331
if( !isPush ){
13201332
cgi_reset_content();
1321
- @ error not\sauthorized\sto\swrite
1333
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
13221334
nErr++;
13231335
break;
13241336
}
13251337
xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
13261338
if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
13371349
** Server accepts a compressed file from the client.
13381350
*/
13391351
if( blob_eq(&xfer.aToken[0], "cfile") ){
13401352
if( !isPush ){
13411353
cgi_reset_content();
1342
- @ error not\sauthorized\sto\swrite
1354
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
13431355
nErr++;
13441356
break;
13451357
}
13461358
xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
13471359
if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
14611473
}
14621474
login_check_credentials();
14631475
if( blob_eq(&xfer.aToken[0], "pull") ){
14641476
if( !g.perm.Read ){
14651477
cgi_reset_content();
1466
- @ error not\sauthorized\sto\sread
1478
+ @ error not\sauthorized\sto\sread%s(whyNotAuth())
14671479
nErr++;
14681480
break;
14691481
}
14701482
isPull = 1;
14711483
}else{
14721484
if( !g.perm.Write ){
14731485
if( !isPull ){
14741486
cgi_reset_content();
1475
- @ error not\sauthorized\sto\swrite
1487
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
14761488
nErr++;
14771489
}else{
1478
- @ message pull\sonly\s-\snot\sauthorized\sto\spush
1490
+ @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
14791491
}
14801492
}else{
14811493
isPush = 1;
14821494
}
14831495
}
@@ -1491,11 +1503,11 @@
14911503
int iVers;
14921504
login_check_credentials();
14931505
if( !g.perm.Clone ){
14941506
cgi_reset_content();
14951507
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1496
- @ error not\sauthorized\sto\sclone
1508
+ @ error not\sauthorized\sto\sclone%s(whyNotAuth())
14971509
nErr++;
14981510
break;
14991511
}
15001512
if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
15011513
@ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
15921604
}
15931605
blob_zero(&content);
15941606
blob_extract(xfer.pIn, size, &content);
15951607
if( !g.perm.Admin ){
15961608
cgi_reset_content();
1597
- @ error not\sauthorized\sto\spush\sconfiguration
1609
+ @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
15981610
nErr++;
15991611
break;
16001612
}
16011613
configure_receive(zName, &content, CONFIGSET_ALL);
16021614
blob_reset(&content);
16031615
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
1116 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
1117 zName, mtime, zHash, sz);
1118 }
1119 db_finalize(&uvq);
1120 }
 
 
 
 
 
 
 
 
 
 
 
 
1121
1122 /*
1123 ** Called when there is an attempt to transfer private content to and
1124 ** from a server without authorization.
1125 */
1126 static void server_private_xfer_not_authorized(void){
1127 @ error not\sauthorized\sto\ssync\sprivate\scontent
1128 }
1129
1130 /*
1131 ** Return the common TH1 code to evaluate prior to evaluating any other
1132 ** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
1316 ** Server accepts a file from the client.
1317 */
1318 if( blob_eq(&xfer.aToken[0], "file") ){
1319 if( !isPush ){
1320 cgi_reset_content();
1321 @ error not\sauthorized\sto\swrite
1322 nErr++;
1323 break;
1324 }
1325 xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
1326 if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
1337 ** Server accepts a compressed file from the client.
1338 */
1339 if( blob_eq(&xfer.aToken[0], "cfile") ){
1340 if( !isPush ){
1341 cgi_reset_content();
1342 @ error not\sauthorized\sto\swrite
1343 nErr++;
1344 break;
1345 }
1346 xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
1347 if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
1461 }
1462 login_check_credentials();
1463 if( blob_eq(&xfer.aToken[0], "pull") ){
1464 if( !g.perm.Read ){
1465 cgi_reset_content();
1466 @ error not\sauthorized\sto\sread
1467 nErr++;
1468 break;
1469 }
1470 isPull = 1;
1471 }else{
1472 if( !g.perm.Write ){
1473 if( !isPull ){
1474 cgi_reset_content();
1475 @ error not\sauthorized\sto\swrite
1476 nErr++;
1477 }else{
1478 @ message pull\sonly\s-\snot\sauthorized\sto\spush
1479 }
1480 }else{
1481 isPush = 1;
1482 }
1483 }
@@ -1491,11 +1503,11 @@
1491 int iVers;
1492 login_check_credentials();
1493 if( !g.perm.Clone ){
1494 cgi_reset_content();
1495 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1496 @ error not\sauthorized\sto\sclone
1497 nErr++;
1498 break;
1499 }
1500 if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
1501 @ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
1592 }
1593 blob_zero(&content);
1594 blob_extract(xfer.pIn, size, &content);
1595 if( !g.perm.Admin ){
1596 cgi_reset_content();
1597 @ error not\sauthorized\sto\spush\sconfiguration
1598 nErr++;
1599 break;
1600 }
1601 configure_receive(zName, &content, CONFIGSET_ALL);
1602 blob_reset(&content);
1603
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
1116 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
1117 zName, mtime, zHash, sz);
1118 }
1119 db_finalize(&uvq);
1120 }
1121
1122 /*
1123 ** Return a string that contains supplemental information about a
1124 ** "not authorized" error. The string might be empty if no additional
1125 ** information is available.
1126 */
1127 static char *whyNotAuth(void){
1128 if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
1129 return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
1130 }
1131 return "";
1132 }
1133
1134 /*
1135 ** Called when there is an attempt to transfer private content to and
1136 ** from a server without authorization.
1137 */
1138 static void server_private_xfer_not_authorized(void){
1139 @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
1140 }
1141
1142 /*
1143 ** Return the common TH1 code to evaluate prior to evaluating any other
1144 ** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
1328 ** Server accepts a file from the client.
1329 */
1330 if( blob_eq(&xfer.aToken[0], "file") ){
1331 if( !isPush ){
1332 cgi_reset_content();
1333 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1334 nErr++;
1335 break;
1336 }
1337 xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
1338 if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
1349 ** Server accepts a compressed file from the client.
1350 */
1351 if( blob_eq(&xfer.aToken[0], "cfile") ){
1352 if( !isPush ){
1353 cgi_reset_content();
1354 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1355 nErr++;
1356 break;
1357 }
1358 xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
1359 if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
1473 }
1474 login_check_credentials();
1475 if( blob_eq(&xfer.aToken[0], "pull") ){
1476 if( !g.perm.Read ){
1477 cgi_reset_content();
1478 @ error not\sauthorized\sto\sread%s(whyNotAuth())
1479 nErr++;
1480 break;
1481 }
1482 isPull = 1;
1483 }else{
1484 if( !g.perm.Write ){
1485 if( !isPull ){
1486 cgi_reset_content();
1487 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1488 nErr++;
1489 }else{
1490 @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
1491 }
1492 }else{
1493 isPush = 1;
1494 }
1495 }
@@ -1491,11 +1503,11 @@
1503 int iVers;
1504 login_check_credentials();
1505 if( !g.perm.Clone ){
1506 cgi_reset_content();
1507 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1508 @ error not\sauthorized\sto\sclone%s(whyNotAuth())
1509 nErr++;
1510 break;
1511 }
1512 if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
1513 @ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
1604 }
1605 blob_zero(&content);
1606 blob_extract(xfer.pIn, size, &content);
1607 if( !g.perm.Admin ){
1608 cgi_reset_content();
1609 @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
1610 nErr++;
1611 break;
1612 }
1613 configure_receive(zName, &content, CONFIGSET_ALL);
1614 blob_reset(&content);
1615
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356356
mtime-changes \
357357
mv-rm-files \
358358
pgp-command \
359359
preferred-diff-type \
360360
proxy \
361
+ raw-bgcolor \
361362
redirect-to-https \
362363
relative-paths \
363364
repo-cksum \
364365
repolist-skin \
365366
robot-restrict \
@@ -373,13 +374,19 @@
373374
ssl-identity \
374375
tclsh \
375376
th1-setup \
376377
th1-uri-regexp \
377378
ticket-default-report \
379
+ timeline-hard-newlines \
380
+ timeline-plaintext \
381
+ timeline-truncate-at-blank \
382
+ timeline-tslink-info \
378383
timeline-utc \
379384
user-color-map \
385
+ verify-comments \
380386
uv-sync \
387
+ vuln-report \
381388
web-browser]
382389
383390
fossil test-th-eval "hasfeature legacyMvRm"
384391
385392
if {[normalize_result] eq "1"} {
386393
387394
ADDED test/th1-taint.test
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356 mtime-changes \
357 mv-rm-files \
358 pgp-command \
359 preferred-diff-type \
360 proxy \
 
361 redirect-to-https \
362 relative-paths \
363 repo-cksum \
364 repolist-skin \
365 robot-restrict \
@@ -373,13 +374,19 @@
373 ssl-identity \
374 tclsh \
375 th1-setup \
376 th1-uri-regexp \
377 ticket-default-report \
 
 
 
 
378 timeline-utc \
379 user-color-map \
 
380 uv-sync \
 
381 web-browser]
382
383 fossil test-th-eval "hasfeature legacyMvRm"
384
385 if {[normalize_result] eq "1"} {
386
387 DDED test/th1-taint.test
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356 mtime-changes \
357 mv-rm-files \
358 pgp-command \
359 preferred-diff-type \
360 proxy \
361 raw-bgcolor \
362 redirect-to-https \
363 relative-paths \
364 repo-cksum \
365 repolist-skin \
366 robot-restrict \
@@ -373,13 +374,19 @@
374 ssl-identity \
375 tclsh \
376 th1-setup \
377 th1-uri-regexp \
378 ticket-default-report \
379 timeline-hard-newlines \
380 timeline-plaintext \
381 timeline-truncate-at-blank \
382 timeline-tslink-info \
383 timeline-utc \
384 user-color-map \
385 verify-comments \
386 uv-sync \
387 vuln-report \
388 web-browser]
389
390 fossil test-th-eval "hasfeature legacyMvRm"
391
392 if {[normalize_result] eq "1"} {
393
394 DDED test/th1-taint.test
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
1
+#
2
+# Copyright (c) 2025 D. Richard Hipp
3
+#
4
+# This program is free software; you can redistribute it and/or
5
+# modify it under the terms of the Simplified BSD License (also
6
+# known as the "2-Clause License" or "FreeBSD License".)
7
+#
8
+# This program is distributed in the hope that it will be useful,
9
+# but without any warranty; without even the implied warranty of
10
+# merchantability or fitness for a particular purpose.
11
+#
12
+# Author contact information:
13
+# [email protected]
14
+# http://www.hwaci.com/drh/
15
+#
16
+############################################################################
17
+#
18
+# TH1 Commands
19
+#
20
+
21
+set path [file dirname [info script]]; test_setup
22
+
23
+proc taint-test {testnum th1script expected} {
24
+ global fossilexe
25
+ set rc [catch {exec $fossilexe test-th-eval $th1script} got]
26
+ if {$rc} {
27
+ test th1-taint-$testnum 0
28
+ puts $got
29
+ return
30
+ }
31
+ if {$got ne $expected} {
32
+ test th1-taint-$testnum 0
33
+ puts " Expected: $expected"
34
+ puts " Got: $got"
35
+ } else {
36
+ test th1-taint-$testnum 1
37
+ }
38
+}
39
+
40
+taint-test 10 {string is tainted abcd} 0
41
+taint-test 20 {string is tainted [taint abcd]} 1
42
+taint-test 30 {string is tainted [untaint [taint abcd]]} 0
43
+taint-test 40 {string is tainted [untaint abcde]} 0
44
+taint-test 50 {string is tainted "abc[taint def]ghi"} 1
45
+taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1
46
+
47
+taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
48
+taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1
49
+
50
+taint-test 200 {string is tainted [list abc def ghi]} 0
51
+taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
52
+taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
53
+taint-test 230 {string is tainted [list abc def [taint ghi]]} 1
54
+
55
+taint-test 300 {
56
+ set res {}
57
+ foreach x [list abc [taint def] ghi] {
58
+ lappend res [string is tainted $x]
59
+ }
60
+ set res
61
+} {1 1 1}
62
+taint-test 310 {
63
+ set res {}
64
+ foreach {x y} [list abc [taint def] ghi jkl] {
65
+ lappend res [string is tainted $x] [string is tainted $y]
66
+ }
67
+ set res
68
+} {1 1 1 1}
69
+
70
+taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
71
+taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
72
+taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
73
+taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0
74
+
75
+taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
76
+
77
+taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1
78
+
79
+taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1
80
+taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1
81
+taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1
82
+
83
+
84
+test_cleanup
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
1 #
2 # Copyright (c) 2025 D. Richard Hipp
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the Simplified BSD License (also
6 # known as the "2-Clause License" or "FreeBSD License".)
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but without any warranty; without even the implied warranty of
10 # merchantability or fitness for a particular purpose.
11 #
12 # Author contact information:
13 # [email protected]
14 # http://www.hwaci.com/drh/
15 #
16 ############################################################################
17 #
18 # TH1 Commands
19 #
20
21 set path [file dirname [info script]]; test_setup
22
23 proc taint-test {testnum th1script expected} {
24 global fossilexe
25 set rc [catch {exec $fossilexe test-th-eval $th1script} got]
26 if {$rc} {
27 test th1-taint-$testnum 0
28 puts $got
29 return
30 }
31 if {$got ne $expected} {
32 test th1-taint-$testnum 0
33 puts " Expected: $expected"
34 puts " Got: $got"
35 } else {
36 test th1-taint-$testnum 1
37 }
38 }
39
40 taint-test 10 {string is tainted abcd} 0
41 taint-test 20 {string is tainted [taint abcd]} 1
42 taint-test 30 {string is tainted [untaint [taint abcd]]} 0
43 taint-test 40 {string is tainted [untaint abcde]} 0
44 taint-test 50 {string is tainted "abc[taint def]ghi"} 1
45 taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1
46
47 taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
48 taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1
49
50 taint-test 200 {string is tainted [list abc def ghi]} 0
51 taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
52 taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
53 taint-test 230 {string is tainted [list abc def [taint ghi]]} 1
54
55 taint-test 300 {
56 set res {}
57 foreach x [list abc [taint def] ghi] {
58 lappend res [string is tainted $x]
59 }
60 set res
61 } {1 1 1}
62 taint-test 310 {
63 set res {}
64 foreach {x y} [list abc [taint def] ghi jkl] {
65 lappend res [string is tainted $x] [string is tainted $y]
66 }
67 set res
68 } {1 1 1 1}
69
70 taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
71 taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
72 taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
73 taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0
74
75 taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
76
77 taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1
78
79 taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1
80 taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1
81 taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1
82
83
84 test_cleanup
+36 -36
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795795
rpage-\$requested_page\
796796
cpage-\$canonical_page\">" [normalize_result]]}
797797
798798
###############################################################################
799799
800
-fossil test-th-eval "styleHeader {Page Title Here}"
801
-test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
800
+#fossil test-th-eval "styleHeader {Page Title Here}"
801
+#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802802
803803
###############################################################################
804804
805805
test_in_checkout th1-header-2 {
806806
fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807807
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808808
809809
###############################################################################
810810
811
-fossil test-th-eval "styleFooter"
812
-test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
811
+#fossil test-th-eval "styleFooter"
812
+#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813813
814814
###############################################################################
815815
816816
fossil test-th-eval --open-config "styleFooter"
817817
test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879879
test th1-artifact-1 {$RESULT eq \
880880
{TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881881
882882
###############################################################################
883883
884
-fossil test-th-eval "artifact tip"
885
-test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
884
+#fossil test-th-eval "artifact tip"
885
+#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886886
887887
###############################################################################
888888
889889
test_in_checkout th1-artifact-3 {
890890
fossil test-th-eval --open-config "artifact tip"
891891
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892892
893893
###############################################################################
894894
895
-fossil test-th-eval "artifact 0000000000"
896
-test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
895
+#fossil test-th-eval "artifact 0000000000"
896
+#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897897
898898
###############################################################################
899899
900900
fossil test-th-eval --open-config "artifact 0000000000"
901901
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902902
903903
###############################################################################
904904
905
-fossil test-th-eval "artifact tip test/th1.test"
906
-test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
905
+#fossil test-th-eval "artifact tip test/th1.test"
906
+#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907907
908908
###############################################################################
909909
910910
test_in_checkout th1-artifact-7 {
911911
fossil test-th-eval --open-config "artifact tip test/th1.test"
912912
} {[regexp -- {th1-artifact-7} $RESULT]}
913913
914914
###############################################################################
915915
916
-fossil test-th-eval "artifact 0000000000 test/th1.test"
917
-test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
916
+#fossil test-th-eval "artifact 0000000000 test/th1.test"
917
+#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918918
919919
###############################################################################
920920
921921
fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922922
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947947
}
948948
}
949949
950950
###############################################################################
951951
952
-fossil test-th-eval "globalState configuration"
953
-test th1-globalState-3 {[string length $RESULT] == 0}
952
+#fossil test-th-eval "globalState configuration"
953
+#test th1-globalState-3 {[string length $RESULT] == 0}
954954
955955
###############################################################################
956956
957957
fossil test-th-eval --open-config "globalState configuration"
958958
test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
10411041
fossil test-th-eval "globalState flags"
10421042
test th1-globalState-16 {$RESULT eq "0"}
10431043
10441044
###############################################################################
10451045
1046
-fossil test-th-eval "reinitialize; globalState configuration"
1047
-test th1-reinitialize-1 {$RESULT eq ""}
1046
+#fossil test-th-eval "reinitialize; globalState configuration"
1047
+#test th1-reinitialize-1 {$RESULT eq ""}
10481048
10491049
###############################################################################
10501050
10511051
fossil test-th-eval "reinitialize 1; globalState configuration"
10521052
test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
10561056
#
10571057
# NOTE: This test will fail if the command names are added to TH1, or
10581058
# moved from Tcl builds to plain or the reverse. Sorting the
10591059
# command lists eliminates a dependence on order.
10601060
#
1061
-fossil test-th-eval "info commands"
1062
-set sorted_result [lsort $RESULT]
1063
-protOut "Sorted: $sorted_result"
1064
-set base_commands {anoncap anycap array artifact break breakpoint \
1065
- builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
- combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067
- enable_output encode64 error expr for foreach getParameter glob_match \
1068
- globalState hascap hasfeature html htmlize http httpize if info \
1069
- insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
- proc puts query randhex redirect regexp reinitialize rename render \
1071
- repository return searchable set setParameter setting stime string \
1072
- styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
- unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
-set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
-if {$th1Tcl} {
1076
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
-} else {
1078
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
-}
1061
+#fossil test-th-eval "info commands"
1062
+#set sorted_result [lsort $RESULT]
1063
+#protOut "Sorted: $sorted_result"
1064
+#set base_commands {anoncap anycap array artifact break breakpoint \
1065
+# builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
+# combobox continue copybtn date decorate defHeader dir \
1067
+# enable_output encode64 error expr for foreach getParameter glob_match \
1068
+# globalState hascap hasfeature html htmlize http httpize if info \
1069
+# insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
+# proc puts query randhex redirect regexp reinitialize rename render \
1071
+# repository return searchable set setParameter setting stime string \
1072
+# styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
+# unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
+#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
+#if {$th1Tcl} {
1076
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
+#} else {
1078
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
+#}
10801080
10811081
###############################################################################
10821082
10831083
fossil test-th-eval "info vars"
10841084
@@ -1326,11 +1326,11 @@
13261326
13271327
###############################################################################
13281328
13291329
fossil test-th-eval {string is other 123}
13301330
test th1-string-is-4 {$RESULT eq \
1331
-"TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1331
+"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
13321332
13331333
###############################################################################
13341334
13351335
fossil test-th-eval {string is alnum 123}
13361336
test th1-string-is-5 {$RESULT eq "1"}
13371337
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 fossil test-th-eval "styleHeader {Page Title Here}"
801 test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 fossil test-th-eval "styleFooter"
812 test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 fossil test-th-eval "artifact tip"
885 test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 fossil test-th-eval "artifact 0000000000"
896 test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 fossil test-th-eval "artifact tip test/th1.test"
906 test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 fossil test-th-eval "artifact 0000000000 test/th1.test"
917 test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 fossil test-th-eval "globalState configuration"
953 test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 fossil test-th-eval "reinitialize; globalState configuration"
1047 test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 fossil test-th-eval "info commands"
1062 set sorted_result [lsort $RESULT]
1063 protOut "Sorted: $sorted_result"
1064 set base_commands {anoncap anycap array artifact break breakpoint \
1065 builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067 enable_output encode64 error expr for foreach getParameter glob_match \
1068 globalState hascap hasfeature html htmlize http httpize if info \
1069 insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 proc puts query randhex redirect regexp reinitialize rename render \
1071 repository return searchable set setParameter setting stime string \
1072 styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 if {$th1Tcl} {
1076 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 } else {
1078 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 }
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 #fossil test-th-eval "styleHeader {Page Title Here}"
801 #test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 #fossil test-th-eval "styleFooter"
812 #test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 #fossil test-th-eval "artifact tip"
885 #test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 #fossil test-th-eval "artifact 0000000000"
896 #test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 #fossil test-th-eval "artifact tip test/th1.test"
906 #test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 #fossil test-th-eval "artifact 0000000000 test/th1.test"
917 #test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 #fossil test-th-eval "globalState configuration"
953 #test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 #fossil test-th-eval "reinitialize; globalState configuration"
1047 #test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 #fossil test-th-eval "info commands"
1062 #set sorted_result [lsort $RESULT]
1063 #protOut "Sorted: $sorted_result"
1064 #set base_commands {anoncap anycap array artifact break breakpoint \
1065 # builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 # combobox continue copybtn date decorate defHeader dir \
1067 # enable_output encode64 error expr for foreach getParameter glob_match \
1068 # globalState hascap hasfeature html htmlize http httpize if info \
1069 # insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 # proc puts query randhex redirect regexp reinitialize rename render \
1071 # repository return searchable set setParameter setting stime string \
1072 # styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 # unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 #set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 #if {$th1Tcl} {
1076 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 #} else {
1078 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 #}
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
+36 -36
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795795
rpage-\$requested_page\
796796
cpage-\$canonical_page\">" [normalize_result]]}
797797
798798
###############################################################################
799799
800
-fossil test-th-eval "styleHeader {Page Title Here}"
801
-test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
800
+#fossil test-th-eval "styleHeader {Page Title Here}"
801
+#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802802
803803
###############################################################################
804804
805805
test_in_checkout th1-header-2 {
806806
fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807807
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808808
809809
###############################################################################
810810
811
-fossil test-th-eval "styleFooter"
812
-test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
811
+#fossil test-th-eval "styleFooter"
812
+#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813813
814814
###############################################################################
815815
816816
fossil test-th-eval --open-config "styleFooter"
817817
test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879879
test th1-artifact-1 {$RESULT eq \
880880
{TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881881
882882
###############################################################################
883883
884
-fossil test-th-eval "artifact tip"
885
-test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
884
+#fossil test-th-eval "artifact tip"
885
+#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886886
887887
###############################################################################
888888
889889
test_in_checkout th1-artifact-3 {
890890
fossil test-th-eval --open-config "artifact tip"
891891
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892892
893893
###############################################################################
894894
895
-fossil test-th-eval "artifact 0000000000"
896
-test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
895
+#fossil test-th-eval "artifact 0000000000"
896
+#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897897
898898
###############################################################################
899899
900900
fossil test-th-eval --open-config "artifact 0000000000"
901901
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902902
903903
###############################################################################
904904
905
-fossil test-th-eval "artifact tip test/th1.test"
906
-test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
905
+#fossil test-th-eval "artifact tip test/th1.test"
906
+#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907907
908908
###############################################################################
909909
910910
test_in_checkout th1-artifact-7 {
911911
fossil test-th-eval --open-config "artifact tip test/th1.test"
912912
} {[regexp -- {th1-artifact-7} $RESULT]}
913913
914914
###############################################################################
915915
916
-fossil test-th-eval "artifact 0000000000 test/th1.test"
917
-test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
916
+#fossil test-th-eval "artifact 0000000000 test/th1.test"
917
+#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918918
919919
###############################################################################
920920
921921
fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922922
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947947
}
948948
}
949949
950950
###############################################################################
951951
952
-fossil test-th-eval "globalState configuration"
953
-test th1-globalState-3 {[string length $RESULT] == 0}
952
+#fossil test-th-eval "globalState configuration"
953
+#test th1-globalState-3 {[string length $RESULT] == 0}
954954
955955
###############################################################################
956956
957957
fossil test-th-eval --open-config "globalState configuration"
958958
test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
10411041
fossil test-th-eval "globalState flags"
10421042
test th1-globalState-16 {$RESULT eq "0"}
10431043
10441044
###############################################################################
10451045
1046
-fossil test-th-eval "reinitialize; globalState configuration"
1047
-test th1-reinitialize-1 {$RESULT eq ""}
1046
+#fossil test-th-eval "reinitialize; globalState configuration"
1047
+#test th1-reinitialize-1 {$RESULT eq ""}
10481048
10491049
###############################################################################
10501050
10511051
fossil test-th-eval "reinitialize 1; globalState configuration"
10521052
test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
10561056
#
10571057
# NOTE: This test will fail if the command names are added to TH1, or
10581058
# moved from Tcl builds to plain or the reverse. Sorting the
10591059
# command lists eliminates a dependence on order.
10601060
#
1061
-fossil test-th-eval "info commands"
1062
-set sorted_result [lsort $RESULT]
1063
-protOut "Sorted: $sorted_result"
1064
-set base_commands {anoncap anycap array artifact break breakpoint \
1065
- builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
- combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067
- enable_output encode64 error expr for foreach getParameter glob_match \
1068
- globalState hascap hasfeature html htmlize http httpize if info \
1069
- insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
- proc puts query randhex redirect regexp reinitialize rename render \
1071
- repository return searchable set setParameter setting stime string \
1072
- styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
- unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
-set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
-if {$th1Tcl} {
1076
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
-} else {
1078
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
-}
1061
+#fossil test-th-eval "info commands"
1062
+#set sorted_result [lsort $RESULT]
1063
+#protOut "Sorted: $sorted_result"
1064
+#set base_commands {anoncap anycap array artifact break breakpoint \
1065
+# builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
+# combobox continue copybtn date decorate defHeader dir \
1067
+# enable_output encode64 error expr for foreach getParameter glob_match \
1068
+# globalState hascap hasfeature html htmlize http httpize if info \
1069
+# insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
+# proc puts query randhex redirect regexp reinitialize rename render \
1071
+# repository return searchable set setParameter setting stime string \
1072
+# styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
+# unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
+#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
+#if {$th1Tcl} {
1076
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
+#} else {
1078
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
+#}
10801080
10811081
###############################################################################
10821082
10831083
fossil test-th-eval "info vars"
10841084
@@ -1326,11 +1326,11 @@
13261326
13271327
###############################################################################
13281328
13291329
fossil test-th-eval {string is other 123}
13301330
test th1-string-is-4 {$RESULT eq \
1331
-"TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1331
+"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
13321332
13331333
###############################################################################
13341334
13351335
fossil test-th-eval {string is alnum 123}
13361336
test th1-string-is-5 {$RESULT eq "1"}
13371337
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 fossil test-th-eval "styleHeader {Page Title Here}"
801 test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 fossil test-th-eval "styleFooter"
812 test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 fossil test-th-eval "artifact tip"
885 test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 fossil test-th-eval "artifact 0000000000"
896 test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 fossil test-th-eval "artifact tip test/th1.test"
906 test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 fossil test-th-eval "artifact 0000000000 test/th1.test"
917 test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 fossil test-th-eval "globalState configuration"
953 test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 fossil test-th-eval "reinitialize; globalState configuration"
1047 test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 fossil test-th-eval "info commands"
1062 set sorted_result [lsort $RESULT]
1063 protOut "Sorted: $sorted_result"
1064 set base_commands {anoncap anycap array artifact break breakpoint \
1065 builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067 enable_output encode64 error expr for foreach getParameter glob_match \
1068 globalState hascap hasfeature html htmlize http httpize if info \
1069 insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 proc puts query randhex redirect regexp reinitialize rename render \
1071 repository return searchable set setParameter setting stime string \
1072 styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 if {$th1Tcl} {
1076 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 } else {
1078 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 }
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 #fossil test-th-eval "styleHeader {Page Title Here}"
801 #test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 #fossil test-th-eval "styleFooter"
812 #test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 #fossil test-th-eval "artifact tip"
885 #test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 #fossil test-th-eval "artifact 0000000000"
896 #test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 #fossil test-th-eval "artifact tip test/th1.test"
906 #test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 #fossil test-th-eval "artifact 0000000000 test/th1.test"
917 #test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 #fossil test-th-eval "globalState configuration"
953 #test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 #fossil test-th-eval "reinitialize; globalState configuration"
1047 #test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 #fossil test-th-eval "info commands"
1062 #set sorted_result [lsort $RESULT]
1063 #protOut "Sorted: $sorted_result"
1064 #set base_commands {anoncap anycap array artifact break breakpoint \
1065 # builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 # combobox continue copybtn date decorate defHeader dir \
1067 # enable_output encode64 error expr for foreach getParameter glob_match \
1068 # globalState hascap hasfeature html htmlize http httpize if info \
1069 # insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 # proc puts query randhex redirect regexp reinitialize rename render \
1071 # repository return searchable set setParameter setting stime string \
1072 # styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 # unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 #set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 #if {$th1Tcl} {
1076 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 #} else {
1078 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 #}
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
+2 -3
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -81,13 +81,12 @@
8181
the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
8282
environment variable. The variable can be defined in the CGI
8383
control file using the [#setenv|<tt>setenv:</tt>] statement.
8484
8585
The "Project Description" and "Login-Group" columns on the repolist page
86
-are optional. They are hidden by default. Show them by putting value "1"
87
-in the global settings "show-repolist-desc" and "show-repolist-lg", or
88
-by setting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
86
+are optional. They are hidden by default. Show them by
87
+etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
8988
a string that contains substrings "description" and/or "login-group".
9089
9190
The repolist-generated page recurses into subdirectories and will list
9291
all <tt>*.fossil</tt> files found, with the following exceptions:
9392
9493
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -81,13 +81,12 @@
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
84
85 The "Project Description" and "Login-Group" columns on the repolist page
86 are optional. They are hidden by default. Show them by putting value "1"
87 in the global settings "show-repolist-desc" and "show-repolist-lg", or
88 by setting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
89 a string that contains substrings "description" and/or "login-group".
90
91 The repolist-generated page recurses into subdirectories and will list
92 all <tt>*.fossil</tt> files found, with the following exceptions:
93
94
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -81,13 +81,12 @@
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
84
85 The "Project Description" and "Login-Group" columns on the repolist page
86 are optional. They are hidden by default. Show them by
87 etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
 
88 a string that contains substrings "description" and/or "login-group".
89
90 The repolist-generated page recurses into subdirectories and will list
91 all <tt>*.fossil</tt> files found, with the following exceptions:
92
93
+37 -21
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
11
<title>Change Log</title>
22
3
-<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
-
5
- * Enhancements to [/help?cmd=diff|fossil diff] and similar:
3
+<h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>
4
+ <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
65
<ol type="a">
76
<li> The --from can optionally accept a directory name as its argument,
87
and uses files under that directory as the baseline for the diff.
98
<li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
109
is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
1211
<li> The "Reload" button is added to --tk diffs, to bring the displayed
1312
diff up to date with the latest changes on disk.
1413
<li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
1514
diffs of multiple files.
1615
</ol>
17
- * Added the [/help?cmd=/ckout|/ckout web page] to provide information
16
+ <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
1817
about pending changes in a working check-out
19
- * Enhancements to the [/help?cmd=ui|fossil ui] command:
18
+ <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
2019
<ol type="a">
2120
<li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
2221
start page. Or, if the new "--from PATH" option is present, the
2322
default start page becomes "/ckout?exbase=PATH".
2423
<li> The new "--extpage FILENAME" option opens the named file as if it
2524
where in a [./serverext.wiki|CGI extension]. Example usage: the
2625
person editing this change log has
2726
"fossil ui --extpage www/changes.wiki" running and hence can
2827
press "Reload" on the web browser to view edits.
28
+ <li> Accept both IPv4 and IPv6 connections on all platforms, including
29
+ Windows and OpenBSD. This also applies to the "fossil server"
30
+ command.
2931
</ol>
30
- * Enhancements to [/help?cmd=merge|fossil merge]:
32
+ <li>Enhancements to [/help?cmd=merge|fossil merge]:
3133
<ol type="a">
3234
<li> Added the [/help?cmd=merge-info|fossil merge-info] command and
3335
especially the --tk option to that command, to provide analysis
3436
of the most recent merge or update operation.
3537
<li> When a merge conflict occurs, a new section is added to the conflict
3638
text that shows Fossil's suggested resolution to the conflict.
3739
</ol>
38
- * Enhancements to [/help?cmd=commit|fossil commit]:
40
+ <li>Enhancements to [/help?cmd=commit|fossil commit]:
3941
<ol type="a">
4042
<li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
4143
in the check-in comment, it will alert the developer and give
4244
him or her the opportunity to edit the comment before continuing.
4345
This feature is controllable by the
@@ -49,17 +51,17 @@
4951
branch has been changed.
5052
<li> The interactive checkin comment prompt shows the formatting rules
5153
set for that repository.
5254
<li> Add the "--editor" option.
5355
</ol>
54
- * Deprecate the --comfmtflags and --comment-format global options and
56
+ <li>Deprecate the --comfmtflags and --comment-format global options and
5557
no longer list them in the built-in help, but keep them working for
5658
backwards compatibility.
5759
Alternative TTY comment formatting can still be specified using the
5860
[/help?cmd=comment-format|comment-format setting], if desired. The
5961
default comment format is now called "canonical", not "legacy".
60
- * Enhancements to the [/help?cmd=/timeline|/timeline page]:
62
+ <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
6163
<ol type="a">
6264
<li> Added the "ml=" ("Merge-in List") query parameter that works
6365
like "rl=" ("Related List") but adds "mionly" style related
6466
check-ins instead of the full "rel" style.
6567
<li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
9092
<li> The saturation and intensity of user-specified checkin and branch
9193
background colors are automatically adjusted to keep the colors
9294
compatible with the current skin, unless the
9395
[/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
9496
</ol>
95
- * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
97
+ <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
9698
/doc but keeps the title of markdown documents with the document rather
9799
that moving it up to the page title.
98
- * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
100
+ <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
99101
and debugging
100
- * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
102
+ <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
101103
decoding of the artifact described by NAME.
102
- * Improvements to the [/help?cmd=patch|fossil patch] command:
104
+ <li>Improvements to the [/help?cmd=patch|fossil patch] command:
103105
<ol type="a">
104106
<li> Fix a bug in "fossil patch create" that causes
105107
[/help?cmd=revert|fossil revert] operations that happened
106108
on individualfiles after a [/help?cmd=merge|fossil merge]
107109
to be omitted from the patch.
108110
<li> Added the [/help?cmd=patch|patch alias] command for managing
109111
aliases for remote checkout names.
110112
</ol>
111
- * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
113
+ <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
112114
<ol type="a">
113115
<li> Add the ability to search the help text, either in the UI
114116
(on the [/help?cmd=/search|/search page]) or from the command-line
115117
(using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
116118
<li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
117119
COMMAND argument and only shows results for the specified
118120
subcommand, not the entire command.
119121
<li> The -u (--usage) option shows only the command-line syntax
120122
<li> The -o (--options) option shows only the command-line options
121123
</ol>
122
- * Enhancements to the ticket system:
124
+ <li>Enhancements to the ticket system:
123125
<ol type="a">
124126
<li> Added the ability to attach wiki pages to a ticket for extended
125127
descriptions.
126128
<li> Added submenu to the 'View Ticket' page, to use it as
127129
template for a new ticket.
@@ -129,21 +131,35 @@
129131
in a row.
130132
<li> Link the version field in ticket view to a matching checkin or tag.
131133
<li> Show creation time in report and ticket view.
132134
<li> Show previous comments in edit ticket as reference.
133135
</ol>
134
- * Added the "hash" query parameter to the
136
+ <li>Added the "hash" query parameter to the
135137
[/help?cmd=/whatis|/whatis webpage].
136
- * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
138
+ <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
137139
which alerts subscribers when an admin creates a new user or
138140
when a user's permissions change.
139
- * Show project description on repository list.
140
- * Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141
+ <li>Show project description on repository list.
142
+ <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141143
the frequency of reconnection attempts over time and providing feedback
142144
to the user when the connection is down.
143
- * Diverse minor fixes and additions.
144
-
145
+ <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
146
+ security:
147
+ <ol type="a">
148
+ <li> TH1 now makes a distinction between
149
+ [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
150
+ This makes it more difficult to write custom TH1 scripts that
151
+ contain XSS or SQL-injection bugs. The
152
+ [/help?cmd=vuln-report|vuln-report] setting was added to control
153
+ what Fossil does when it encounters a potential TH1
154
+ security problem.
155
+ <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
156
+ command.
157
+ <li> The "enable_htmlify" TH1 command was removed.
158
+ </ol>
159
+ <li>Many other minor fixes and additions.
160
+</ol>
145161
146162
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
147163
148164
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
149165
that have non-ASCII filenames
150166
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
5 * Enhancements to [/help?cmd=diff|fossil diff] and similar:
6 <ol type="a">
7 <li> The --from can optionally accept a directory name as its argument,
8 and uses files under that directory as the baseline for the diff.
9 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
10 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
12 <li> The "Reload" button is added to --tk diffs, to bring the displayed
13 diff up to date with the latest changes on disk.
14 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
15 diffs of multiple files.
16 </ol>
17 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
18 about pending changes in a working check-out
19 * Enhancements to the [/help?cmd=ui|fossil ui] command:
20 <ol type="a">
21 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
22 start page. Or, if the new "--from PATH" option is present, the
23 default start page becomes "/ckout?exbase=PATH".
24 <li> The new "--extpage FILENAME" option opens the named file as if it
25 where in a [./serverext.wiki|CGI extension]. Example usage: the
26 person editing this change log has
27 "fossil ui --extpage www/changes.wiki" running and hence can
28 press "Reload" on the web browser to view edits.
 
 
 
29 </ol>
30 * Enhancements to [/help?cmd=merge|fossil merge]:
31 <ol type="a">
32 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
33 especially the --tk option to that command, to provide analysis
34 of the most recent merge or update operation.
35 <li> When a merge conflict occurs, a new section is added to the conflict
36 text that shows Fossil's suggested resolution to the conflict.
37 </ol>
38 * Enhancements to [/help?cmd=commit|fossil commit]:
39 <ol type="a">
40 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
41 in the check-in comment, it will alert the developer and give
42 him or her the opportunity to edit the comment before continuing.
43 This feature is controllable by the
@@ -49,17 +51,17 @@
49 branch has been changed.
50 <li> The interactive checkin comment prompt shows the formatting rules
51 set for that repository.
52 <li> Add the "--editor" option.
53 </ol>
54 * Deprecate the --comfmtflags and --comment-format global options and
55 no longer list them in the built-in help, but keep them working for
56 backwards compatibility.
57 Alternative TTY comment formatting can still be specified using the
58 [/help?cmd=comment-format|comment-format setting], if desired. The
59 default comment format is now called "canonical", not "legacy".
60 * Enhancements to the [/help?cmd=/timeline|/timeline page]:
61 <ol type="a">
62 <li> Added the "ml=" ("Merge-in List") query parameter that works
63 like "rl=" ("Related List") but adds "mionly" style related
64 check-ins instead of the full "rel" style.
65 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
90 <li> The saturation and intensity of user-specified checkin and branch
91 background colors are automatically adjusted to keep the colors
92 compatible with the current skin, unless the
93 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
94 </ol>
95 * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
96 /doc but keeps the title of markdown documents with the document rather
97 that moving it up to the page title.
98 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
99 and debugging
100 * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
101 decoding of the artifact described by NAME.
102 * Improvements to the [/help?cmd=patch|fossil patch] command:
103 <ol type="a">
104 <li> Fix a bug in "fossil patch create" that causes
105 [/help?cmd=revert|fossil revert] operations that happened
106 on individualfiles after a [/help?cmd=merge|fossil merge]
107 to be omitted from the patch.
108 <li> Added the [/help?cmd=patch|patch alias] command for managing
109 aliases for remote checkout names.
110 </ol>
111 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
112 <ol type="a">
113 <li> Add the ability to search the help text, either in the UI
114 (on the [/help?cmd=/search|/search page]) or from the command-line
115 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
116 <li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
117 COMMAND argument and only shows results for the specified
118 subcommand, not the entire command.
119 <li> The -u (--usage) option shows only the command-line syntax
120 <li> The -o (--options) option shows only the command-line options
121 </ol>
122 * Enhancements to the ticket system:
123 <ol type="a">
124 <li> Added the ability to attach wiki pages to a ticket for extended
125 descriptions.
126 <li> Added submenu to the 'View Ticket' page, to use it as
127 template for a new ticket.
@@ -129,21 +131,35 @@
129 in a row.
130 <li> Link the version field in ticket view to a matching checkin or tag.
131 <li> Show creation time in report and ticket view.
132 <li> Show previous comments in edit ticket as reference.
133 </ol>
134 * Added the "hash" query parameter to the
135 [/help?cmd=/whatis|/whatis webpage].
136 * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
137 which alerts subscribers when an admin creates a new user or
138 when a user's permissions change.
139 * Show project description on repository list.
140 * Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141 the frequency of reconnection attempts over time and providing feedback
142 to the user when the connection is down.
143 * Diverse minor fixes and additions.
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
146 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
147
148 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
149 that have non-ASCII filenames
150
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>
4 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
 
5 <ol type="a">
6 <li> The --from can optionally accept a directory name as its argument,
7 and uses files under that directory as the baseline for the diff.
8 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
9 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
11 <li> The "Reload" button is added to --tk diffs, to bring the displayed
12 diff up to date with the latest changes on disk.
13 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
14 diffs of multiple files.
15 </ol>
16 <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
17 about pending changes in a working check-out
18 <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
19 <ol type="a">
20 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
21 start page. Or, if the new "--from PATH" option is present, the
22 default start page becomes "/ckout?exbase=PATH".
23 <li> The new "--extpage FILENAME" option opens the named file as if it
24 where in a [./serverext.wiki|CGI extension]. Example usage: the
25 person editing this change log has
26 "fossil ui --extpage www/changes.wiki" running and hence can
27 press "Reload" on the web browser to view edits.
28 <li> Accept both IPv4 and IPv6 connections on all platforms, including
29 Windows and OpenBSD. This also applies to the "fossil server"
30 command.
31 </ol>
32 <li>Enhancements to [/help?cmd=merge|fossil merge]:
33 <ol type="a">
34 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
35 especially the --tk option to that command, to provide analysis
36 of the most recent merge or update operation.
37 <li> When a merge conflict occurs, a new section is added to the conflict
38 text that shows Fossil's suggested resolution to the conflict.
39 </ol>
40 <li>Enhancements to [/help?cmd=commit|fossil commit]:
41 <ol type="a">
42 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
43 in the check-in comment, it will alert the developer and give
44 him or her the opportunity to edit the comment before continuing.
45 This feature is controllable by the
@@ -49,17 +51,17 @@
51 branch has been changed.
52 <li> The interactive checkin comment prompt shows the formatting rules
53 set for that repository.
54 <li> Add the "--editor" option.
55 </ol>
56 <li>Deprecate the --comfmtflags and --comment-format global options and
57 no longer list them in the built-in help, but keep them working for
58 backwards compatibility.
59 Alternative TTY comment formatting can still be specified using the
60 [/help?cmd=comment-format|comment-format setting], if desired. The
61 default comment format is now called "canonical", not "legacy".
62 <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
63 <ol type="a">
64 <li> Added the "ml=" ("Merge-in List") query parameter that works
65 like "rl=" ("Related List") but adds "mionly" style related
66 check-ins instead of the full "rel" style.
67 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
92 <li> The saturation and intensity of user-specified checkin and branch
93 background colors are automatically adjusted to keep the colors
94 compatible with the current skin, unless the
95 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
96 </ol>
97 <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
98 /doc but keeps the title of markdown documents with the document rather
99 that moving it up to the page title.
100 <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
101 and debugging
102 <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
103 decoding of the artifact described by NAME.
104 <li>Improvements to the [/help?cmd=patch|fossil patch] command:
105 <ol type="a">
106 <li> Fix a bug in "fossil patch create" that causes
107 [/help?cmd=revert|fossil revert] operations that happened
108 on individualfiles after a [/help?cmd=merge|fossil merge]
109 to be omitted from the patch.
110 <li> Added the [/help?cmd=patch|patch alias] command for managing
111 aliases for remote checkout names.
112 </ol>
113 <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
114 <ol type="a">
115 <li> Add the ability to search the help text, either in the UI
116 (on the [/help?cmd=/search|/search page]) or from the command-line
117 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
118 <li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
119 COMMAND argument and only shows results for the specified
120 subcommand, not the entire command.
121 <li> The -u (--usage) option shows only the command-line syntax
122 <li> The -o (--options) option shows only the command-line options
123 </ol>
124 <li>Enhancements to the ticket system:
125 <ol type="a">
126 <li> Added the ability to attach wiki pages to a ticket for extended
127 descriptions.
128 <li> Added submenu to the 'View Ticket' page, to use it as
129 template for a new ticket.
@@ -129,21 +131,35 @@
131 in a row.
132 <li> Link the version field in ticket view to a matching checkin or tag.
133 <li> Show creation time in report and ticket view.
134 <li> Show previous comments in edit ticket as reference.
135 </ol>
136 <li>Added the "hash" query parameter to the
137 [/help?cmd=/whatis|/whatis webpage].
138 <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
139 which alerts subscribers when an admin creates a new user or
140 when a user's permissions change.
141 <li>Show project description on repository list.
142 <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
143 the frequency of reconnection attempts over time and providing feedback
144 to the user when the connection is down.
145 <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
146 security:
147 <ol type="a">
148 <li> TH1 now makes a distinction between
149 [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
150 This makes it more difficult to write custom TH1 scripts that
151 contain XSS or SQL-injection bugs. The
152 [/help?cmd=vuln-report|vuln-report] setting was added to control
153 what Fossil does when it encounters a potential TH1
154 security problem.
155 <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
156 command.
157 <li> The "enable_htmlify" TH1 command was removed.
158 </ol>
159 <li>Many other minor fixes and additions.
160 </ol>
161
162 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
163
164 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
165 that have non-ASCII filenames
166
+37 -21
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
11
<title>Change Log</title>
22
3
-<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
-
5
- * Enhancements to [/help?cmd=diff|fossil diff] and similar:
3
+<h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>
4
+ <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
65
<ol type="a">
76
<li> The --from can optionally accept a directory name as its argument,
87
and uses files under that directory as the baseline for the diff.
98
<li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
109
is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
1211
<li> The "Reload" button is added to --tk diffs, to bring the displayed
1312
diff up to date with the latest changes on disk.
1413
<li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
1514
diffs of multiple files.
1615
</ol>
17
- * Added the [/help?cmd=/ckout|/ckout web page] to provide information
16
+ <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
1817
about pending changes in a working check-out
19
- * Enhancements to the [/help?cmd=ui|fossil ui] command:
18
+ <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
2019
<ol type="a">
2120
<li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
2221
start page. Or, if the new "--from PATH" option is present, the
2322
default start page becomes "/ckout?exbase=PATH".
2423
<li> The new "--extpage FILENAME" option opens the named file as if it
2524
where in a [./serverext.wiki|CGI extension]. Example usage: the
2625
person editing this change log has
2726
"fossil ui --extpage www/changes.wiki" running and hence can
2827
press "Reload" on the web browser to view edits.
28
+ <li> Accept both IPv4 and IPv6 connections on all platforms, including
29
+ Windows and OpenBSD. This also applies to the "fossil server"
30
+ command.
2931
</ol>
30
- * Enhancements to [/help?cmd=merge|fossil merge]:
32
+ <li>Enhancements to [/help?cmd=merge|fossil merge]:
3133
<ol type="a">
3234
<li> Added the [/help?cmd=merge-info|fossil merge-info] command and
3335
especially the --tk option to that command, to provide analysis
3436
of the most recent merge or update operation.
3537
<li> When a merge conflict occurs, a new section is added to the conflict
3638
text that shows Fossil's suggested resolution to the conflict.
3739
</ol>
38
- * Enhancements to [/help?cmd=commit|fossil commit]:
40
+ <li>Enhancements to [/help?cmd=commit|fossil commit]:
3941
<ol type="a">
4042
<li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
4143
in the check-in comment, it will alert the developer and give
4244
him or her the opportunity to edit the comment before continuing.
4345
This feature is controllable by the
@@ -49,17 +51,17 @@
4951
branch has been changed.
5052
<li> The interactive checkin comment prompt shows the formatting rules
5153
set for that repository.
5254
<li> Add the "--editor" option.
5355
</ol>
54
- * Deprecate the --comfmtflags and --comment-format global options and
56
+ <li>Deprecate the --comfmtflags and --comment-format global options and
5557
no longer list them in the built-in help, but keep them working for
5658
backwards compatibility.
5759
Alternative TTY comment formatting can still be specified using the
5860
[/help?cmd=comment-format|comment-format setting], if desired. The
5961
default comment format is now called "canonical", not "legacy".
60
- * Enhancements to the [/help?cmd=/timeline|/timeline page]:
62
+ <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
6163
<ol type="a">
6264
<li> Added the "ml=" ("Merge-in List") query parameter that works
6365
like "rl=" ("Related List") but adds "mionly" style related
6466
check-ins instead of the full "rel" style.
6567
<li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
9092
<li> The saturation and intensity of user-specified checkin and branch
9193
background colors are automatically adjusted to keep the colors
9294
compatible with the current skin, unless the
9395
[/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
9496
</ol>
95
- * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
97
+ <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
9698
/doc but keeps the title of markdown documents with the document rather
9799
that moving it up to the page title.
98
- * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
100
+ <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
99101
and debugging
100
- * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
102
+ <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
101103
decoding of the artifact described by NAME.
102
- * Improvements to the [/help?cmd=patch|fossil patch] command:
104
+ <li>Improvements to the [/help?cmd=patch|fossil patch] command:
103105
<ol type="a">
104106
<li> Fix a bug in "fossil patch create" that causes
105107
[/help?cmd=revert|fossil revert] operations that happened
106108
on individualfiles after a [/help?cmd=merge|fossil merge]
107109
to be omitted from the patch.
108110
<li> Added the [/help?cmd=patch|patch alias] command for managing
109111
aliases for remote checkout names.
110112
</ol>
111
- * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
113
+ <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
112114
<ol type="a">
113115
<li> Add the ability to search the help text, either in the UI
114116
(on the [/help?cmd=/search|/search page]) or from the command-line
115117
(using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
116118
<li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
117119
COMMAND argument and only shows results for the specified
118120
subcommand, not the entire command.
119121
<li> The -u (--usage) option shows only the command-line syntax
120122
<li> The -o (--options) option shows only the command-line options
121123
</ol>
122
- * Enhancements to the ticket system:
124
+ <li>Enhancements to the ticket system:
123125
<ol type="a">
124126
<li> Added the ability to attach wiki pages to a ticket for extended
125127
descriptions.
126128
<li> Added submenu to the 'View Ticket' page, to use it as
127129
template for a new ticket.
@@ -129,21 +131,35 @@
129131
in a row.
130132
<li> Link the version field in ticket view to a matching checkin or tag.
131133
<li> Show creation time in report and ticket view.
132134
<li> Show previous comments in edit ticket as reference.
133135
</ol>
134
- * Added the "hash" query parameter to the
136
+ <li>Added the "hash" query parameter to the
135137
[/help?cmd=/whatis|/whatis webpage].
136
- * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
138
+ <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
137139
which alerts subscribers when an admin creates a new user or
138140
when a user's permissions change.
139
- * Show project description on repository list.
140
- * Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141
+ <li>Show project description on repository list.
142
+ <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141143
the frequency of reconnection attempts over time and providing feedback
142144
to the user when the connection is down.
143
- * Diverse minor fixes and additions.
144
-
145
+ <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
146
+ security:
147
+ <ol type="a">
148
+ <li> TH1 now makes a distinction between
149
+ [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
150
+ This makes it more difficult to write custom TH1 scripts that
151
+ contain XSS or SQL-injection bugs. The
152
+ [/help?cmd=vuln-report|vuln-report] setting was added to control
153
+ what Fossil does when it encounters a potential TH1
154
+ security problem.
155
+ <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
156
+ command.
157
+ <li> The "enable_htmlify" TH1 command was removed.
158
+ </ol>
159
+ <li>Many other minor fixes and additions.
160
+</ol>
145161
146162
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
147163
148164
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
149165
that have non-ASCII filenames
150166
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
5 * Enhancements to [/help?cmd=diff|fossil diff] and similar:
6 <ol type="a">
7 <li> The --from can optionally accept a directory name as its argument,
8 and uses files under that directory as the baseline for the diff.
9 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
10 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
12 <li> The "Reload" button is added to --tk diffs, to bring the displayed
13 diff up to date with the latest changes on disk.
14 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
15 diffs of multiple files.
16 </ol>
17 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
18 about pending changes in a working check-out
19 * Enhancements to the [/help?cmd=ui|fossil ui] command:
20 <ol type="a">
21 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
22 start page. Or, if the new "--from PATH" option is present, the
23 default start page becomes "/ckout?exbase=PATH".
24 <li> The new "--extpage FILENAME" option opens the named file as if it
25 where in a [./serverext.wiki|CGI extension]. Example usage: the
26 person editing this change log has
27 "fossil ui --extpage www/changes.wiki" running and hence can
28 press "Reload" on the web browser to view edits.
 
 
 
29 </ol>
30 * Enhancements to [/help?cmd=merge|fossil merge]:
31 <ol type="a">
32 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
33 especially the --tk option to that command, to provide analysis
34 of the most recent merge or update operation.
35 <li> When a merge conflict occurs, a new section is added to the conflict
36 text that shows Fossil's suggested resolution to the conflict.
37 </ol>
38 * Enhancements to [/help?cmd=commit|fossil commit]:
39 <ol type="a">
40 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
41 in the check-in comment, it will alert the developer and give
42 him or her the opportunity to edit the comment before continuing.
43 This feature is controllable by the
@@ -49,17 +51,17 @@
49 branch has been changed.
50 <li> The interactive checkin comment prompt shows the formatting rules
51 set for that repository.
52 <li> Add the "--editor" option.
53 </ol>
54 * Deprecate the --comfmtflags and --comment-format global options and
55 no longer list them in the built-in help, but keep them working for
56 backwards compatibility.
57 Alternative TTY comment formatting can still be specified using the
58 [/help?cmd=comment-format|comment-format setting], if desired. The
59 default comment format is now called "canonical", not "legacy".
60 * Enhancements to the [/help?cmd=/timeline|/timeline page]:
61 <ol type="a">
62 <li> Added the "ml=" ("Merge-in List") query parameter that works
63 like "rl=" ("Related List") but adds "mionly" style related
64 check-ins instead of the full "rel" style.
65 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
90 <li> The saturation and intensity of user-specified checkin and branch
91 background colors are automatically adjusted to keep the colors
92 compatible with the current skin, unless the
93 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
94 </ol>
95 * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
96 /doc but keeps the title of markdown documents with the document rather
97 that moving it up to the page title.
98 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
99 and debugging
100 * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
101 decoding of the artifact described by NAME.
102 * Improvements to the [/help?cmd=patch|fossil patch] command:
103 <ol type="a">
104 <li> Fix a bug in "fossil patch create" that causes
105 [/help?cmd=revert|fossil revert] operations that happened
106 on individualfiles after a [/help?cmd=merge|fossil merge]
107 to be omitted from the patch.
108 <li> Added the [/help?cmd=patch|patch alias] command for managing
109 aliases for remote checkout names.
110 </ol>
111 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
112 <ol type="a">
113 <li> Add the ability to search the help text, either in the UI
114 (on the [/help?cmd=/search|/search page]) or from the command-line
115 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
116 <li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
117 COMMAND argument and only shows results for the specified
118 subcommand, not the entire command.
119 <li> The -u (--usage) option shows only the command-line syntax
120 <li> The -o (--options) option shows only the command-line options
121 </ol>
122 * Enhancements to the ticket system:
123 <ol type="a">
124 <li> Added the ability to attach wiki pages to a ticket for extended
125 descriptions.
126 <li> Added submenu to the 'View Ticket' page, to use it as
127 template for a new ticket.
@@ -129,21 +131,35 @@
129 in a row.
130 <li> Link the version field in ticket view to a matching checkin or tag.
131 <li> Show creation time in report and ticket view.
132 <li> Show previous comments in edit ticket as reference.
133 </ol>
134 * Added the "hash" query parameter to the
135 [/help?cmd=/whatis|/whatis webpage].
136 * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
137 which alerts subscribers when an admin creates a new user or
138 when a user's permissions change.
139 * Show project description on repository list.
140 * Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
141 the frequency of reconnection attempts over time and providing feedback
142 to the user when the connection is down.
143 * Diverse minor fixes and additions.
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
146 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
147
148 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
149 that have non-ASCII filenames
150
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,10 +1,9 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>
4 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
 
5 <ol type="a">
6 <li> The --from can optionally accept a directory name as its argument,
7 and uses files under that directory as the baseline for the diff.
8 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
9 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
@@ -12,32 +11,35 @@
11 <li> The "Reload" button is added to --tk diffs, to bring the displayed
12 diff up to date with the latest changes on disk.
13 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
14 diffs of multiple files.
15 </ol>
16 <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
17 about pending changes in a working check-out
18 <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
19 <ol type="a">
20 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
21 start page. Or, if the new "--from PATH" option is present, the
22 default start page becomes "/ckout?exbase=PATH".
23 <li> The new "--extpage FILENAME" option opens the named file as if it
24 where in a [./serverext.wiki|CGI extension]. Example usage: the
25 person editing this change log has
26 "fossil ui --extpage www/changes.wiki" running and hence can
27 press "Reload" on the web browser to view edits.
28 <li> Accept both IPv4 and IPv6 connections on all platforms, including
29 Windows and OpenBSD. This also applies to the "fossil server"
30 command.
31 </ol>
32 <li>Enhancements to [/help?cmd=merge|fossil merge]:
33 <ol type="a">
34 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
35 especially the --tk option to that command, to provide analysis
36 of the most recent merge or update operation.
37 <li> When a merge conflict occurs, a new section is added to the conflict
38 text that shows Fossil's suggested resolution to the conflict.
39 </ol>
40 <li>Enhancements to [/help?cmd=commit|fossil commit]:
41 <ol type="a">
42 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
43 in the check-in comment, it will alert the developer and give
44 him or her the opportunity to edit the comment before continuing.
45 This feature is controllable by the
@@ -49,17 +51,17 @@
51 branch has been changed.
52 <li> The interactive checkin comment prompt shows the formatting rules
53 set for that repository.
54 <li> Add the "--editor" option.
55 </ol>
56 <li>Deprecate the --comfmtflags and --comment-format global options and
57 no longer list them in the built-in help, but keep them working for
58 backwards compatibility.
59 Alternative TTY comment formatting can still be specified using the
60 [/help?cmd=comment-format|comment-format setting], if desired. The
61 default comment format is now called "canonical", not "legacy".
62 <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
63 <ol type="a">
64 <li> Added the "ml=" ("Merge-in List") query parameter that works
65 like "rl=" ("Related List") but adds "mionly" style related
66 check-ins instead of the full "rel" style.
67 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -90,27 +92,27 @@
92 <li> The saturation and intensity of user-specified checkin and branch
93 background colors are automatically adjusted to keep the colors
94 compatible with the current skin, unless the
95 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
96 </ol>
97 <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
98 /doc but keeps the title of markdown documents with the document rather
99 that moving it up to the page title.
100 <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
101 and debugging
102 <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
103 decoding of the artifact described by NAME.
104 <li>Improvements to the [/help?cmd=patch|fossil patch] command:
105 <ol type="a">
106 <li> Fix a bug in "fossil patch create" that causes
107 [/help?cmd=revert|fossil revert] operations that happened
108 on individualfiles after a [/help?cmd=merge|fossil merge]
109 to be omitted from the patch.
110 <li> Added the [/help?cmd=patch|patch alias] command for managing
111 aliases for remote checkout names.
112 </ol>
113 <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
114 <ol type="a">
115 <li> Add the ability to search the help text, either in the UI
116 (on the [/help?cmd=/search|/search page]) or from the command-line
117 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
118 <li> Accepts an optional SUBCOMMAND argument following the
@@ -117,11 +119,11 @@
119 COMMAND argument and only shows results for the specified
120 subcommand, not the entire command.
121 <li> The -u (--usage) option shows only the command-line syntax
122 <li> The -o (--options) option shows only the command-line options
123 </ol>
124 <li>Enhancements to the ticket system:
125 <ol type="a">
126 <li> Added the ability to attach wiki pages to a ticket for extended
127 descriptions.
128 <li> Added submenu to the 'View Ticket' page, to use it as
129 template for a new ticket.
@@ -129,21 +131,35 @@
131 in a row.
132 <li> Link the version field in ticket view to a matching checkin or tag.
133 <li> Show creation time in report and ticket view.
134 <li> Show previous comments in edit ticket as reference.
135 </ol>
136 <li>Added the "hash" query parameter to the
137 [/help?cmd=/whatis|/whatis webpage].
138 <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
139 which alerts subscribers when an admin creates a new user or
140 when a user's permissions change.
141 <li>Show project description on repository list.
142 <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
143 the frequency of reconnection attempts over time and providing feedback
144 to the user when the connection is down.
145 <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
146 security:
147 <ol type="a">
148 <li> TH1 now makes a distinction between
149 [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
150 This makes it more difficult to write custom TH1 scripts that
151 contain XSS or SQL-injection bugs. The
152 [/help?cmd=vuln-report|vuln-report] setting was added to control
153 what Fossil does when it encounters a potential TH1
154 security problem.
155 <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
156 command.
157 <li> The "enable_htmlify" TH1 command was removed.
158 </ol>
159 <li>Many other minor fixes and additions.
160 </ol>
161
162 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
163
164 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
165 that have non-ASCII filenames
166
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
1919
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
2020
</b></pre>
2121
2222
<h2 id="workflow" name="fslclone">General Work Flow</h2>
2323
24
-Fossil works with repository files (a database in a single file with the project's
25
-complete history) and with checked-out local trees (the working directory
26
-you use to do your work).
27
-(See [./glossary.md | the glossary] for more background.)
28
-The workflow looks like this:
24
+Fossil works with [./glossary.md#repository | repository files]
25
+and [./glossary.md#check-out | check-out directories] using a
26
+workflow like this:
2927
3028
<ul>
3129
<li>Create or clone a repository file. ([/help/init|fossil init] or
3230
[/help/clone | fossil clone])
3331
<li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
4139
The following sections give a brief overview of these
4240
operations.
4341
4442
<h2 id="new">Starting A New Project</h2>
4543
46
-To start a new project with fossil create a new empty repository
47
-this way: ([/help/init | more info])
44
+To start a new project with Fossil, [/help/init | create a new empty repository]:
4845
4946
<pre><b>fossil init</b> <i>repository-filename</i>
5047
</pre>
5148
5249
You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
8279
<h2 id="clone">Cloning An Existing Repository</h2>
8380
8481
Most fossil operations interact with a repository that is on the
8582
local disk drive, not on a remote system. Hence, before accessing
8683
a remote repository it is necessary to make a local copy of that
87
-repository. Making a local copy of a remote repository is called
88
-"cloning".
84
+repository, a process called
85
+"[/help/clone | cloning]".
8986
90
-Clone a remote repository as follows: ([/help/clone | more info])
87
+This is done as follows:
9188
9289
<pre><b>fossil clone</b> <i>URL repository-filename</i>
9390
</pre>
9491
9592
The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
107104
100% complete...
108105
Extra delta compression...
109106
Vacuuming the database...
110107
project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
111108
server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
112
-admin-user: exampleuser (password is "yoWgDR42iv")>
109
+admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
113110
</b></pre>
111
+
112
+This <i>exampleuser</i> will be used by Fossil as the author of commits when
113
+you checkin changes to the repository. It is also used by Fossil when you
114
+make your repository available to others using the built-in server mode by
115
+running <tt>[/help/server | fossil server]</tt> and will also be used when
116
+running <tt>[/help/ui | fossil ui]</tt> to view the repository through
117
+the Fossil UI. See the quick start topic for setting up a
118
+<a href="#server">server</a> for more details.
114119
115120
If the remote repository requires a login, include a
116121
userid in the URL like this:
117122
118123
<pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
153158
154159
<h2 id="checkout">Checking Out A Local Tree</h2>
155160
156161
To work on a project in fossil, you need to check out a local
157162
copy of the source tree. Create the directory you want to be
158
-the root of your tree and cd into that directory. Then
159
-do this: ([/help/open | more info])
163
+the root of your tree, <tt>cd</tt> into that directory, and then:
160164
161165
<pre><b>fossil open</b> <i>repository-filename</i></pre>
162166
163
-for example:
167
+For example:
164168
165169
<pre><b>fossil open ../myclone.fossil
166170
BUILD.txt
167171
COPYRIGHT-BSD2.txt
168172
README.md
169173
170174
</tt></b></pre>
171175
172
-(or "fossil open ..\myclone.fossil" on Windows).
173
-
174176
This leaves you with the newest version of the tree
175177
checked out.
176178
From anywhere underneath the root of your local tree, you
177179
can type commands like the following to find out the status of
178180
your local tree:
@@ -320,41 +322,60 @@
320322
321323
This will get you started on identifying checkins. The
322324
<a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
323325
how timestamps can also be used.
324326
325
-<h2 id="config">Configuring Your Local Repository</h2>
327
+<h2 id="config">Accessing Your Local Repository's Web User Interface</h2>
326328
327
-When you create a new repository, either by cloning an existing
328
-project or create a new project of your own, you usually want to do some
329
-local configuration. This is easily accomplished using the web-server
330
-that is built into fossil. Start the fossil web server like this:
331
-([/help/ui | more info])
329
+After you create a new repository, you usually want to do some local
330
+configuration. This is most easily accomplished by firing up the Fossil
331
+UI:
332332
333333
<pre>
334334
<b>fossil ui</b> <i>repository-filename</i>
335335
</pre>
336336
337
-You can omit the <i>repository-filename</i> from the command above
337
+You can shorten that to just [/help/ui | <b>fossil ui</b>]
338338
if you are inside a checked-out local tree.
339339
340
-This starts a web server then automatically launches your
341
-web browser and makes it point to this web server. If your system
342
-has an unusual configuration, fossil might not be able to figure out
343
-how to start your web browser. In that case, first tell fossil
344
-where to find your web browser using a command like this:
340
+This command starts an internal web server, after which Fossil
341
+automatically launches your default browser, pointed at itself,
342
+presenting a special view of the repository, its web user interface.
343
+
344
+You may override Fossil's logic for selecting the default browser so:
345345
346346
<pre>
347347
<b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348348
</pre>
349349
350
-By default, fossil does not require a login for HTTP connections
351
-coming in from the IP loopback address 127.0.0.1. You can, and perhaps
352
-should, change this after you create a few users.
350
+When launched this way, Fossil binds its internal web server to the IP
351
+loopback address, 127.0.0.1, which it treats specially, bypassing all
352
+user controls, effectively giving visitors the
353
+[./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].
354
+
355
+Why is that a good idea, you ask? Because it is a safe
356
+presumption that only someone with direct file access to the repository
357
+database file could be using the resulting web interface. Anyone who can
358
+modify the repo DB directly could give themselves any and all access
359
+with a SQL query, or even by direct file manipulation; no amount of
360
+access control matters to such a user.
361
+
362
+(Contrast the [#server | many <i>other</i> ways] of setting Fossil up
363
+as an HTTP server, where the repo DB is on the other side of the HTTP
364
+server wall, inaccessible by all means other than Fossil's own
365
+mediation. For this reason, the "localhost bypasses access control"
366
+policy does <i>not</i> apply to these other interfaces. That is a very
367
+good thing, since without this difference in policy, it would be unsafe
368
+to bind a [/help?cmd=server | <b>fossil server</b>] instance to
369
+localhost on a high-numbered port and then reverse-proxy it out to the
370
+world via HTTPS, a practice this author does engage in, with confidence.)
353371
354
-When you are finished configuring, just press Control-C or use
355
-the <b>kill</b> command to shut down the mini-server.
372
+Once you are finished configuring Fossil, you may safely Control-C out
373
+of the <b>fossil&nbsp;ui</b> command to shut down this privileged
374
+built-in web server. Moreover, you may by grace of SQLite do this <i>at
375
+any time</i>: all changes are either committed durably to the repo DB or
376
+rolled back, in their totality. This includes configuration changes.
356377
357378
<h2 id="sharing">Sharing Changes</h2>
358379
359380
When [./concepts.wiki#workflow|autosync] is turned off,
360381
the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
464485
level of undo/redo.
465486
466487
467488
<h2 id="server">Setting Up A Server</h2>
468489
469
-Fossil can act as a stand-alone web server using one of these
470
-commands:
490
+In addition to the inward-facing <b>fossil ui</b> mode covered [#config
491
+| above], Fossil can also act as an outward-facing web server:
471492
472493
<pre>
473494
<b>[/help/server | fossil server]</b> <i>repository-filename</i>
474
-<b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
475495
</pre>
476496
477
-The <i>repository-filename</i> can be omitted when these commands
478
-are run from within an open check-out, which is a particularly useful
479
-shortcut with the <b>fossil ui</b> command.
480
-
481
-The <b>ui</b> command is intended for accessing the web user interface
482
-from a local desktop. (We sometimes call this mode "Fossil UI.")
483
-The <b>ui</b> command differs from the
484
-<b>server</b> command by binding to the loopback IP
485
-address only (thus making the web UI visible only on the
486
-local machine) and by automatically starting your default web browser,
487
-pointing it at the running UI
488
-server. The localhost restriction exists because it also gives anyone
489
-who can access the resulting web UI full control over the
490
-repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
491
-Setup capabliity].)
492
-
493
-For cross-machine collaboration, use the <b>server</b> command instead,
494
-which binds on all IP addresses, does not try to start a web browser,
495
-and enforces [./caps/ | Fossil's role-based access control system].
496
-
497
-Servers are also easily configured as:
497
+Just as with <b>fossil ui</b>, you may omit the
498
+<i>repository-filename</i> parameter when running this from within an open
499
+check-out.
500
+
501
+<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
502
+interfaces by default in this mode, and it enforces the configured
503
+[./caps/ | role-based access controls]. Further, because it is meant to
504
+provide external web service, it doesn't try to launch a local web
505
+browser pointing to a "Fossil UI" presentation; external visitors see
506
+your repository's configured home page instead.
507
+
508
+To serve varying needs, there are additional ways to serve a Fossil repo
509
+to external users:
498510
499511
<ul>
512
+<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
513
+ self-hosting repositories]
514
+<li>[./server/any/scgi.md|SCGI]
500515
<li>[./server/any/inetd.md|inetd]
501516
<li>[./server/debian/service.md|systemd]
502
-<li>[./server/any/cgi.md|CGI]
503
-<li>[./server/any/scgi.md|SCGI]
504517
</ul>
505518
506519
…along with [./server/#matrix | several other options].
507520
508
-The [./selfhost.wiki | self-hosting fossil repositories] use
509
-CGI.
510
-
511
-You might <i>need</i> to set up a server, whether you know it yet or
512
-not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
513
-article for details.
521
+We recommend that you read the [./server/whyuseaserver.wiki | Benefits
522
+of a Fossil Server] article, because you might <i>need</i> to do this
523
+and not yet know it.
514524
515525
<h2 id="proxy">HTTP Proxies</h2>
516526
517527
If you are behind a restrictive firewall that requires you to use
518528
an HTTP proxy to reach the internet, then you can configure the proxy
519529
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
19 This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
20 </b></pre>
21
22 <h2 id="workflow" name="fslclone">General Work Flow</h2>
23
24 Fossil works with repository files (a database in a single file with the project's
25 complete history) and with checked-out local trees (the working directory
26 you use to do your work).
27 (See [./glossary.md | the glossary] for more background.)
28 The workflow looks like this:
29
30 <ul>
31 <li>Create or clone a repository file. ([/help/init|fossil init] or
32 [/help/clone | fossil clone])
33 <li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
41 The following sections give a brief overview of these
42 operations.
43
44 <h2 id="new">Starting A New Project</h2>
45
46 To start a new project with fossil create a new empty repository
47 this way: ([/help/init | more info])
48
49 <pre><b>fossil init</b> <i>repository-filename</i>
50 </pre>
51
52 You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
82 <h2 id="clone">Cloning An Existing Repository</h2>
83
84 Most fossil operations interact with a repository that is on the
85 local disk drive, not on a remote system. Hence, before accessing
86 a remote repository it is necessary to make a local copy of that
87 repository. Making a local copy of a remote repository is called
88 "cloning".
89
90 Clone a remote repository as follows: ([/help/clone | more info])
91
92 <pre><b>fossil clone</b> <i>URL repository-filename</i>
93 </pre>
94
95 The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
107 100% complete...
108 Extra delta compression...
109 Vacuuming the database...
110 project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
111 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
112 admin-user: exampleuser (password is "yoWgDR42iv")>
113 </b></pre>
 
 
 
 
 
 
 
 
114
115 If the remote repository requires a login, include a
116 userid in the URL like this:
117
118 <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
153
154 <h2 id="checkout">Checking Out A Local Tree</h2>
155
156 To work on a project in fossil, you need to check out a local
157 copy of the source tree. Create the directory you want to be
158 the root of your tree and cd into that directory. Then
159 do this: ([/help/open | more info])
160
161 <pre><b>fossil open</b> <i>repository-filename</i></pre>
162
163 for example:
164
165 <pre><b>fossil open ../myclone.fossil
166 BUILD.txt
167 COPYRIGHT-BSD2.txt
168 README.md
169
170 </tt></b></pre>
171
172 (or "fossil open ..\myclone.fossil" on Windows).
173
174 This leaves you with the newest version of the tree
175 checked out.
176 From anywhere underneath the root of your local tree, you
177 can type commands like the following to find out the status of
178 your local tree:
@@ -320,41 +322,60 @@
320
321 This will get you started on identifying checkins. The
322 <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
323 how timestamps can also be used.
324
325 <h2 id="config">Configuring Your Local Repository</h2>
326
327 When you create a new repository, either by cloning an existing
328 project or create a new project of your own, you usually want to do some
329 local configuration. This is easily accomplished using the web-server
330 that is built into fossil. Start the fossil web server like this:
331 ([/help/ui | more info])
332
333 <pre>
334 <b>fossil ui</b> <i>repository-filename</i>
335 </pre>
336
337 You can omit the <i>repository-filename</i> from the command above
338 if you are inside a checked-out local tree.
339
340 This starts a web server then automatically launches your
341 web browser and makes it point to this web server. If your system
342 has an unusual configuration, fossil might not be able to figure out
343 how to start your web browser. In that case, first tell fossil
344 where to find your web browser using a command like this:
345
346 <pre>
347 <b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348 </pre>
349
350 By default, fossil does not require a login for HTTP connections
351 coming in from the IP loopback address 127.0.0.1. You can, and perhaps
352 should, change this after you create a few users.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
354 When you are finished configuring, just press Control-C or use
355 the <b>kill</b> command to shut down the mini-server.
 
 
 
356
357 <h2 id="sharing">Sharing Changes</h2>
358
359 When [./concepts.wiki#workflow|autosync] is turned off,
360 the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
464 level of undo/redo.
465
466
467 <h2 id="server">Setting Up A Server</h2>
468
469 Fossil can act as a stand-alone web server using one of these
470 commands:
471
472 <pre>
473 <b>[/help/server | fossil server]</b> <i>repository-filename</i>
474 <b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
475 </pre>
476
477 The <i>repository-filename</i> can be omitted when these commands
478 are run from within an open check-out, which is a particularly useful
479 shortcut with the <b>fossil ui</b> command.
480
481 The <b>ui</b> command is intended for accessing the web user interface
482 from a local desktop. (We sometimes call this mode "Fossil UI.")
483 The <b>ui</b> command differs from the
484 <b>server</b> command by binding to the loopback IP
485 address only (thus making the web UI visible only on the
486 local machine) and by automatically starting your default web browser,
487 pointing it at the running UI
488 server. The localhost restriction exists because it also gives anyone
489 who can access the resulting web UI full control over the
490 repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
491 Setup capabliity].)
492
493 For cross-machine collaboration, use the <b>server</b> command instead,
494 which binds on all IP addresses, does not try to start a web browser,
495 and enforces [./caps/ | Fossil's role-based access control system].
496
497 Servers are also easily configured as:
498
499 <ul>
 
 
 
500 <li>[./server/any/inetd.md|inetd]
501 <li>[./server/debian/service.md|systemd]
502 <li>[./server/any/cgi.md|CGI]
503 <li>[./server/any/scgi.md|SCGI]
504 </ul>
505
506 …along with [./server/#matrix | several other options].
507
508 The [./selfhost.wiki | self-hosting fossil repositories] use
509 CGI.
510
511 You might <i>need</i> to set up a server, whether you know it yet or
512 not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
513 article for details.
514
515 <h2 id="proxy">HTTP Proxies</h2>
516
517 If you are behind a restrictive firewall that requires you to use
518 an HTTP proxy to reach the internet, then you can configure the proxy
519
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
19 This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
20 </b></pre>
21
22 <h2 id="workflow" name="fslclone">General Work Flow</h2>
23
24 Fossil works with [./glossary.md#repository | repository files]
25 and [./glossary.md#check-out | check-out directories] using a
26 workflow like this:
 
 
27
28 <ul>
29 <li>Create or clone a repository file. ([/help/init|fossil init] or
30 [/help/clone | fossil clone])
31 <li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
39 The following sections give a brief overview of these
40 operations.
41
42 <h2 id="new">Starting A New Project</h2>
43
44 To start a new project with Fossil, [/help/init | create a new empty repository]:
 
45
46 <pre><b>fossil init</b> <i>repository-filename</i>
47 </pre>
48
49 You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
79 <h2 id="clone">Cloning An Existing Repository</h2>
80
81 Most fossil operations interact with a repository that is on the
82 local disk drive, not on a remote system. Hence, before accessing
83 a remote repository it is necessary to make a local copy of that
84 repository, a process called
85 "[/help/clone | cloning]".
86
87 This is done as follows:
88
89 <pre><b>fossil clone</b> <i>URL repository-filename</i>
90 </pre>
91
92 The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
104 100% complete...
105 Extra delta compression...
106 Vacuuming the database...
107 project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
108 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
109 admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
110 </b></pre>
111
112 This <i>exampleuser</i> will be used by Fossil as the author of commits when
113 you checkin changes to the repository. It is also used by Fossil when you
114 make your repository available to others using the built-in server mode by
115 running <tt>[/help/server | fossil server]</tt> and will also be used when
116 running <tt>[/help/ui | fossil ui]</tt> to view the repository through
117 the Fossil UI. See the quick start topic for setting up a
118 <a href="#server">server</a> for more details.
119
120 If the remote repository requires a login, include a
121 userid in the URL like this:
122
123 <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
158
159 <h2 id="checkout">Checking Out A Local Tree</h2>
160
161 To work on a project in fossil, you need to check out a local
162 copy of the source tree. Create the directory you want to be
163 the root of your tree, <tt>cd</tt> into that directory, and then:
 
164
165 <pre><b>fossil open</b> <i>repository-filename</i></pre>
166
167 For example:
168
169 <pre><b>fossil open ../myclone.fossil
170 BUILD.txt
171 COPYRIGHT-BSD2.txt
172 README.md
173
174 </tt></b></pre>
175
 
 
176 This leaves you with the newest version of the tree
177 checked out.
178 From anywhere underneath the root of your local tree, you
179 can type commands like the following to find out the status of
180 your local tree:
@@ -320,41 +322,60 @@
322
323 This will get you started on identifying checkins. The
324 <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
325 how timestamps can also be used.
326
327 <h2 id="config">Accessing Your Local Repository's Web User Interface</h2>
328
329 After you create a new repository, you usually want to do some local
330 configuration. This is most easily accomplished by firing up the Fossil
331 UI:
 
 
332
333 <pre>
334 <b>fossil ui</b> <i>repository-filename</i>
335 </pre>
336
337 You can shorten that to just [/help/ui | <b>fossil ui</b>]
338 if you are inside a checked-out local tree.
339
340 This command starts an internal web server, after which Fossil
341 automatically launches your default browser, pointed at itself,
342 presenting a special view of the repository, its web user interface.
343
344 You may override Fossil's logic for selecting the default browser so:
345
346 <pre>
347 <b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348 </pre>
349
350 When launched this way, Fossil binds its internal web server to the IP
351 loopback address, 127.0.0.1, which it treats specially, bypassing all
352 user controls, effectively giving visitors the
353 [./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].
354
355 Why is that a good idea, you ask? Because it is a safe
356 presumption that only someone with direct file access to the repository
357 database file could be using the resulting web interface. Anyone who can
358 modify the repo DB directly could give themselves any and all access
359 with a SQL query, or even by direct file manipulation; no amount of
360 access control matters to such a user.
361
362 (Contrast the [#server | many <i>other</i> ways] of setting Fossil up
363 as an HTTP server, where the repo DB is on the other side of the HTTP
364 server wall, inaccessible by all means other than Fossil's own
365 mediation. For this reason, the "localhost bypasses access control"
366 policy does <i>not</i> apply to these other interfaces. That is a very
367 good thing, since without this difference in policy, it would be unsafe
368 to bind a [/help?cmd=server | <b>fossil server</b>] instance to
369 localhost on a high-numbered port and then reverse-proxy it out to the
370 world via HTTPS, a practice this author does engage in, with confidence.)
371
372 Once you are finished configuring Fossil, you may safely Control-C out
373 of the <b>fossil&nbsp;ui</b> command to shut down this privileged
374 built-in web server. Moreover, you may by grace of SQLite do this <i>at
375 any time</i>: all changes are either committed durably to the repo DB or
376 rolled back, in their totality. This includes configuration changes.
377
378 <h2 id="sharing">Sharing Changes</h2>
379
380 When [./concepts.wiki#workflow|autosync] is turned off,
381 the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
485 level of undo/redo.
486
487
488 <h2 id="server">Setting Up A Server</h2>
489
490 In addition to the inward-facing <b>fossil ui</b> mode covered [#config
491 | above], Fossil can also act as an outward-facing web server:
492
493 <pre>
494 <b>[/help/server | fossil server]</b> <i>repository-filename</i>
 
495 </pre>
496
497 Just as with <b>fossil ui</b>, you may omit the
498 <i>repository-filename</i> parameter when running this from within an open
499 check-out.
500
501 <i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
502 interfaces by default in this mode, and it enforces the configured
503 [./caps/ | role-based access controls]. Further, because it is meant to
504 provide external web service, it doesn't try to launch a local web
505 browser pointing to a "Fossil UI" presentation; external visitors see
506 your repository's configured home page instead.
507
508 To serve varying needs, there are additional ways to serve a Fossil repo
509 to external users:
 
 
 
 
 
 
 
 
510
511 <ul>
512 <li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
513 self-hosting repositories]
514 <li>[./server/any/scgi.md|SCGI]
515 <li>[./server/any/inetd.md|inetd]
516 <li>[./server/debian/service.md|systemd]
 
 
517 </ul>
518
519 …along with [./server/#matrix | several other options].
520
521 We recommend that you read the [./server/whyuseaserver.wiki | Benefits
522 of a Fossil Server] article, because you might <i>need</i> to do this
523 and not yet know it.
 
 
 
524
525 <h2 id="proxy">HTTP Proxies</h2>
526
527 If you are behind a restrictive firewall that requires you to use
528 an HTTP proxy to reach the internet, then you can configure the proxy
529
+87 -24
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
6868
are removed from each token by the command parser.) The third token
6969
is the `puts "hello"`, with its whitespace and newlines. The fourth token
7070
is `else` and the fifth and last token is `puts "world"`.
7171
7272
The `if` command evaluates its first argument (the second token)
73
-as an expression, and if that expression is true, evaluates its
73
+as an expression, and if that expression is true, it evaluates its
7474
second argument (the third token) as a TH1 script.
7575
If the expression is false and the third argument is `else`, then
7676
the fourth argument is evaluated as a TH1 expression.
7777
7878
So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106106
$repository "" info trunk]]] end]
107107
108108
Those backslashes allow the command to wrap nicely within a standard
109109
terminal width while telling the interpreter to consider those three
110110
lines as a single command.
111
+
112
+<a id="taint"></a>Tainted And Untainted Strings
113
+-----------------------------------------------
114
+
115
+Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
116
+"tainted" and "untainted" strings. Tainted strings are strings that are
117
+derived from user inputs that might contain text that is designed to subvert
118
+the script. Untainted strings are known to come from secure sources and
119
+are assumed to contain no malicious content.
120
+
121
+Beginning with Fossil version 2.26, and depending on the value of the
122
+[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
123
+strings from being used in ways that might lead to XSS or SQL-injection
124
+attacks. This feature helps to ensure that XSS and SQL-injection
125
+vulnerabilities are not *accidentally* added to Fossil when
126
+custom TH1 scripts for headers or footers or tickets are added to a
127
+repository. Note that the tainted/untainted distinction in strings does
128
+not make it impossible to introduce XSS and SQL-injections vulnerabilities
129
+using poorly-written TH1 scripts; it just makes it more difficult and
130
+less likely to happen by accident. Developers must still consider the
131
+security implications TH1 customizations they add to Fossil, and take
132
+appropriate precautions when writing custom TH1. Peer review of TH1
133
+script changes is encouraged.
134
+
135
+In Fossil version 2.26, if the vuln-report setting is set to "block"
136
+or "fatal", the [html](#html) and [query](#query) TH1 commands will
137
+fail with an error if their argument is a tainted string. This helps
138
+to prevent XSS and SQL-injection attacks, respectively. Note that
139
+the default value of the vuln-report setting is "log", which allows those
140
+commands to continue working and only writes a warning message into the
141
+error log. <b>Future versions of Fossil may change the default value
142
+of the vuln-report setting to "block" or "fatal".</b> Fossil users
143
+with customized TH1 scripts are encouraged to audit their customizations
144
+and fix any potential vulnerabilities soon, so as to avoid breakage
145
+caused by future upgrades. <b>Future versions of Fossil might also
146
+place additional restrictions on the use of tainted strings.</b>
147
+For example, it is likely that future versions of Fossil will disallow
148
+using tainted strings as script, for example as the body of a "for"
149
+loop or of a "proc".
111150
112151
113152
Summary of Core TH1 Commands
114153
----------------------------
115154
@@ -147,10 +186,13 @@
147186
* string last NEEDLE HAYSTACK ?START-INDEX?
148187
* string match PATTERN STRING
149188
* string length STRING
150189
* string range STRING FIRST LAST
151190
* string repeat STRING COUNT
191
+ * string trim STRING
192
+ * string trimleft STRING
193
+ * string trimright STRING
152194
* unset VARNAME
153195
* uplevel ?LEVEL? SCRIPT
154196
* upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155197
156198
All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
182224
* [copybtn](#copybtn)
183225
* [date](#date)
184226
* [decorate](#decorate)
185227
* [defHeader](#defHeader)
186228
* [dir](#dir)
187
- * [enable\_htmlify](#enable_htmlify)
188229
* [enable\_output](#enable_output)
189230
* [encode64](#encode64)
190231
* [getParameter](#getParameter)
191232
* [glob\_match](#glob_match)
192233
* [globalState](#globalState)
@@ -214,17 +255,19 @@
214255
* [stime](#stime)
215256
* [styleHeader](#styleHeader)
216257
* [styleFooter](#styleFooter)
217258
* [styleScript](#styleScript)
218259
* [submenu](#submenu)
260
+ * [taint](#taintCmd)
219261
* [tclEval](#tclEval)
220262
* [tclExpr](#tclExpr)
221263
* [tclInvoke](#tclInvoke)
222264
* [tclIsSafe](#tclIsSafe)
223265
* [tclMakeSafe](#tclMakeSafe)
224266
* [tclReady](#tclReady)
225267
* [trace](#trace)
268
+ * [untaint](#untaintCmd)
226269
* [unversioned content](#unversioned_content)
227270
* [unversioned list](#unversioned_list)
228271
* [utime](#utime)
229272
* [verifyCsrf](#verifyCsrf)
230273
* [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
413456
the files matching the pattern GLOB within CHECKIN will be returned.
414457
If DETAILS is non-zero, the result will be a list-of-lists, with each
415458
element containing at least three elements: the file name, the file
416459
size (in bytes), and the file last modification time (relative to the
417460
time zone configured for the repository).
418
-
419
-<a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
-
421
- * enable\_htmlify
422
- * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
-
424
-By default, certain output from `puts` and similar commands is escaped
425
-for HTML. The first call form returns the current state of that
426
-feature: `1` for on and `0` for off. The second call form enables
427
-(non-0) or disables (0) that feature and returns the *pre-call* state
428
-of that feature (so that a second call can pass that value to restore
429
-it to its previous state). The optional `TRACE-LABEL` argument causes
430
-the TH1 tracing output (if enabled) to add a marker when the second
431
-form of this command is invoked, and includes that label and the
432
-boolean argument's value in the trace. If tracing is disabled, that
433
-argument has no effect.
434
-
435461
436462
<a id="enable_output"></a>TH1 enable\_output Command
437463
------------------------------------------------------
438464
439465
* enable\_output BOOLEAN
@@ -528,22 +553,24 @@
528553
-----------------------------------
529554
530555
* html STRING
531556
532557
Outputs the STRING literally. It is assumed that STRING contains
533
-valid HTML, or that if it text then any characters that are
558
+valid HTML, or that if STRING contains any characters that are
534559
significant to HTML (such as `<`, `>`, `'`, or `&`) have already
535
-been escaped, perhaps by the htmlize command. Use the
560
+been escaped, perhaps by the [htmlize](#htmlize) command. Use the
536561
[puts](#puts) command to output text that might contain unescaped
537562
HTML markup.
538563
539564
**Beware of XSS attacks!** If the STRING value to the html command
540565
can be controlled by a hostile user, then he might be able to sneak
541566
in malicious HTML or Javascript which could result in a
542567
cross-site scripting (XSS) attack. Be careful that all text that
543568
in STRING that might come from user input has been sanitized by the
544
-[htmlize](#htmlize) command or similar.
569
+[htmlize](#htmlize) command or similar. In recent versions of Fossil,
570
+the STRING value must be [untainted](#taint) or else the "html" command
571
+will fail.
545572
546573
<a id="htmlize"></a>TH1 htmlize Command
547574
-----------------------------------------
548575
549576
* htmlize STRING
@@ -610,11 +637,13 @@
610637
* puts STRING
611638
612639
Outputs STRING. Characters within STRING that have special meaning
613640
in HTML are escaped prior to being output. Thus is it safe for STRING
614641
to be derived from user inputs. See also the [html](#html) command
615
-which behaves similarly except does not escape HTML markup.
642
+which behaves similarly except does not escape HTML markup. This
643
+command ("puts") is safe to use on [tainted strings](#taint), but the "html"
644
+command is not.
616645
617646
<a id="query"></a>TH1 query Command
618647
-------------------------------------
619648
620649
* query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
622651
Runs the SQL query given by the SQL argument. For each row in the result
623652
set, run CODE.
624653
625654
In SQL, parameters such as $var are filled in using the value of variable
626655
"var". Result values are stored in variables with the column name prior
627
-to each invocation of CODE.
656
+to each invocation of CODE. The names of the variables in which results
657
+are stored can be controlled using "AS name" clauses in the SQL. As
658
+the database will often contain content that originates from untrusted
659
+users, all result values are marked as [tainted](#taint).
628660
629661
**Beware of SQL injections in the `query` command!**
630662
The SQL argument to the query command should always be literal SQL
631663
text enclosed in {...}. The SQL argument should never be a double-quoted
632664
string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
649681
~~~
650682
651683
In this second example, TH1 does the expansion of `$mykey` prior to passing
652684
the text down into SQLite. So if `$mykey` contains a single-quote character,
653685
followed by additional hostile text, that will result in an SQL injection.
686
+
687
+To help guard against SQL-injections, recent versions of Fossil require
688
+that the SQL argument be [untainted](#taint) or else the "query" command
689
+will fail.
654690
655691
<a id="randhex"></a>TH1 randhex Command
656692
-----------------------------------------
657693
658694
* randhex N
@@ -783,10 +819,24 @@
783819
784820
* submenu link LABEL URL
785821
786822
Add hyperlink to the submenu of the current page.
787823
824
+<a id="taintCmd"></a>TH1 taint Command
825
+-----------------------------------------
826
+
827
+ * taint STRING
828
+
829
+This command returns a copy of STRING that has been marked as
830
+[tainted](#taint). Tainted strings are strings which might be
831
+controlled by an attacker and might contain hostile inputs and
832
+are thus unsafe to use in certain contexts. For example, tainted
833
+strings should not be output as part of a webpage as they might
834
+contain rogue HTML or Javascript that could lead to an XSS
835
+vulnerability. Similarly, tainted strings should not be run as
836
+SQL since they might contain an SQL-injection vulerability.
837
+
788838
<a id="tclEval"></a>TH1 tclEval Command
789839
-----------------------------------------
790840
791841
**This command requires the Tcl integration feature.**
792842
@@ -854,10 +904,22 @@
854904
855905
* trace STRING
856906
857907
Generates a TH1 trace message if TH1 tracing is enabled.
858908
909
+<a id="untaintCmd"></a>TH1 taint Command
910
+-----------------------------------------
911
+
912
+ * untaint STRING
913
+
914
+This command returns a copy of STRING that has been marked as
915
+[untainted](#taint). Untainted strings are strings which are
916
+believed to be free of potentially hostile content. Use this
917
+command with caution, as it overwrites the tainted-string protection
918
+mechanisms that are built into TH1. If you do not understand all
919
+the implications of executing this command, then do not use it.
920
+
859921
<a id="unversioned_content"></a>TH1 unversioned content Command
860922
-----------------------------------------------------------------
861923
862924
* unversioned content FILENAME
863925
864926
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
68 are removed from each token by the command parser.) The third token
69 is the `puts "hello"`, with its whitespace and newlines. The fourth token
70 is `else` and the fifth and last token is `puts "world"`.
71
72 The `if` command evaluates its first argument (the second token)
73 as an expression, and if that expression is true, evaluates its
74 second argument (the third token) as a TH1 script.
75 If the expression is false and the third argument is `else`, then
76 the fourth argument is evaluated as a TH1 expression.
77
78 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106 $repository "" info trunk]]] end]
107
108 Those backslashes allow the command to wrap nicely within a standard
109 terminal width while telling the interpreter to consider those three
110 lines as a single command.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
112
113 Summary of Core TH1 Commands
114 ----------------------------
115
@@ -147,10 +186,13 @@
147 * string last NEEDLE HAYSTACK ?START-INDEX?
148 * string match PATTERN STRING
149 * string length STRING
150 * string range STRING FIRST LAST
151 * string repeat STRING COUNT
 
 
 
152 * unset VARNAME
153 * uplevel ?LEVEL? SCRIPT
154 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155
156 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
182 * [copybtn](#copybtn)
183 * [date](#date)
184 * [decorate](#decorate)
185 * [defHeader](#defHeader)
186 * [dir](#dir)
187 * [enable\_htmlify](#enable_htmlify)
188 * [enable\_output](#enable_output)
189 * [encode64](#encode64)
190 * [getParameter](#getParameter)
191 * [glob\_match](#glob_match)
192 * [globalState](#globalState)
@@ -214,17 +255,19 @@
214 * [stime](#stime)
215 * [styleHeader](#styleHeader)
216 * [styleFooter](#styleFooter)
217 * [styleScript](#styleScript)
218 * [submenu](#submenu)
 
219 * [tclEval](#tclEval)
220 * [tclExpr](#tclExpr)
221 * [tclInvoke](#tclInvoke)
222 * [tclIsSafe](#tclIsSafe)
223 * [tclMakeSafe](#tclMakeSafe)
224 * [tclReady](#tclReady)
225 * [trace](#trace)
 
226 * [unversioned content](#unversioned_content)
227 * [unversioned list](#unversioned_list)
228 * [utime](#utime)
229 * [verifyCsrf](#verifyCsrf)
230 * [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
413 the files matching the pattern GLOB within CHECKIN will be returned.
414 If DETAILS is non-zero, the result will be a list-of-lists, with each
415 element containing at least three elements: the file name, the file
416 size (in bytes), and the file last modification time (relative to the
417 time zone configured for the repository).
418
419 <a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
421 * enable\_htmlify
422 * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
424 By default, certain output from `puts` and similar commands is escaped
425 for HTML. The first call form returns the current state of that
426 feature: `1` for on and `0` for off. The second call form enables
427 (non-0) or disables (0) that feature and returns the *pre-call* state
428 of that feature (so that a second call can pass that value to restore
429 it to its previous state). The optional `TRACE-LABEL` argument causes
430 the TH1 tracing output (if enabled) to add a marker when the second
431 form of this command is invoked, and includes that label and the
432 boolean argument's value in the trace. If tracing is disabled, that
433 argument has no effect.
434
435
436 <a id="enable_output"></a>TH1 enable\_output Command
437 ------------------------------------------------------
438
439 * enable\_output BOOLEAN
@@ -528,22 +553,24 @@
528 -----------------------------------
529
530 * html STRING
531
532 Outputs the STRING literally. It is assumed that STRING contains
533 valid HTML, or that if it text then any characters that are
534 significant to HTML (such as `<`, `>`, `'`, or `&`) have already
535 been escaped, perhaps by the htmlize command. Use the
536 [puts](#puts) command to output text that might contain unescaped
537 HTML markup.
538
539 **Beware of XSS attacks!** If the STRING value to the html command
540 can be controlled by a hostile user, then he might be able to sneak
541 in malicious HTML or Javascript which could result in a
542 cross-site scripting (XSS) attack. Be careful that all text that
543 in STRING that might come from user input has been sanitized by the
544 [htmlize](#htmlize) command or similar.
 
 
545
546 <a id="htmlize"></a>TH1 htmlize Command
547 -----------------------------------------
548
549 * htmlize STRING
@@ -610,11 +637,13 @@
610 * puts STRING
611
612 Outputs STRING. Characters within STRING that have special meaning
613 in HTML are escaped prior to being output. Thus is it safe for STRING
614 to be derived from user inputs. See also the [html](#html) command
615 which behaves similarly except does not escape HTML markup.
 
 
616
617 <a id="query"></a>TH1 query Command
618 -------------------------------------
619
620 * query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
622 Runs the SQL query given by the SQL argument. For each row in the result
623 set, run CODE.
624
625 In SQL, parameters such as $var are filled in using the value of variable
626 "var". Result values are stored in variables with the column name prior
627 to each invocation of CODE.
 
 
 
628
629 **Beware of SQL injections in the `query` command!**
630 The SQL argument to the query command should always be literal SQL
631 text enclosed in {...}. The SQL argument should never be a double-quoted
632 string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
649 ~~~
650
651 In this second example, TH1 does the expansion of `$mykey` prior to passing
652 the text down into SQLite. So if `$mykey` contains a single-quote character,
653 followed by additional hostile text, that will result in an SQL injection.
 
 
 
 
654
655 <a id="randhex"></a>TH1 randhex Command
656 -----------------------------------------
657
658 * randhex N
@@ -783,10 +819,24 @@
783
784 * submenu link LABEL URL
785
786 Add hyperlink to the submenu of the current page.
787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788 <a id="tclEval"></a>TH1 tclEval Command
789 -----------------------------------------
790
791 **This command requires the Tcl integration feature.**
792
@@ -854,10 +904,22 @@
854
855 * trace STRING
856
857 Generates a TH1 trace message if TH1 tracing is enabled.
858
 
 
 
 
 
 
 
 
 
 
 
 
859 <a id="unversioned_content"></a>TH1 unversioned content Command
860 -----------------------------------------------------------------
861
862 * unversioned content FILENAME
863
864
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
68 are removed from each token by the command parser.) The third token
69 is the `puts "hello"`, with its whitespace and newlines. The fourth token
70 is `else` and the fifth and last token is `puts "world"`.
71
72 The `if` command evaluates its first argument (the second token)
73 as an expression, and if that expression is true, it evaluates its
74 second argument (the third token) as a TH1 script.
75 If the expression is false and the third argument is `else`, then
76 the fourth argument is evaluated as a TH1 expression.
77
78 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106 $repository "" info trunk]]] end]
107
108 Those backslashes allow the command to wrap nicely within a standard
109 terminal width while telling the interpreter to consider those three
110 lines as a single command.
111
112 <a id="taint"></a>Tainted And Untainted Strings
113 -----------------------------------------------
114
115 Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
116 "tainted" and "untainted" strings. Tainted strings are strings that are
117 derived from user inputs that might contain text that is designed to subvert
118 the script. Untainted strings are known to come from secure sources and
119 are assumed to contain no malicious content.
120
121 Beginning with Fossil version 2.26, and depending on the value of the
122 [vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
123 strings from being used in ways that might lead to XSS or SQL-injection
124 attacks. This feature helps to ensure that XSS and SQL-injection
125 vulnerabilities are not *accidentally* added to Fossil when
126 custom TH1 scripts for headers or footers or tickets are added to a
127 repository. Note that the tainted/untainted distinction in strings does
128 not make it impossible to introduce XSS and SQL-injections vulnerabilities
129 using poorly-written TH1 scripts; it just makes it more difficult and
130 less likely to happen by accident. Developers must still consider the
131 security implications TH1 customizations they add to Fossil, and take
132 appropriate precautions when writing custom TH1. Peer review of TH1
133 script changes is encouraged.
134
135 In Fossil version 2.26, if the vuln-report setting is set to "block"
136 or "fatal", the [html](#html) and [query](#query) TH1 commands will
137 fail with an error if their argument is a tainted string. This helps
138 to prevent XSS and SQL-injection attacks, respectively. Note that
139 the default value of the vuln-report setting is "log", which allows those
140 commands to continue working and only writes a warning message into the
141 error log. <b>Future versions of Fossil may change the default value
142 of the vuln-report setting to "block" or "fatal".</b> Fossil users
143 with customized TH1 scripts are encouraged to audit their customizations
144 and fix any potential vulnerabilities soon, so as to avoid breakage
145 caused by future upgrades. <b>Future versions of Fossil might also
146 place additional restrictions on the use of tainted strings.</b>
147 For example, it is likely that future versions of Fossil will disallow
148 using tainted strings as script, for example as the body of a "for"
149 loop or of a "proc".
150
151
152 Summary of Core TH1 Commands
153 ----------------------------
154
@@ -147,10 +186,13 @@
186 * string last NEEDLE HAYSTACK ?START-INDEX?
187 * string match PATTERN STRING
188 * string length STRING
189 * string range STRING FIRST LAST
190 * string repeat STRING COUNT
191 * string trim STRING
192 * string trimleft STRING
193 * string trimright STRING
194 * unset VARNAME
195 * uplevel ?LEVEL? SCRIPT
196 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
197
198 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
224 * [copybtn](#copybtn)
225 * [date](#date)
226 * [decorate](#decorate)
227 * [defHeader](#defHeader)
228 * [dir](#dir)
 
229 * [enable\_output](#enable_output)
230 * [encode64](#encode64)
231 * [getParameter](#getParameter)
232 * [glob\_match](#glob_match)
233 * [globalState](#globalState)
@@ -214,17 +255,19 @@
255 * [stime](#stime)
256 * [styleHeader](#styleHeader)
257 * [styleFooter](#styleFooter)
258 * [styleScript](#styleScript)
259 * [submenu](#submenu)
260 * [taint](#taintCmd)
261 * [tclEval](#tclEval)
262 * [tclExpr](#tclExpr)
263 * [tclInvoke](#tclInvoke)
264 * [tclIsSafe](#tclIsSafe)
265 * [tclMakeSafe](#tclMakeSafe)
266 * [tclReady](#tclReady)
267 * [trace](#trace)
268 * [untaint](#untaintCmd)
269 * [unversioned content](#unversioned_content)
270 * [unversioned list](#unversioned_list)
271 * [utime](#utime)
272 * [verifyCsrf](#verifyCsrf)
273 * [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
456 the files matching the pattern GLOB within CHECKIN will be returned.
457 If DETAILS is non-zero, the result will be a list-of-lists, with each
458 element containing at least three elements: the file name, the file
459 size (in bytes), and the file last modification time (relative to the
460 time zone configured for the repository).
 
 
-------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
462 <a id="enable_output"></a>TH1 enable\_output Command
463 ------------------------------------------------------
464
465 * enable\_output BOOLEAN
@@ -528,22 +553,24 @@
553 -----------------------------------
554
555 * html STRING
556
557 Outputs the STRING literally. It is assumed that STRING contains
558 valid HTML, or that if STRING contains any characters that are
559 significant to HTML (such as `<`, `>`, `'`, or `&`) have already
560 been escaped, perhaps by the [htmlize](#htmlize) command. Use the
561 [puts](#puts) command to output text that might contain unescaped
562 HTML markup.
563
564 **Beware of XSS attacks!** If the STRING value to the html command
565 can be controlled by a hostile user, then he might be able to sneak
566 in malicious HTML or Javascript which could result in a
567 cross-site scripting (XSS) attack. Be careful that all text that
568 in STRING that might come from user input has been sanitized by the
569 [htmlize](#htmlize) command or similar. In recent versions of Fossil,
570 the STRING value must be [untainted](#taint) or else the "html" command
571 will fail.
572
573 <a id="htmlize"></a>TH1 htmlize Command
574 -----------------------------------------
575
576 * htmlize STRING
@@ -610,11 +637,13 @@
637 * puts STRING
638
639 Outputs STRING. Characters within STRING that have special meaning
640 in HTML are escaped prior to being output. Thus is it safe for STRING
641 to be derived from user inputs. See also the [html](#html) command
642 which behaves similarly except does not escape HTML markup. This
643 command ("puts") is safe to use on [tainted strings](#taint), but the "html"
644 command is not.
645
646 <a id="query"></a>TH1 query Command
647 -------------------------------------
648
649 * query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
651 Runs the SQL query given by the SQL argument. For each row in the result
652 set, run CODE.
653
654 In SQL, parameters such as $var are filled in using the value of variable
655 "var". Result values are stored in variables with the column name prior
656 to each invocation of CODE. The names of the variables in which results
657 are stored can be controlled using "AS name" clauses in the SQL. As
658 the database will often contain content that originates from untrusted
659 users, all result values are marked as [tainted](#taint).
660
661 **Beware of SQL injections in the `query` command!**
662 The SQL argument to the query command should always be literal SQL
663 text enclosed in {...}. The SQL argument should never be a double-quoted
664 string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
681 ~~~
682
683 In this second example, TH1 does the expansion of `$mykey` prior to passing
684 the text down into SQLite. So if `$mykey` contains a single-quote character,
685 followed by additional hostile text, that will result in an SQL injection.
686
687 To help guard against SQL-injections, recent versions of Fossil require
688 that the SQL argument be [untainted](#taint) or else the "query" command
689 will fail.
690
691 <a id="randhex"></a>TH1 randhex Command
692 -----------------------------------------
693
694 * randhex N
@@ -783,10 +819,24 @@
819
820 * submenu link LABEL URL
821
822 Add hyperlink to the submenu of the current page.
823
824 <a id="taintCmd"></a>TH1 taint Command
825 -----------------------------------------
826
827 * taint STRING
828
829 This command returns a copy of STRING that has been marked as
830 [tainted](#taint). Tainted strings are strings which might be
831 controlled by an attacker and might contain hostile inputs and
832 are thus unsafe to use in certain contexts. For example, tainted
833 strings should not be output as part of a webpage as they might
834 contain rogue HTML or Javascript that could lead to an XSS
835 vulnerability. Similarly, tainted strings should not be run as
836 SQL since they might contain an SQL-injection vulerability.
837
838 <a id="tclEval"></a>TH1 tclEval Command
839 -----------------------------------------
840
841 **This command requires the Tcl integration feature.**
842
@@ -854,10 +904,22 @@
904
905 * trace STRING
906
907 Generates a TH1 trace message if TH1 tracing is enabled.
908
909 <a id="untaintCmd"></a>TH1 taint Command
910 -----------------------------------------
911
912 * untaint STRING
913
914 This command returns a copy of STRING that has been marked as
915 [untainted](#taint). Untainted strings are strings which are
916 believed to be free of potentially hostile content. Use this
917 command with caution, as it overwrites the tainted-string protection
918 mechanisms that are built into TH1. If you do not understand all
919 the implications of executing this command, then do not use it.
920
921 <a id="unversioned_content"></a>TH1 unversioned content Command
922 -----------------------------------------------------------------
923
924 * unversioned content FILENAME
925
926
+87 -24
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
6868
are removed from each token by the command parser.) The third token
6969
is the `puts "hello"`, with its whitespace and newlines. The fourth token
7070
is `else` and the fifth and last token is `puts "world"`.
7171
7272
The `if` command evaluates its first argument (the second token)
73
-as an expression, and if that expression is true, evaluates its
73
+as an expression, and if that expression is true, it evaluates its
7474
second argument (the third token) as a TH1 script.
7575
If the expression is false and the third argument is `else`, then
7676
the fourth argument is evaluated as a TH1 expression.
7777
7878
So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106106
$repository "" info trunk]]] end]
107107
108108
Those backslashes allow the command to wrap nicely within a standard
109109
terminal width while telling the interpreter to consider those three
110110
lines as a single command.
111
+
112
+<a id="taint"></a>Tainted And Untainted Strings
113
+-----------------------------------------------
114
+
115
+Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
116
+"tainted" and "untainted" strings. Tainted strings are strings that are
117
+derived from user inputs that might contain text that is designed to subvert
118
+the script. Untainted strings are known to come from secure sources and
119
+are assumed to contain no malicious content.
120
+
121
+Beginning with Fossil version 2.26, and depending on the value of the
122
+[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
123
+strings from being used in ways that might lead to XSS or SQL-injection
124
+attacks. This feature helps to ensure that XSS and SQL-injection
125
+vulnerabilities are not *accidentally* added to Fossil when
126
+custom TH1 scripts for headers or footers or tickets are added to a
127
+repository. Note that the tainted/untainted distinction in strings does
128
+not make it impossible to introduce XSS and SQL-injections vulnerabilities
129
+using poorly-written TH1 scripts; it just makes it more difficult and
130
+less likely to happen by accident. Developers must still consider the
131
+security implications TH1 customizations they add to Fossil, and take
132
+appropriate precautions when writing custom TH1. Peer review of TH1
133
+script changes is encouraged.
134
+
135
+In Fossil version 2.26, if the vuln-report setting is set to "block"
136
+or "fatal", the [html](#html) and [query](#query) TH1 commands will
137
+fail with an error if their argument is a tainted string. This helps
138
+to prevent XSS and SQL-injection attacks, respectively. Note that
139
+the default value of the vuln-report setting is "log", which allows those
140
+commands to continue working and only writes a warning message into the
141
+error log. <b>Future versions of Fossil may change the default value
142
+of the vuln-report setting to "block" or "fatal".</b> Fossil users
143
+with customized TH1 scripts are encouraged to audit their customizations
144
+and fix any potential vulnerabilities soon, so as to avoid breakage
145
+caused by future upgrades. <b>Future versions of Fossil might also
146
+place additional restrictions on the use of tainted strings.</b>
147
+For example, it is likely that future versions of Fossil will disallow
148
+using tainted strings as script, for example as the body of a "for"
149
+loop or of a "proc".
111150
112151
113152
Summary of Core TH1 Commands
114153
----------------------------
115154
@@ -147,10 +186,13 @@
147186
* string last NEEDLE HAYSTACK ?START-INDEX?
148187
* string match PATTERN STRING
149188
* string length STRING
150189
* string range STRING FIRST LAST
151190
* string repeat STRING COUNT
191
+ * string trim STRING
192
+ * string trimleft STRING
193
+ * string trimright STRING
152194
* unset VARNAME
153195
* uplevel ?LEVEL? SCRIPT
154196
* upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155197
156198
All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
182224
* [copybtn](#copybtn)
183225
* [date](#date)
184226
* [decorate](#decorate)
185227
* [defHeader](#defHeader)
186228
* [dir](#dir)
187
- * [enable\_htmlify](#enable_htmlify)
188229
* [enable\_output](#enable_output)
189230
* [encode64](#encode64)
190231
* [getParameter](#getParameter)
191232
* [glob\_match](#glob_match)
192233
* [globalState](#globalState)
@@ -214,17 +255,19 @@
214255
* [stime](#stime)
215256
* [styleHeader](#styleHeader)
216257
* [styleFooter](#styleFooter)
217258
* [styleScript](#styleScript)
218259
* [submenu](#submenu)
260
+ * [taint](#taintCmd)
219261
* [tclEval](#tclEval)
220262
* [tclExpr](#tclExpr)
221263
* [tclInvoke](#tclInvoke)
222264
* [tclIsSafe](#tclIsSafe)
223265
* [tclMakeSafe](#tclMakeSafe)
224266
* [tclReady](#tclReady)
225267
* [trace](#trace)
268
+ * [untaint](#untaintCmd)
226269
* [unversioned content](#unversioned_content)
227270
* [unversioned list](#unversioned_list)
228271
* [utime](#utime)
229272
* [verifyCsrf](#verifyCsrf)
230273
* [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
413456
the files matching the pattern GLOB within CHECKIN will be returned.
414457
If DETAILS is non-zero, the result will be a list-of-lists, with each
415458
element containing at least three elements: the file name, the file
416459
size (in bytes), and the file last modification time (relative to the
417460
time zone configured for the repository).
418
-
419
-<a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
-
421
- * enable\_htmlify
422
- * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
-
424
-By default, certain output from `puts` and similar commands is escaped
425
-for HTML. The first call form returns the current state of that
426
-feature: `1` for on and `0` for off. The second call form enables
427
-(non-0) or disables (0) that feature and returns the *pre-call* state
428
-of that feature (so that a second call can pass that value to restore
429
-it to its previous state). The optional `TRACE-LABEL` argument causes
430
-the TH1 tracing output (if enabled) to add a marker when the second
431
-form of this command is invoked, and includes that label and the
432
-boolean argument's value in the trace. If tracing is disabled, that
433
-argument has no effect.
434
-
435461
436462
<a id="enable_output"></a>TH1 enable\_output Command
437463
------------------------------------------------------
438464
439465
* enable\_output BOOLEAN
@@ -528,22 +553,24 @@
528553
-----------------------------------
529554
530555
* html STRING
531556
532557
Outputs the STRING literally. It is assumed that STRING contains
533
-valid HTML, or that if it text then any characters that are
558
+valid HTML, or that if STRING contains any characters that are
534559
significant to HTML (such as `<`, `>`, `'`, or `&`) have already
535
-been escaped, perhaps by the htmlize command. Use the
560
+been escaped, perhaps by the [htmlize](#htmlize) command. Use the
536561
[puts](#puts) command to output text that might contain unescaped
537562
HTML markup.
538563
539564
**Beware of XSS attacks!** If the STRING value to the html command
540565
can be controlled by a hostile user, then he might be able to sneak
541566
in malicious HTML or Javascript which could result in a
542567
cross-site scripting (XSS) attack. Be careful that all text that
543568
in STRING that might come from user input has been sanitized by the
544
-[htmlize](#htmlize) command or similar.
569
+[htmlize](#htmlize) command or similar. In recent versions of Fossil,
570
+the STRING value must be [untainted](#taint) or else the "html" command
571
+will fail.
545572
546573
<a id="htmlize"></a>TH1 htmlize Command
547574
-----------------------------------------
548575
549576
* htmlize STRING
@@ -610,11 +637,13 @@
610637
* puts STRING
611638
612639
Outputs STRING. Characters within STRING that have special meaning
613640
in HTML are escaped prior to being output. Thus is it safe for STRING
614641
to be derived from user inputs. See also the [html](#html) command
615
-which behaves similarly except does not escape HTML markup.
642
+which behaves similarly except does not escape HTML markup. This
643
+command ("puts") is safe to use on [tainted strings](#taint), but the "html"
644
+command is not.
616645
617646
<a id="query"></a>TH1 query Command
618647
-------------------------------------
619648
620649
* query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
622651
Runs the SQL query given by the SQL argument. For each row in the result
623652
set, run CODE.
624653
625654
In SQL, parameters such as $var are filled in using the value of variable
626655
"var". Result values are stored in variables with the column name prior
627
-to each invocation of CODE.
656
+to each invocation of CODE. The names of the variables in which results
657
+are stored can be controlled using "AS name" clauses in the SQL. As
658
+the database will often contain content that originates from untrusted
659
+users, all result values are marked as [tainted](#taint).
628660
629661
**Beware of SQL injections in the `query` command!**
630662
The SQL argument to the query command should always be literal SQL
631663
text enclosed in {...}. The SQL argument should never be a double-quoted
632664
string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
649681
~~~
650682
651683
In this second example, TH1 does the expansion of `$mykey` prior to passing
652684
the text down into SQLite. So if `$mykey` contains a single-quote character,
653685
followed by additional hostile text, that will result in an SQL injection.
686
+
687
+To help guard against SQL-injections, recent versions of Fossil require
688
+that the SQL argument be [untainted](#taint) or else the "query" command
689
+will fail.
654690
655691
<a id="randhex"></a>TH1 randhex Command
656692
-----------------------------------------
657693
658694
* randhex N
@@ -783,10 +819,24 @@
783819
784820
* submenu link LABEL URL
785821
786822
Add hyperlink to the submenu of the current page.
787823
824
+<a id="taintCmd"></a>TH1 taint Command
825
+-----------------------------------------
826
+
827
+ * taint STRING
828
+
829
+This command returns a copy of STRING that has been marked as
830
+[tainted](#taint). Tainted strings are strings which might be
831
+controlled by an attacker and might contain hostile inputs and
832
+are thus unsafe to use in certain contexts. For example, tainted
833
+strings should not be output as part of a webpage as they might
834
+contain rogue HTML or Javascript that could lead to an XSS
835
+vulnerability. Similarly, tainted strings should not be run as
836
+SQL since they might contain an SQL-injection vulerability.
837
+
788838
<a id="tclEval"></a>TH1 tclEval Command
789839
-----------------------------------------
790840
791841
**This command requires the Tcl integration feature.**
792842
@@ -854,10 +904,22 @@
854904
855905
* trace STRING
856906
857907
Generates a TH1 trace message if TH1 tracing is enabled.
858908
909
+<a id="untaintCmd"></a>TH1 taint Command
910
+-----------------------------------------
911
+
912
+ * untaint STRING
913
+
914
+This command returns a copy of STRING that has been marked as
915
+[untainted](#taint). Untainted strings are strings which are
916
+believed to be free of potentially hostile content. Use this
917
+command with caution, as it overwrites the tainted-string protection
918
+mechanisms that are built into TH1. If you do not understand all
919
+the implications of executing this command, then do not use it.
920
+
859921
<a id="unversioned_content"></a>TH1 unversioned content Command
860922
-----------------------------------------------------------------
861923
862924
* unversioned content FILENAME
863925
864926
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
68 are removed from each token by the command parser.) The third token
69 is the `puts "hello"`, with its whitespace and newlines. The fourth token
70 is `else` and the fifth and last token is `puts "world"`.
71
72 The `if` command evaluates its first argument (the second token)
73 as an expression, and if that expression is true, evaluates its
74 second argument (the third token) as a TH1 script.
75 If the expression is false and the third argument is `else`, then
76 the fourth argument is evaluated as a TH1 expression.
77
78 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106 $repository "" info trunk]]] end]
107
108 Those backslashes allow the command to wrap nicely within a standard
109 terminal width while telling the interpreter to consider those three
110 lines as a single command.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
112
113 Summary of Core TH1 Commands
114 ----------------------------
115
@@ -147,10 +186,13 @@
147 * string last NEEDLE HAYSTACK ?START-INDEX?
148 * string match PATTERN STRING
149 * string length STRING
150 * string range STRING FIRST LAST
151 * string repeat STRING COUNT
 
 
 
152 * unset VARNAME
153 * uplevel ?LEVEL? SCRIPT
154 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155
156 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
182 * [copybtn](#copybtn)
183 * [date](#date)
184 * [decorate](#decorate)
185 * [defHeader](#defHeader)
186 * [dir](#dir)
187 * [enable\_htmlify](#enable_htmlify)
188 * [enable\_output](#enable_output)
189 * [encode64](#encode64)
190 * [getParameter](#getParameter)
191 * [glob\_match](#glob_match)
192 * [globalState](#globalState)
@@ -214,17 +255,19 @@
214 * [stime](#stime)
215 * [styleHeader](#styleHeader)
216 * [styleFooter](#styleFooter)
217 * [styleScript](#styleScript)
218 * [submenu](#submenu)
 
219 * [tclEval](#tclEval)
220 * [tclExpr](#tclExpr)
221 * [tclInvoke](#tclInvoke)
222 * [tclIsSafe](#tclIsSafe)
223 * [tclMakeSafe](#tclMakeSafe)
224 * [tclReady](#tclReady)
225 * [trace](#trace)
 
226 * [unversioned content](#unversioned_content)
227 * [unversioned list](#unversioned_list)
228 * [utime](#utime)
229 * [verifyCsrf](#verifyCsrf)
230 * [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
413 the files matching the pattern GLOB within CHECKIN will be returned.
414 If DETAILS is non-zero, the result will be a list-of-lists, with each
415 element containing at least three elements: the file name, the file
416 size (in bytes), and the file last modification time (relative to the
417 time zone configured for the repository).
418
419 <a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
421 * enable\_htmlify
422 * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
424 By default, certain output from `puts` and similar commands is escaped
425 for HTML. The first call form returns the current state of that
426 feature: `1` for on and `0` for off. The second call form enables
427 (non-0) or disables (0) that feature and returns the *pre-call* state
428 of that feature (so that a second call can pass that value to restore
429 it to its previous state). The optional `TRACE-LABEL` argument causes
430 the TH1 tracing output (if enabled) to add a marker when the second
431 form of this command is invoked, and includes that label and the
432 boolean argument's value in the trace. If tracing is disabled, that
433 argument has no effect.
434
435
436 <a id="enable_output"></a>TH1 enable\_output Command
437 ------------------------------------------------------
438
439 * enable\_output BOOLEAN
@@ -528,22 +553,24 @@
528 -----------------------------------
529
530 * html STRING
531
532 Outputs the STRING literally. It is assumed that STRING contains
533 valid HTML, or that if it text then any characters that are
534 significant to HTML (such as `<`, `>`, `'`, or `&`) have already
535 been escaped, perhaps by the htmlize command. Use the
536 [puts](#puts) command to output text that might contain unescaped
537 HTML markup.
538
539 **Beware of XSS attacks!** If the STRING value to the html command
540 can be controlled by a hostile user, then he might be able to sneak
541 in malicious HTML or Javascript which could result in a
542 cross-site scripting (XSS) attack. Be careful that all text that
543 in STRING that might come from user input has been sanitized by the
544 [htmlize](#htmlize) command or similar.
 
 
545
546 <a id="htmlize"></a>TH1 htmlize Command
547 -----------------------------------------
548
549 * htmlize STRING
@@ -610,11 +637,13 @@
610 * puts STRING
611
612 Outputs STRING. Characters within STRING that have special meaning
613 in HTML are escaped prior to being output. Thus is it safe for STRING
614 to be derived from user inputs. See also the [html](#html) command
615 which behaves similarly except does not escape HTML markup.
 
 
616
617 <a id="query"></a>TH1 query Command
618 -------------------------------------
619
620 * query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
622 Runs the SQL query given by the SQL argument. For each row in the result
623 set, run CODE.
624
625 In SQL, parameters such as $var are filled in using the value of variable
626 "var". Result values are stored in variables with the column name prior
627 to each invocation of CODE.
 
 
 
628
629 **Beware of SQL injections in the `query` command!**
630 The SQL argument to the query command should always be literal SQL
631 text enclosed in {...}. The SQL argument should never be a double-quoted
632 string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
649 ~~~
650
651 In this second example, TH1 does the expansion of `$mykey` prior to passing
652 the text down into SQLite. So if `$mykey` contains a single-quote character,
653 followed by additional hostile text, that will result in an SQL injection.
 
 
 
 
654
655 <a id="randhex"></a>TH1 randhex Command
656 -----------------------------------------
657
658 * randhex N
@@ -783,10 +819,24 @@
783
784 * submenu link LABEL URL
785
786 Add hyperlink to the submenu of the current page.
787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788 <a id="tclEval"></a>TH1 tclEval Command
789 -----------------------------------------
790
791 **This command requires the Tcl integration feature.**
792
@@ -854,10 +904,22 @@
854
855 * trace STRING
856
857 Generates a TH1 trace message if TH1 tracing is enabled.
858
 
 
 
 
 
 
 
 
 
 
 
 
859 <a id="unversioned_content"></a>TH1 unversioned content Command
860 -----------------------------------------------------------------
861
862 * unversioned content FILENAME
863
864
--- www/th1.md
+++ www/th1.md
@@ -68,11 +68,11 @@
68 are removed from each token by the command parser.) The third token
69 is the `puts "hello"`, with its whitespace and newlines. The fourth token
70 is `else` and the fifth and last token is `puts "world"`.
71
72 The `if` command evaluates its first argument (the second token)
73 as an expression, and if that expression is true, it evaluates its
74 second argument (the third token) as a TH1 script.
75 If the expression is false and the third argument is `else`, then
76 the fourth argument is evaluated as a TH1 expression.
77
78 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +106,49 @@
106 $repository "" info trunk]]] end]
107
108 Those backslashes allow the command to wrap nicely within a standard
109 terminal width while telling the interpreter to consider those three
110 lines as a single command.
111
112 <a id="taint"></a>Tainted And Untainted Strings
113 -----------------------------------------------
114
115 Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
116 "tainted" and "untainted" strings. Tainted strings are strings that are
117 derived from user inputs that might contain text that is designed to subvert
118 the script. Untainted strings are known to come from secure sources and
119 are assumed to contain no malicious content.
120
121 Beginning with Fossil version 2.26, and depending on the value of the
122 [vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
123 strings from being used in ways that might lead to XSS or SQL-injection
124 attacks. This feature helps to ensure that XSS and SQL-injection
125 vulnerabilities are not *accidentally* added to Fossil when
126 custom TH1 scripts for headers or footers or tickets are added to a
127 repository. Note that the tainted/untainted distinction in strings does
128 not make it impossible to introduce XSS and SQL-injections vulnerabilities
129 using poorly-written TH1 scripts; it just makes it more difficult and
130 less likely to happen by accident. Developers must still consider the
131 security implications TH1 customizations they add to Fossil, and take
132 appropriate precautions when writing custom TH1. Peer review of TH1
133 script changes is encouraged.
134
135 In Fossil version 2.26, if the vuln-report setting is set to "block"
136 or "fatal", the [html](#html) and [query](#query) TH1 commands will
137 fail with an error if their argument is a tainted string. This helps
138 to prevent XSS and SQL-injection attacks, respectively. Note that
139 the default value of the vuln-report setting is "log", which allows those
140 commands to continue working and only writes a warning message into the
141 error log. <b>Future versions of Fossil may change the default value
142 of the vuln-report setting to "block" or "fatal".</b> Fossil users
143 with customized TH1 scripts are encouraged to audit their customizations
144 and fix any potential vulnerabilities soon, so as to avoid breakage
145 caused by future upgrades. <b>Future versions of Fossil might also
146 place additional restrictions on the use of tainted strings.</b>
147 For example, it is likely that future versions of Fossil will disallow
148 using tainted strings as script, for example as the body of a "for"
149 loop or of a "proc".
150
151
152 Summary of Core TH1 Commands
153 ----------------------------
154
@@ -147,10 +186,13 @@
186 * string last NEEDLE HAYSTACK ?START-INDEX?
187 * string match PATTERN STRING
188 * string length STRING
189 * string range STRING FIRST LAST
190 * string repeat STRING COUNT
191 * string trim STRING
192 * string trimleft STRING
193 * string trimright STRING
194 * unset VARNAME
195 * uplevel ?LEVEL? SCRIPT
196 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
197
198 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +224,10 @@
224 * [copybtn](#copybtn)
225 * [date](#date)
226 * [decorate](#decorate)
227 * [defHeader](#defHeader)
228 * [dir](#dir)
 
229 * [enable\_output](#enable_output)
230 * [encode64](#encode64)
231 * [getParameter](#getParameter)
232 * [glob\_match](#glob_match)
233 * [globalState](#globalState)
@@ -214,17 +255,19 @@
255 * [stime](#stime)
256 * [styleHeader](#styleHeader)
257 * [styleFooter](#styleFooter)
258 * [styleScript](#styleScript)
259 * [submenu](#submenu)
260 * [taint](#taintCmd)
261 * [tclEval](#tclEval)
262 * [tclExpr](#tclExpr)
263 * [tclInvoke](#tclInvoke)
264 * [tclIsSafe](#tclIsSafe)
265 * [tclMakeSafe](#tclMakeSafe)
266 * [tclReady](#tclReady)
267 * [trace](#trace)
268 * [untaint](#untaintCmd)
269 * [unversioned content](#unversioned_content)
270 * [unversioned list](#unversioned_list)
271 * [utime](#utime)
272 * [verifyCsrf](#verifyCsrf)
273 * [verifyLogin](#verifyLogin)
@@ -413,28 +456,10 @@
456 the files matching the pattern GLOB within CHECKIN will be returned.
457 If DETAILS is non-zero, the result will be a list-of-lists, with each
458 element containing at least three elements: the file name, the file
459 size (in bytes), and the file last modification time (relative to the
460 time zone configured for the repository).
 
 
-------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
462 <a id="enable_output"></a>TH1 enable\_output Command
463 ------------------------------------------------------
464
465 * enable\_output BOOLEAN
@@ -528,22 +553,24 @@
553 -----------------------------------
554
555 * html STRING
556
557 Outputs the STRING literally. It is assumed that STRING contains
558 valid HTML, or that if STRING contains any characters that are
559 significant to HTML (such as `<`, `>`, `'`, or `&`) have already
560 been escaped, perhaps by the [htmlize](#htmlize) command. Use the
561 [puts](#puts) command to output text that might contain unescaped
562 HTML markup.
563
564 **Beware of XSS attacks!** If the STRING value to the html command
565 can be controlled by a hostile user, then he might be able to sneak
566 in malicious HTML or Javascript which could result in a
567 cross-site scripting (XSS) attack. Be careful that all text that
568 in STRING that might come from user input has been sanitized by the
569 [htmlize](#htmlize) command or similar. In recent versions of Fossil,
570 the STRING value must be [untainted](#taint) or else the "html" command
571 will fail.
572
573 <a id="htmlize"></a>TH1 htmlize Command
574 -----------------------------------------
575
576 * htmlize STRING
@@ -610,11 +637,13 @@
637 * puts STRING
638
639 Outputs STRING. Characters within STRING that have special meaning
640 in HTML are escaped prior to being output. Thus is it safe for STRING
641 to be derived from user inputs. See also the [html](#html) command
642 which behaves similarly except does not escape HTML markup. This
643 command ("puts") is safe to use on [tainted strings](#taint), but the "html"
644 command is not.
645
646 <a id="query"></a>TH1 query Command
647 -------------------------------------
648
649 * query ?-nocomplain? SQL CODE
@@ -622,11 +651,14 @@
651 Runs the SQL query given by the SQL argument. For each row in the result
652 set, run CODE.
653
654 In SQL, parameters such as $var are filled in using the value of variable
655 "var". Result values are stored in variables with the column name prior
656 to each invocation of CODE. The names of the variables in which results
657 are stored can be controlled using "AS name" clauses in the SQL. As
658 the database will often contain content that originates from untrusted
659 users, all result values are marked as [tainted](#taint).
660
661 **Beware of SQL injections in the `query` command!**
662 The SQL argument to the query command should always be literal SQL
663 text enclosed in {...}. The SQL argument should never be a double-quoted
664 string or the value of a \$variable, as those constructs can lead to
@@ -649,10 +681,14 @@
681 ~~~
682
683 In this second example, TH1 does the expansion of `$mykey` prior to passing
684 the text down into SQLite. So if `$mykey` contains a single-quote character,
685 followed by additional hostile text, that will result in an SQL injection.
686
687 To help guard against SQL-injections, recent versions of Fossil require
688 that the SQL argument be [untainted](#taint) or else the "query" command
689 will fail.
690
691 <a id="randhex"></a>TH1 randhex Command
692 -----------------------------------------
693
694 * randhex N
@@ -783,10 +819,24 @@
819
820 * submenu link LABEL URL
821
822 Add hyperlink to the submenu of the current page.
823
824 <a id="taintCmd"></a>TH1 taint Command
825 -----------------------------------------
826
827 * taint STRING
828
829 This command returns a copy of STRING that has been marked as
830 [tainted](#taint). Tainted strings are strings which might be
831 controlled by an attacker and might contain hostile inputs and
832 are thus unsafe to use in certain contexts. For example, tainted
833 strings should not be output as part of a webpage as they might
834 contain rogue HTML or Javascript that could lead to an XSS
835 vulnerability. Similarly, tainted strings should not be run as
836 SQL since they might contain an SQL-injection vulerability.
837
838 <a id="tclEval"></a>TH1 tclEval Command
839 -----------------------------------------
840
841 **This command requires the Tcl integration feature.**
842
@@ -854,10 +904,22 @@
904
905 * trace STRING
906
907 Generates a TH1 trace message if TH1 tracing is enabled.
908
909 <a id="untaintCmd"></a>TH1 taint Command
910 -----------------------------------------
911
912 * untaint STRING
913
914 This command returns a copy of STRING that has been marked as
915 [untainted](#taint). Untainted strings are strings which are
916 believed to be free of potentially hostile content. Use this
917 command with caution, as it overwrites the tainted-string protection
918 mechanisms that are built into TH1. If you do not understand all
919 the implications of executing this command, then do not use it.
920
921 <a id="unversioned_content"></a>TH1 unversioned content Command
922 -----------------------------------------------------------------
923
924 * unversioned content FILENAME
925
926

Keyboard Shortcuts

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