Fossil SCM
Sync with trunk.
Commit
855076ce79275ccf2e206b5359371a900734dff9228aae0262547f5f99e90b53
Parent
6cb7a7e28dfd13a…
44 files changed
+1
-1
+1
-1
+1
-1
+1
-1
+31
-17
+2
-2
+252
-187
+1
-1
+1
+1
-1
+3
-3
+1
-1
+29
-9
+29
-9
+29
-9
+1
-1
+1
-1
+89
-182
+89
-182
+1
-1
+1
-22
+38
-12
+3
-2
+87
-54
+53
-14
+92
-53
+209
-89
+209
-89
+27
-89
+1
-1
+14
-10
+15
-15
+79
-41
+20
-8
+7
+84
+36
-36
+36
-36
+2
-3
+37
-21
+37
-21
+75
-65
+87
-24
+87
-24
~
skins/default/header.txt
~
skins/eagle/header.txt
~
skins/original/header.txt
~
skins/xekri/header.txt
~
src/alerts.c
~
src/browse.c
~
src/cgi.c
~
src/db.c
~
src/default.css
~
src/doc.c
~
src/info.c
~
src/login.c
~
src/main.c
~
src/main.c
~
src/main.c
~
src/markdown_html.c
~
src/markdown_html.c
~
src/pikchrshow.c
~
src/pikchrshow.c
~
src/printf.c
~
src/repolist.c
~
src/security_audit.c
~
src/style.c
~
src/th.c
~
src/th.h
~
src/th_lang.c
~
src/th_main.c
~
src/th_main.c
~
src/th_tcl.c
~
src/timeline.c
~
src/tkt.c
~
src/tktsetup.c
~
src/user.c
~
src/xfer.c
~
test/tester.tcl
~
test/th1-taint.test
~
test/th1.test
~
test/th1.test
~
www/cgi.wiki
~
www/changes.wiki
~
www/changes.wiki
~
www/quickstart.wiki
~
www/th1.md
~
www/th1.md
+1
-1
| --- skins/default/header.txt | ||
| +++ skins/default/header.txt | ||
| @@ -28,11 +28,11 @@ | ||
| 28 | 28 | return $logourl |
| 29 | 29 | } |
| 30 | 30 | set logourl [getLogoUrl $baseurl] |
| 31 | 31 | </th1> |
| 32 | 32 | <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>"> | |
| 34 | 34 | </a> |
| 35 | 35 | </div> |
| 36 | 36 | <div class="title"> |
| 37 | 37 | <h1>$<project_name></h1> |
| 38 | 38 | <span class="page-title">$<title></span> |
| 39 | 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/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 |
+1
-1
| --- skins/eagle/header.txt | ||
| +++ skins/eagle/header.txt | ||
| @@ -65,11 +65,11 @@ | ||
| 65 | 65 | # Link logo to the top of the current repo |
| 66 | 66 | set logourl $baseurl |
| 67 | 67 | } |
| 68 | 68 | </th1> |
| 69 | 69 | <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>"> | |
| 71 | 71 | </a> |
| 72 | 72 | </div> |
| 73 | 73 | <div class="title">$<title></div> |
| 74 | 74 | <div class="status"><nobr><th1> |
| 75 | 75 | if {[info exists login]} { |
| 76 | 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/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 |
+1
-1
| --- skins/original/header.txt | ||
| +++ skins/original/header.txt | ||
| @@ -59,11 +59,11 @@ | ||
| 59 | 59 | return $logourl |
| 60 | 60 | } |
| 61 | 61 | set logourl [getLogoUrl $baseurl] |
| 62 | 62 | </th1> |
| 63 | 63 | <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>"> | |
| 65 | 65 | </a> |
| 66 | 66 | </div> |
| 67 | 67 | <div class="title">$<title></div> |
| 68 | 68 | <div class="status"><nobr><th1> |
| 69 | 69 | if {[info exists login]} { |
| 70 | 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/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 |
+1
-1
| --- skins/xekri/header.txt | ||
| +++ skins/xekri/header.txt | ||
| @@ -65,11 +65,11 @@ | ||
| 65 | 65 | # Link logo to the top of the current repo |
| 66 | 66 | set logourl $baseurl |
| 67 | 67 | } |
| 68 | 68 | </th1> |
| 69 | 69 | <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>"> | |
| 71 | 71 | </a> |
| 72 | 72 | </div> |
| 73 | 73 | <div class="title">$<title></div> |
| 74 | 74 | <div class="status"><nobr> |
| 75 | 75 | <th1> |
| 76 | 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 |
| --- 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 @@ | ||
| 984 | 984 | blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); |
| 985 | 985 | }else{ |
| 986 | 986 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 987 | 987 | } |
| 988 | 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 | 989 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 993 | 990 | /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is |
| 994 | 991 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 995 | 992 | ** and $(from) is the domain part of the email-self setting. */ |
| 996 | 993 | sqlite3_randomness(sizeof(r1), &r1); |
| @@ -3215,18 +3212,21 @@ | ||
| 3215 | 3212 | Blob fhdr, fbody; |
| 3216 | 3213 | blob_init(&fhdr, 0, 0); |
| 3217 | 3214 | blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); |
| 3218 | 3215 | blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
| 3219 | 3216 | 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 | + } | |
| 3228 | 3228 | alert_send(pSender,&fhdr,&fbody,p->zFromName); |
| 3229 | 3229 | nSent++; |
| 3230 | 3230 | blob_reset(&fhdr); |
| 3231 | 3231 | blob_reset(&fbody); |
| 3232 | 3232 | }else{ |
| @@ -3245,15 +3245,19 @@ | ||
| 3245 | 3245 | blob_append(&body, "\n", 1); |
| 3246 | 3246 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3247 | 3247 | } |
| 3248 | 3248 | } |
| 3249 | 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); | |
| 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 | + } | |
| 3255 | 3259 | alert_send(pSender,&hdr,&body,0); |
| 3256 | 3260 | nSent++; |
| 3257 | 3261 | blob_truncate(&hdr, 0); |
| 3258 | 3262 | blob_truncate(&body, 0); |
| 3259 | 3263 | } |
| @@ -3295,18 +3299,28 @@ | ||
| 3295 | 3299 | " AND length(sdigest)>0", |
| 3296 | 3300 | iNewWarn, iOldWarn |
| 3297 | 3301 | ); |
| 3298 | 3302 | while( db_step(&q)==SQLITE_ROW ){ |
| 3299 | 3303 | Blob hdr, body; |
| 3304 | + const char *zCode = db_column_text(&q,0); | |
| 3300 | 3305 | blob_init(&hdr, 0, 0); |
| 3301 | 3306 | blob_init(&body, 0, 0); |
| 3302 | 3307 | alert_renewal_msg(&hdr, &body, |
| 3303 | - db_column_text(&q,0), | |
| 3308 | + zCode, | |
| 3304 | 3309 | db_column_int(&q,1), |
| 3305 | 3310 | db_column_text(&q,2), |
| 3306 | 3311 | db_column_text(&q,3), |
| 3307 | 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 | + } | |
| 3308 | 3322 | alert_send(pSender,&hdr,&body,0); |
| 3309 | 3323 | blob_reset(&hdr); |
| 3310 | 3324 | blob_reset(&body); |
| 3311 | 3325 | } |
| 3312 | 3326 | db_finalize(&q); |
| 3313 | 3327 |
| --- 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 @@ | ||
| 205 | 205 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 206 | 206 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | 207 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | 208 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | 209 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | - Th_Store("current_checkin", zCI); | |
| 210 | + Th_StoreUnsafe("current_checkin", zCI); | |
| 211 | 211 | }else{ |
| 212 | 212 | zCI = 0; |
| 213 | 213 | } |
| 214 | 214 | } |
| 215 | 215 | |
| @@ -771,11 +771,11 @@ | ||
| 771 | 771 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | 772 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | 773 | " FROM event WHERE objid=%d", rid); |
| 774 | 774 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 775 | 775 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 776 | - Th_Store("current_checkin", zCI); | |
| 776 | + Th_StoreUnsafe("current_checkin", zCI); | |
| 777 | 777 | }else{ |
| 778 | 778 | zCI = 0; |
| 779 | 779 | } |
| 780 | 780 | } |
| 781 | 781 | if( zCI==0 ){ |
| 782 | 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_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 @@ | ||
| 2497 | 2497 | } |
| 2498 | 2498 | fossil_free(zToFree); |
| 2499 | 2499 | fgetc(g.httpIn); /* Read past the "," separating header from content */ |
| 2500 | 2500 | cgi_init(); |
| 2501 | 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 | 2502 | |
| 2522 | 2503 | #if INTERFACE |
| 2523 | 2504 | /* |
| 2524 | 2505 | ** Bitmap values for the flags parameter to cgi_http_server(). |
| 2525 | 2506 | */ |
| @@ -2559,150 +2540,222 @@ | ||
| 2559 | 2540 | ){ |
| 2560 | 2541 | #if defined(_WIN32) |
| 2561 | 2542 | /* Use win32_http_server() instead */ |
| 2562 | 2543 | fossil_exit(1); |
| 2563 | 2544 | #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 */ | |
| 2566 | 2549 | int nRequest = 0; /* Number of requests handled so far */ |
| 2567 | 2550 | fd_set readfds; /* Set of file descriptors for select() */ |
| 2568 | 2551 | socklen_t lenaddr; /* Length of the inaddr structure */ |
| 2569 | 2552 | int child; /* PID of the child process */ |
| 2570 | 2553 | int nchildren = 0; /* Number of child processes */ |
| 2571 | 2554 | 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 */ | |
| 2574 | 2557 | struct sockaddr_un uxaddr; /* The address for unix-domain sockets */ |
| 2575 | 2558 | int opt = 1; /* setsockopt flag */ |
| 2576 | 2559 | int rc; /* Result code from system calls */ |
| 2577 | 2560 | 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); | |
| 2704 | 2757 | if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){ |
| 2705 | 2758 | assert( strstr(zBrowser,"%d")!=0 ); |
| 2706 | 2759 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 2707 | 2760 | #if defined(__CYGWIN__) |
| 2708 | 2761 | /* On Cygwin, we can do better than "echo" */ |
| @@ -2716,57 +2769,69 @@ | ||
| 2716 | 2769 | #endif |
| 2717 | 2770 | if( fossil_system(zBrowser)<0 ){ |
| 2718 | 2771 | fossil_warning("cannot start browser: %s\n", zBrowser); |
| 2719 | 2772 | } |
| 2720 | 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 | + */ | |
| 2721 | 2779 | while( 1 ){ |
| 2722 | 2780 | #if FOSSIL_MAX_CONNECTIONS>0 |
| 2723 | 2781 | while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ |
| 2724 | 2782 | if( wait(0)>=0 ) nchildren--; |
| 2725 | 2783 | } |
| 2726 | 2784 | #endif |
| 2727 | 2785 | delay.tv_sec = 0; |
| 2728 | 2786 | delay.tv_usec = 100000; |
| 2729 | 2787 | 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; | |
| 2768 | 2833 | } |
| 2769 | 2834 | } |
| 2770 | 2835 | /* Bury dead children */ |
| 2771 | 2836 | if( nchildren ){ |
| 2772 | 2837 | while(1){ |
| 2773 | 2838 |
| --- 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 |
M
src/db.c
+1
-1
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -3369,11 +3369,11 @@ | ||
| 3369 | 3369 | if( zProjectName ) fossil_print("project-name: %s\n", zProjectName); |
| 3370 | 3370 | if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc); |
| 3371 | 3371 | fossil_print("project-id: %s\n", db_get("project-code", 0)); |
| 3372 | 3372 | fossil_print("server-id: %s\n", db_get("server-code", 0)); |
| 3373 | 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", | |
| 3374 | + fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n", | |
| 3375 | 3375 | g.zLogin, zPassword); |
| 3376 | 3376 | hash_user_password(g.zLogin); |
| 3377 | 3377 | } |
| 3378 | 3378 | |
| 3379 | 3379 | /* |
| 3380 | 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 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 |
+1
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -751,10 +751,11 @@ | ||
| 751 | 751 | border-left: 1px solid gold; |
| 752 | 752 | } |
| 753 | 753 | body.cpage-ckout .file-change-line, |
| 754 | 754 | body.cpage-info .file-change-line, |
| 755 | 755 | body.cpage-vinfo .file-change-line, |
| 756 | +body.cpage-ci .file-change-line, | |
| 756 | 757 | body.cpage-vdiff .file-change-line { |
| 757 | 758 | margin-top: 16px; |
| 758 | 759 | margin-bottom: 16px; |
| 759 | 760 | margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; |
| 760 | 761 | display: flex; |
| 761 | 762 |
| --- 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 @@ | ||
| 1052 | 1052 | */ |
| 1053 | 1053 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1054 | 1054 | if( zMime==0 ){ |
| 1055 | 1055 | zMime = mimetype_from_name(zName); |
| 1056 | 1056 | } |
| 1057 | - Th_Store("doc_name", zName); | |
| 1057 | + Th_StoreUnsafe("doc_name", zName); | |
| 1058 | 1058 | if( vid ){ |
| 1059 | 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1060 | 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1061 | 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1062 | 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1063 | 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_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 @@ | ||
| 951 | 951 | const char *zOrigDate; |
| 952 | 952 | int okWiki = 0; |
| 953 | 953 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 954 | 954 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 955 | 955 | |
| 956 | - Th_Store("current_checkin", zName); | |
| 956 | + Th_StoreUnsafe("current_checkin", zName); | |
| 957 | 957 | style_header("Check-in [%S]", zUuid); |
| 958 | 958 | login_anonymous_available(); |
| 959 | 959 | zEUser = db_text(0, |
| 960 | 960 | "SELECT value FROM tagxref" |
| 961 | 961 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| @@ -1182,14 +1182,14 @@ | ||
| 1182 | 1182 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 1183 | 1183 | @ Side-by-Side Diff</a> |
| 1184 | 1184 | } |
| 1185 | 1185 | if( diffType!=0 ){ |
| 1186 | 1186 | if( *zW ){ |
| 1187 | - @ %z(chref("button","%R/%s/%T",zPage,zName)) | |
| 1187 | + @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType)) | |
| 1188 | 1188 | @ Show Whitespace Changes</a> |
| 1189 | 1189 | }else{ |
| 1190 | - @ %z(chref("button","%R/%s/%T?w",zPage,zName)) | |
| 1190 | + @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType)) | |
| 1191 | 1191 | @ Ignore Whitespace</a> |
| 1192 | 1192 | } |
| 1193 | 1193 | } |
| 1194 | 1194 | if( zParent ){ |
| 1195 | 1195 | @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid)) |
| 1196 | 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_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 Diff</a> |
| 1184 | } |
| 1185 | if( diffType!=0 ){ |
| 1186 | if( *zW ){ |
| 1187 | @ %z(chref("button","%R/%s/%T",zPage,zName)) |
| 1188 | @ Show Whitespace Changes</a> |
| 1189 | }else{ |
| 1190 | @ %z(chref("button","%R/%s/%T?w",zPage,zName)) |
| 1191 | @ Ignore 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 Diff</a> |
| 1184 | } |
| 1185 | if( diffType!=0 ){ |
| 1186 | if( *zW ){ |
| 1187 | @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType)) |
| 1188 | @ Show Whitespace Changes</a> |
| 1189 | }else{ |
| 1190 | @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType)) |
| 1191 | @ Ignore 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 @@ | ||
| 1416 | 1416 | */ |
| 1417 | 1417 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 1418 | 1418 | if( ( cgi_is_loopback(zIpAddr) |
| 1419 | 1419 | || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
| 1420 | 1420 | && g.useLocalauth |
| 1421 | - && db_get_int("localauth",0)==0 | |
| 1421 | + && db_get_boolean("localauth",0)==0 | |
| 1422 | 1422 | && P("HTTPS")==0 |
| 1423 | 1423 | ){ |
| 1424 | 1424 | char *zSeed; |
| 1425 | 1425 | if( g.localOpen ) zLogin = db_lget("default-user",0); |
| 1426 | 1426 | if( zLogin!=0 ){ |
| 1427 | 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_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 @@ | ||
| 3764 | 3764 | ** case=3 Extra db_end_transaction() |
| 3765 | 3765 | ** case=4 Error during SQL processing |
| 3766 | 3766 | ** case=5 Call the segfault handler |
| 3767 | 3767 | ** case=6 Call webpage_assert() |
| 3768 | 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 | |
| 3769 | 3772 | */ |
| 3770 | 3773 | void test_warning_page(void){ |
| 3771 | 3774 | int iCase = atoi(PD("case","0")); |
| 3772 | 3775 | int i; |
| 3773 | 3776 | login_check_credentials(); |
| @@ -3776,17 +3779,15 @@ | ||
| 3776 | 3779 | return; |
| 3777 | 3780 | } |
| 3778 | 3781 | style_set_current_feature("test"); |
| 3779 | 3782 | style_header("Warning Test Page"); |
| 3780 | 3783 | 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++){ | |
| 3788 | 3789 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3789 | 3790 | } |
| 3790 | 3791 | @ </p> |
| 3791 | 3792 | @ <p><ol> |
| 3792 | 3793 | @ <li value='1'> Call fossil_warning() |
| @@ -3815,20 +3816,39 @@ | ||
| 3815 | 3816 | } |
| 3816 | 3817 | @ <li value='6'> call webpage_assert(0) |
| 3817 | 3818 | if( iCase==6 ){ |
| 3818 | 3819 | webpage_assert( 5==7 ); |
| 3819 | 3820 | } |
| 3820 | - @ <li value='7'> call webpage_error()" | |
| 3821 | + @ <li value='7'> call webpage_error() | |
| 3821 | 3822 | if( iCase==7 ){ |
| 3822 | 3823 | cgi_reset_content(); |
| 3823 | 3824 | webpage_error("Case 7 from /test-warning"); |
| 3824 | 3825 | } |
| 3825 | - @ <li value='8'> simulated timeout" | |
| 3826 | + @ <li value='8'> simulated timeout | |
| 3826 | 3827 | if( iCase==8 ){ |
| 3827 | 3828 | fossil_set_timeout(1); |
| 3828 | 3829 | cgi_reset_content(); |
| 3829 | 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 | + } | |
| 3830 | 3850 | } |
| 3831 | 3851 | @ </ol> |
| 3832 | 3852 | @ <p>End of test</p> |
| 3833 | 3853 | style_finish_page(); |
| 3834 | 3854 | } |
| 3835 | 3855 |
| --- 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 @@ | ||
| 3764 | 3764 | ** case=3 Extra db_end_transaction() |
| 3765 | 3765 | ** case=4 Error during SQL processing |
| 3766 | 3766 | ** case=5 Call the segfault handler |
| 3767 | 3767 | ** case=6 Call webpage_assert() |
| 3768 | 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 | |
| 3769 | 3772 | */ |
| 3770 | 3773 | void test_warning_page(void){ |
| 3771 | 3774 | int iCase = atoi(PD("case","0")); |
| 3772 | 3775 | int i; |
| 3773 | 3776 | login_check_credentials(); |
| @@ -3776,17 +3779,15 @@ | ||
| 3776 | 3779 | return; |
| 3777 | 3780 | } |
| 3778 | 3781 | style_set_current_feature("test"); |
| 3779 | 3782 | style_header("Warning Test Page"); |
| 3780 | 3783 | 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++){ | |
| 3788 | 3789 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3789 | 3790 | } |
| 3790 | 3791 | @ </p> |
| 3791 | 3792 | @ <p><ol> |
| 3792 | 3793 | @ <li value='1'> Call fossil_warning() |
| @@ -3815,20 +3816,39 @@ | ||
| 3815 | 3816 | } |
| 3816 | 3817 | @ <li value='6'> call webpage_assert(0) |
| 3817 | 3818 | if( iCase==6 ){ |
| 3818 | 3819 | webpage_assert( 5==7 ); |
| 3819 | 3820 | } |
| 3820 | - @ <li value='7'> call webpage_error()" | |
| 3821 | + @ <li value='7'> call webpage_error() | |
| 3821 | 3822 | if( iCase==7 ){ |
| 3822 | 3823 | cgi_reset_content(); |
| 3823 | 3824 | webpage_error("Case 7 from /test-warning"); |
| 3824 | 3825 | } |
| 3825 | - @ <li value='8'> simulated timeout" | |
| 3826 | + @ <li value='8'> simulated timeout | |
| 3826 | 3827 | if( iCase==8 ){ |
| 3827 | 3828 | fossil_set_timeout(1); |
| 3828 | 3829 | cgi_reset_content(); |
| 3829 | 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 | + } | |
| 3830 | 3850 | } |
| 3831 | 3851 | @ </ol> |
| 3832 | 3852 | @ <p>End of test</p> |
| 3833 | 3853 | style_finish_page(); |
| 3834 | 3854 | } |
| 3835 | 3855 |
| --- 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 @@ | ||
| 3764 | 3764 | ** case=3 Extra db_end_transaction() |
| 3765 | 3765 | ** case=4 Error during SQL processing |
| 3766 | 3766 | ** case=5 Call the segfault handler |
| 3767 | 3767 | ** case=6 Call webpage_assert() |
| 3768 | 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 | |
| 3769 | 3772 | */ |
| 3770 | 3773 | void test_warning_page(void){ |
| 3771 | 3774 | int iCase = atoi(PD("case","0")); |
| 3772 | 3775 | int i; |
| 3773 | 3776 | login_check_credentials(); |
| @@ -3776,17 +3779,15 @@ | ||
| 3776 | 3779 | return; |
| 3777 | 3780 | } |
| 3778 | 3781 | style_set_current_feature("test"); |
| 3779 | 3782 | style_header("Warning Test Page"); |
| 3780 | 3783 | 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++){ | |
| 3788 | 3789 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3789 | 3790 | } |
| 3790 | 3791 | @ </p> |
| 3791 | 3792 | @ <p><ol> |
| 3792 | 3793 | @ <li value='1'> Call fossil_warning() |
| @@ -3815,20 +3816,39 @@ | ||
| 3815 | 3816 | } |
| 3816 | 3817 | @ <li value='6'> call webpage_assert(0) |
| 3817 | 3818 | if( iCase==6 ){ |
| 3818 | 3819 | webpage_assert( 5==7 ); |
| 3819 | 3820 | } |
| 3820 | - @ <li value='7'> call webpage_error()" | |
| 3821 | + @ <li value='7'> call webpage_error() | |
| 3821 | 3822 | if( iCase==7 ){ |
| 3822 | 3823 | cgi_reset_content(); |
| 3823 | 3824 | webpage_error("Case 7 from /test-warning"); |
| 3824 | 3825 | } |
| 3825 | - @ <li value='8'> simulated timeout" | |
| 3826 | + @ <li value='8'> simulated timeout | |
| 3826 | 3827 | if( iCase==8 ){ |
| 3827 | 3828 | fossil_set_timeout(1); |
| 3828 | 3829 | cgi_reset_content(); |
| 3829 | 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 | + } | |
| 3830 | 3850 | } |
| 3831 | 3851 | @ </ol> |
| 3832 | 3852 | @ <p>End of test</p> |
| 3833 | 3853 | style_finish_page(); |
| 3834 | 3854 | } |
| 3835 | 3855 |
| --- 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 |
+1
-1
| --- src/markdown_html.c | ||
| +++ src/markdown_html.c | ||
| @@ -696,11 +696,11 @@ | ||
| 696 | 696 | ){ |
| 697 | 697 | blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); |
| 698 | 698 | } |
| 699 | 699 | blob_append(&bSrc, zSrc, nSrc) |
| 700 | 700 | /*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); | |
| 702 | 702 | blob_reset(&bSrc); |
| 703 | 703 | } |
| 704 | 704 | |
| 705 | 705 | /* Invoked for `...` blocks where there are nSep grave accents in a |
| 706 | 706 | ** row that serve as the delimiter. According to CommonMark: |
| 707 | 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, 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 |
+1
-1
| --- src/markdown_html.c | ||
| +++ src/markdown_html.c | ||
| @@ -696,11 +696,11 @@ | ||
| 696 | 696 | ){ |
| 697 | 697 | blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); |
| 698 | 698 | } |
| 699 | 699 | blob_append(&bSrc, zSrc, nSrc) |
| 700 | 700 | /*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); | |
| 702 | 702 | blob_reset(&bSrc); |
| 703 | 703 | } |
| 704 | 704 | |
| 705 | 705 | /* Invoked for `...` blocks where there are nSep grave accents in a |
| 706 | 706 | ** row that serve as the delimiter. According to CommonMark: |
| 707 | 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, 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 @@ | ||
| 27 | 27 | /* The first two must match the values from pikchr.c */ |
| 28 | 28 | #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001 |
| 29 | 29 | #define PIKCHR_PROCESS_DARK_MODE 0x0002 |
| 30 | 30 | /* end of flags supported directly by pikchr() */ |
| 31 | 31 | #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ |
| 32 | -#define PIKCHR_PROCESS_TH1 0x0004 | |
| 33 | -#define PIKCHR_PROCESS_TH1_NOSVG 0x0008 | |
| 34 | 32 | #define PIKCHR_PROCESS_NONCE 0x0010 |
| 35 | 33 | #define PIKCHR_PROCESS_ERR_PRE 0x0020 |
| 36 | 34 | #define PIKCHR_PROCESS_SRC 0x0040 |
| 37 | 35 | #define PIKCHR_PROCESS_DIV 0x0080 |
| 38 | 36 | #define PIKCHR_PROCESS_DIV_INDENT 0x0100 |
| @@ -43,36 +41,20 @@ | ||
| 43 | 41 | #define PIKCHR_PROCESS_DIV_SOURCE 0x2000 |
| 44 | 42 | #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 |
| 45 | 43 | #endif |
| 46 | 44 | |
| 47 | 45 | /* |
| 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 | |
| 50 | 47 | ** 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, | |
| 54 | 49 | ** |
| 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. | |
| 58 | 53 | ** |
| 59 | 54 | ** pikFlags flag descriptions: |
| 60 | 55 | ** |
| 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 | 56 | ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV |
| 75 | 57 | ** element which specifies a max-width style value based on the SVG's |
| 76 | 58 | ** calculated size. This flag has multiple mutually exclusive forms: |
| 77 | 59 | ** |
| 78 | 60 | ** - PIKCHR_PROCESS_DIV uses default element alignment. |
| @@ -116,14 +98,14 @@ | ||
| 116 | 98 | ** |
| 117 | 99 | ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting |
| 118 | 100 | ** error report is wrapped in a PRE element, else it is retained |
| 119 | 101 | ** as-is (intended only for console output). |
| 120 | 102 | */ |
| 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){ | |
| 124 | 104 | int isErr = 0; |
| 105 | + int w = 0, h = 0; | |
| 106 | + char *zOut; | |
| 125 | 107 | const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) |
| 126 | 108 | ? safe_html_nonce(1) : 0; |
| 127 | 109 | |
| 128 | 110 | if(!(PIKCHR_PROCESS_DIV & pikFlags) |
| 129 | 111 | /* If any DIV_xxx flags are set, set DIV */ |
| @@ -135,115 +117,87 @@ | ||
| 135 | 117 | | PIKCHR_PROCESS_DIV_SOURCE_INLINE |
| 136 | 118 | | PIKCHR_PROCESS_DIV_TOGGLE |
| 137 | 119 | ) & pikFlags){ |
| 138 | 120 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 139 | 121 | } |
| 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 | - ">→ /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 | + ">→ /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 | + } | |
| 245 | 199 | return isErr; |
| 246 | 200 | } |
| 247 | 201 | |
| 248 | 202 | /* |
| 249 | 203 | ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to |
| @@ -279,11 +233,11 @@ | ||
| 279 | 233 | TODO: respond with JSON instead.*/ |
| 280 | 234 | cgi_set_content_type("text/html"); |
| 281 | 235 | if(zContent && *zContent){ |
| 282 | 236 | Blob out = empty_blob; |
| 283 | 237 | const int isErr = |
| 284 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 238 | + pikchr_process(zContent, pikFlags, &out); | |
| 285 | 239 | if(isErr){ |
| 286 | 240 | cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); |
| 287 | 241 | } |
| 288 | 242 | CX("%b", &out); |
| 289 | 243 | blob_reset(&out); |
| @@ -384,11 +338,11 @@ | ||
| 384 | 338 | /* Reminder: Firefox does not properly flexbox a LEGEND |
| 385 | 339 | element, always flowing it in column mode. */); |
| 386 | 340 | CX("<div id='pikchrshow-output'>"); |
| 387 | 341 | if(*zContent){ |
| 388 | 342 | Blob out = empty_blob; |
| 389 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 343 | + pikchr_process(zContent, pikFlags, &out); | |
| 390 | 344 | CX("%b", &out); |
| 391 | 345 | blob_reset(&out); |
| 392 | 346 | } CX("</div>"/*#pikchrshow-output*/); |
| 393 | 347 | } CX("</fieldset>"/*#pikchrshow-output-wrapper*/); |
| 394 | 348 | } CX("</div>"/*sbs-wrapper*/); |
| @@ -561,60 +515,23 @@ | ||
| 561 | 515 | ** |
| 562 | 516 | ** -src Store the input pikchr's source code in the output as |
| 563 | 517 | ** a separate element adjacent to the SVG one. Implied |
| 564 | 518 | ** by -div-source. |
| 565 | 519 | ** |
| 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 | 520 | ** -dark Change pikchr colors to assume a dark-mode theme. |
| 580 | 521 | ** |
| 581 | 522 | ** |
| 582 | 523 | ** 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 | 524 | */ |
| 602 | 525 | void pikchr_cmd(void){ |
| 603 | 526 | Blob bIn = empty_blob; |
| 604 | 527 | Blob bOut = empty_blob; |
| 605 | 528 | const char * zInfile = "-"; |
| 606 | 529 | 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 | 530 | int isErr = 0; |
| 610 | 531 | int pikFlags = find_option("src",0,0)!=0 |
| 611 | 532 | ? 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 | 533 | |
| 617 | 534 | if(find_option("div",0,0)!=0){ |
| 618 | 535 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 619 | 536 | }else if(find_option("div-indent",0,0)!=0){ |
| 620 | 537 | pikFlags |= PIKCHR_PROCESS_DIV_INDENT; |
| @@ -644,24 +561,14 @@ | ||
| 644 | 561 | } |
| 645 | 562 | if(g.argc>3){ |
| 646 | 563 | zOutfile = g.argv[3]; |
| 647 | 564 | } |
| 648 | 565 | 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); | |
| 657 | 567 | 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); | |
| 661 | 569 | }else{ |
| 662 | 570 | blob_write_to_file(&bOut, zOutfile); |
| 663 | 571 | } |
| 664 | - Th_PrintTraceLog(); | |
| 665 | 572 | blob_reset(&bIn); |
| 666 | 573 | blob_reset(&bOut); |
| 667 | 574 | } |
| 668 | 575 |
| --- 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 | ">→ /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 | ">→ /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 @@ | ||
| 27 | 27 | /* The first two must match the values from pikchr.c */ |
| 28 | 28 | #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001 |
| 29 | 29 | #define PIKCHR_PROCESS_DARK_MODE 0x0002 |
| 30 | 30 | /* end of flags supported directly by pikchr() */ |
| 31 | 31 | #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ |
| 32 | -#define PIKCHR_PROCESS_TH1 0x0004 | |
| 33 | -#define PIKCHR_PROCESS_TH1_NOSVG 0x0008 | |
| 34 | 32 | #define PIKCHR_PROCESS_NONCE 0x0010 |
| 35 | 33 | #define PIKCHR_PROCESS_ERR_PRE 0x0020 |
| 36 | 34 | #define PIKCHR_PROCESS_SRC 0x0040 |
| 37 | 35 | #define PIKCHR_PROCESS_DIV 0x0080 |
| 38 | 36 | #define PIKCHR_PROCESS_DIV_INDENT 0x0100 |
| @@ -43,36 +41,20 @@ | ||
| 43 | 41 | #define PIKCHR_PROCESS_DIV_SOURCE 0x2000 |
| 44 | 42 | #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 |
| 45 | 43 | #endif |
| 46 | 44 | |
| 47 | 45 | /* |
| 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 | |
| 50 | 47 | ** 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, | |
| 54 | 49 | ** |
| 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. | |
| 58 | 53 | ** |
| 59 | 54 | ** pikFlags flag descriptions: |
| 60 | 55 | ** |
| 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 | 56 | ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV |
| 75 | 57 | ** element which specifies a max-width style value based on the SVG's |
| 76 | 58 | ** calculated size. This flag has multiple mutually exclusive forms: |
| 77 | 59 | ** |
| 78 | 60 | ** - PIKCHR_PROCESS_DIV uses default element alignment. |
| @@ -116,14 +98,14 @@ | ||
| 116 | 98 | ** |
| 117 | 99 | ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting |
| 118 | 100 | ** error report is wrapped in a PRE element, else it is retained |
| 119 | 101 | ** as-is (intended only for console output). |
| 120 | 102 | */ |
| 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){ | |
| 124 | 104 | int isErr = 0; |
| 105 | + int w = 0, h = 0; | |
| 106 | + char *zOut; | |
| 125 | 107 | const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) |
| 126 | 108 | ? safe_html_nonce(1) : 0; |
| 127 | 109 | |
| 128 | 110 | if(!(PIKCHR_PROCESS_DIV & pikFlags) |
| 129 | 111 | /* If any DIV_xxx flags are set, set DIV */ |
| @@ -135,115 +117,87 @@ | ||
| 135 | 117 | | PIKCHR_PROCESS_DIV_SOURCE_INLINE |
| 136 | 118 | | PIKCHR_PROCESS_DIV_TOGGLE |
| 137 | 119 | ) & pikFlags){ |
| 138 | 120 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 139 | 121 | } |
| 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 | - ">→ /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 | + ">→ /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 | + } | |
| 245 | 199 | return isErr; |
| 246 | 200 | } |
| 247 | 201 | |
| 248 | 202 | /* |
| 249 | 203 | ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to |
| @@ -279,11 +233,11 @@ | ||
| 279 | 233 | TODO: respond with JSON instead.*/ |
| 280 | 234 | cgi_set_content_type("text/html"); |
| 281 | 235 | if(zContent && *zContent){ |
| 282 | 236 | Blob out = empty_blob; |
| 283 | 237 | const int isErr = |
| 284 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 238 | + pikchr_process(zContent, pikFlags, &out); | |
| 285 | 239 | if(isErr){ |
| 286 | 240 | cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); |
| 287 | 241 | } |
| 288 | 242 | CX("%b", &out); |
| 289 | 243 | blob_reset(&out); |
| @@ -384,11 +338,11 @@ | ||
| 384 | 338 | /* Reminder: Firefox does not properly flexbox a LEGEND |
| 385 | 339 | element, always flowing it in column mode. */); |
| 386 | 340 | CX("<div id='pikchrshow-output'>"); |
| 387 | 341 | if(*zContent){ |
| 388 | 342 | Blob out = empty_blob; |
| 389 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 343 | + pikchr_process(zContent, pikFlags, &out); | |
| 390 | 344 | CX("%b", &out); |
| 391 | 345 | blob_reset(&out); |
| 392 | 346 | } CX("</div>"/*#pikchrshow-output*/); |
| 393 | 347 | } CX("</fieldset>"/*#pikchrshow-output-wrapper*/); |
| 394 | 348 | } CX("</div>"/*sbs-wrapper*/); |
| @@ -561,60 +515,23 @@ | ||
| 561 | 515 | ** |
| 562 | 516 | ** -src Store the input pikchr's source code in the output as |
| 563 | 517 | ** a separate element adjacent to the SVG one. Implied |
| 564 | 518 | ** by -div-source. |
| 565 | 519 | ** |
| 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 | 520 | ** -dark Change pikchr colors to assume a dark-mode theme. |
| 580 | 521 | ** |
| 581 | 522 | ** |
| 582 | 523 | ** 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 | 524 | */ |
| 602 | 525 | void pikchr_cmd(void){ |
| 603 | 526 | Blob bIn = empty_blob; |
| 604 | 527 | Blob bOut = empty_blob; |
| 605 | 528 | const char * zInfile = "-"; |
| 606 | 529 | 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 | 530 | int isErr = 0; |
| 610 | 531 | int pikFlags = find_option("src",0,0)!=0 |
| 611 | 532 | ? 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 | 533 | |
| 617 | 534 | if(find_option("div",0,0)!=0){ |
| 618 | 535 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 619 | 536 | }else if(find_option("div-indent",0,0)!=0){ |
| 620 | 537 | pikFlags |= PIKCHR_PROCESS_DIV_INDENT; |
| @@ -644,24 +561,14 @@ | ||
| 644 | 561 | } |
| 645 | 562 | if(g.argc>3){ |
| 646 | 563 | zOutfile = g.argv[3]; |
| 647 | 564 | } |
| 648 | 565 | 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); | |
| 657 | 567 | 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); | |
| 661 | 569 | }else{ |
| 662 | 570 | blob_write_to_file(&bOut, zOutfile); |
| 663 | 571 | } |
| 664 | - Th_PrintTraceLog(); | |
| 665 | 572 | blob_reset(&bIn); |
| 666 | 573 | blob_reset(&bOut); |
| 667 | 574 | } |
| 668 | 575 |
| --- 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 | ">→ /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 | ">→ /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 @@ | ||
| 1121 | 1121 | }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){ |
| 1122 | 1122 | fprintf(out, "%s=%s\n", azEnv[i], z); |
| 1123 | 1123 | } |
| 1124 | 1124 | } |
| 1125 | 1125 | } |
| 1126 | - fclose(out); | |
| 1126 | + if( out!=stderr ) fclose(out); | |
| 1127 | 1127 | } |
| 1128 | 1128 | |
| 1129 | 1129 | /* |
| 1130 | 1130 | ** The following variable becomes true while processing a fatal error |
| 1131 | 1131 | ** or a panic. If additional "recursive-fatal" errors occur while |
| 1132 | 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 | 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 @@ | ||
| 101 | 101 | finish_repo_list: |
| 102 | 102 | g.dbIgnoreErrors--; |
| 103 | 103 | sqlite3_close(db); |
| 104 | 104 | } |
| 105 | 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 | 106 | /* |
| 122 | 107 | ** Generate a web-page that lists all repositories located under the |
| 123 | 108 | ** g.zRepositoryName directory and return non-zero. |
| 124 | 109 | ** |
| 125 | 110 | ** For the special case when g.zRepositoryName is a non-chroot-jail "/", |
| @@ -150,17 +135,10 @@ | ||
| 150 | 135 | assert( g.db==0 ); |
| 151 | 136 | zShow = P("FOSSIL_REPOLIST_SHOW"); |
| 152 | 137 | if( zShow ){ |
| 153 | 138 | bShowDesc = strstr(zShow,"description")!=0; |
| 154 | 139 | 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 | 140 | } |
| 163 | 141 | blob_init(&html, 0, 0); |
| 164 | 142 | if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ |
| 165 | 143 | /* For the special case of the "repository directory" being "/", |
| 166 | 144 | ** show all of the repositories named in the ~/.fossil database. |
| @@ -168,10 +146,11 @@ | ||
| 168 | 146 | ** On unix systems, then entries are of the form "repo:/home/..." |
| 169 | 147 | ** and on Windows systems they are like on unix, starting with a "/" |
| 170 | 148 | ** or they can begin with a drive letter: "repo:C:/Users/...". In either |
| 171 | 149 | ** case, we want returned path to omit any initial "/". |
| 172 | 150 | */ |
| 151 | + db_open_config(1, 0); | |
| 173 | 152 | db_multi_exec( |
| 174 | 153 | "CREATE TEMP VIEW sfile AS" |
| 175 | 154 | " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config" |
| 176 | 155 | " WHERE name GLOB 'repo:*'" |
| 177 | 156 | ); |
| 178 | 157 |
| --- 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 |
+38
-12
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -100,10 +100,11 @@ | ||
| 100 | 100 | const char *zReadCap; /* Capabilities of user group "reader" */ |
| 101 | 101 | const char *zPubPages; /* GLOB pattern for public pages */ |
| 102 | 102 | const char *zSelfCap; /* Capabilities of self-registered users */ |
| 103 | 103 | int hasSelfReg = 0; /* True if able to self-register */ |
| 104 | 104 | const char *zPublicUrl; /* Canonical access URL */ |
| 105 | + const char *zVulnReport; /* The vuln-report setting */ | |
| 105 | 106 | Blob cmd; |
| 106 | 107 | char *z; |
| 107 | 108 | int n, i; |
| 108 | 109 | CapabilityString *pCap; |
| 109 | 110 | char **azCSP; /* Parsed content security policy */ |
| @@ -362,10 +363,22 @@ | ||
| 362 | 363 | @ <li><p><b>WARNING:</b> |
| 363 | 364 | @ The "strict-manifest-syntax" flag is off. This is a security |
| 364 | 365 | @ risk. Turn this setting on (its default) to protect the users |
| 365 | 366 | @ of this repository. |
| 366 | 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 | + } | |
| 367 | 380 | |
| 368 | 381 | /* Obsolete: */ |
| 369 | 382 | if( hasAnyCap(zAnonCap, "d") || |
| 370 | 383 | hasAnyCap(zDevCap, "d") || |
| 371 | 384 | hasAnyCap(zReadCap, "d") ){ |
| @@ -810,27 +823,28 @@ | ||
| 810 | 823 | ** WEBPAGE: errorlog |
| 811 | 824 | ** |
| 812 | 825 | ** Show the content of the error log. Only the administrator can view |
| 813 | 826 | ** this page. |
| 814 | 827 | ** |
| 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 | |
| 822 | 836 | ** |
| 823 | 837 | ** If y is omitted or is zero, a count of the various message types is |
| 824 | 838 | ** shown. |
| 825 | 839 | */ |
| 826 | 840 | void errorlog_page(void){ |
| 827 | 841 | i64 szFile; |
| 828 | 842 | FILE *in; |
| 829 | 843 | char *zLog; |
| 830 | 844 | const char *zType = P("y"); |
| 831 | - static const int eAllTypes = 0x7f; | |
| 845 | + static const int eAllTypes = 0x87f; | |
| 832 | 846 | long eType = 0; |
| 833 | 847 | int bOutput = 0; |
| 834 | 848 | int prevWasTime = 0; |
| 835 | 849 | int nHack = 0; |
| 836 | 850 | int nPanic = 0; |
| @@ -837,10 +851,11 @@ | ||
| 837 | 851 | int nOther = 0; |
| 838 | 852 | int nHang = 0; |
| 839 | 853 | int nXPost = 0; |
| 840 | 854 | int nAuth = 0; |
| 841 | 855 | int nSmtp = 0; |
| 856 | + int nVuln = 0; | |
| 842 | 857 | char z[10000]; |
| 843 | 858 | char zTime[10000]; |
| 844 | 859 | |
| 845 | 860 | login_check_credentials(); |
| 846 | 861 | if( !g.perm.Admin ){ |
| @@ -917,10 +932,13 @@ | ||
| 917 | 932 | } |
| 918 | 933 | if( eType & 0x20 ){ |
| 919 | 934 | @ <li>SMTP malfunctions |
| 920 | 935 | } |
| 921 | 936 | if( eType & 0x40 ){ |
| 937 | + @ <li>TH1 vulnerabilities | |
| 938 | + } | |
| 939 | + if( eType & 0x800 ){ | |
| 922 | 940 | @ <li>Other uncategorized messages |
| 923 | 941 | } |
| 924 | 942 | @ </ul> |
| 925 | 943 | } |
| 926 | 944 | @ <hr> |
| @@ -953,12 +971,16 @@ | ||
| 953 | 971 | || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 |
| 954 | 972 | ){ |
| 955 | 973 | bOutput = (eType & 0x10)!=0; |
| 956 | 974 | nAuth++; |
| 957 | 975 | }else |
| 958 | - { | |
| 976 | + if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){ | |
| 959 | 977 | bOutput = (eType & 0x40)!=0; |
| 978 | + nVuln++; | |
| 979 | + }else | |
| 980 | + { | |
| 981 | + bOutput = (eType & 0x800)!=0; | |
| 960 | 982 | nOther++; |
| 961 | 983 | } |
| 962 | 984 | if( bOutput ){ |
| 963 | 985 | @ %h(zTime)\ |
| 964 | 986 | } |
| @@ -978,17 +1000,21 @@ | ||
| 978 | 1000 | fclose(in); |
| 979 | 1001 | if( eType ){ |
| 980 | 1002 | @ </pre> |
| 981 | 1003 | } |
| 982 | 1004 | if( eType==0 ){ |
| 983 | - int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther; | |
| 1005 | + int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther; | |
| 984 | 1006 | int nTotal = nNonHack + nHack + nXPost; |
| 985 | 1007 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 986 | 1008 | if( nPanic>0 ){ |
| 987 | 1009 | @ <tr><td align="right">%d(nPanic)</td> |
| 988 | 1010 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| 989 | 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 | + } | |
| 990 | 1016 | if( nHack>0 ){ |
| 991 | 1017 | @ <tr><td align="right">%d(nHack)</td> |
| 992 | 1018 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 993 | 1019 | } |
| 994 | 1020 | if( nHang>0 ){ |
| @@ -1007,17 +1033,17 @@ | ||
| 1007 | 1033 | @ <tr><td align="right">%d(nSmtp)</td> |
| 1008 | 1034 | @ <td><a href="./errorlog?y=32">SMTP faults</a></td> |
| 1009 | 1035 | } |
| 1010 | 1036 | if( nOther>0 ){ |
| 1011 | 1037 | @ <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> | |
| 1013 | 1039 | } |
| 1014 | 1040 | @ <tr><td align="right">%d(nTotal)</td> |
| 1015 | 1041 | 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> | |
| 1017 | 1043 | }else{ |
| 1018 | 1044 | @ <td>All Messages</td> |
| 1019 | 1045 | } |
| 1020 | 1046 | @ </table> |
| 1021 | 1047 | } |
| 1022 | 1048 | style_finish_page(); |
| 1023 | 1049 | } |
| 1024 | 1050 |
| --- 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 @@ | ||
| 744 | 744 | ** is evaluated before the header is rendered). |
| 745 | 745 | */ |
| 746 | 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | 747 | fossil_free(zDfltCsp); |
| 748 | 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","")); | |
| 749 | + Th_StoreUnsafe("project_name", | |
| 750 | + db_get("project-name","Unnamed Fossil Project")); | |
| 751 | + Th_StoreUnsafe("project_description", db_get("project-description","")); | |
| 751 | 752 | if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); |
| 752 | 753 | Th_Store("baseurl", g.zBaseURL); |
| 753 | 754 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | 755 | Th_Store("home", g.zTop); |
| 755 | 756 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 | 757 |
| --- 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 |
M
src/th.c
+87
-54
| --- src/th.c | ||
| +++ src/th.c | ||
| @@ -7,10 +7,16 @@ | ||
| 7 | 7 | #include "config.h" |
| 8 | 8 | #include "th.h" |
| 9 | 9 | #include <string.h> |
| 10 | 10 | #include <assert.h> |
| 11 | 11 | |
| 12 | +/* | |
| 13 | +** External routines | |
| 14 | +*/ | |
| 15 | +void fossil_panic(const char*,...); | |
| 16 | +void fossil_errorlog(const char*,...); | |
| 17 | + | |
| 12 | 18 | /* |
| 13 | 19 | ** Values used for element values in the tcl_platform array. |
| 14 | 20 | */ |
| 15 | 21 | |
| 16 | 22 | #if !defined(TH_ENGINE) |
| @@ -197,10 +203,11 @@ | ||
| 197 | 203 | */ |
| 198 | 204 | struct Buffer { |
| 199 | 205 | char *zBuf; |
| 200 | 206 | int nBuf; |
| 201 | 207 | int nBufAlloc; |
| 208 | + int bTaint; | |
| 202 | 209 | }; |
| 203 | 210 | typedef struct Buffer Buffer; |
| 204 | 211 | static void thBufferInit(Buffer *); |
| 205 | 212 | static void thBufferFree(Th_Interp *interp, Buffer *); |
| 206 | 213 | |
| @@ -209,10 +216,18 @@ | ||
| 209 | 216 | ** be NULL as long as the number of bytes to copy is zero. |
| 210 | 217 | */ |
| 211 | 218 | static void th_memcpy(void *dest, const void *src, size_t n){ |
| 212 | 219 | if( n>0 ) memcpy(dest,src,n); |
| 213 | 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 | +} | |
| 214 | 229 | |
| 215 | 230 | /* |
| 216 | 231 | ** Append nAdd bytes of content copied from zAdd to the end of buffer |
| 217 | 232 | ** pBuffer. If there is not enough space currently allocated, resize |
| 218 | 233 | ** the allocation to make space. |
| @@ -219,40 +234,46 @@ | ||
| 219 | 234 | */ |
| 220 | 235 | static void thBufferWriteResize( |
| 221 | 236 | Th_Interp *interp, |
| 222 | 237 | Buffer *pBuffer, |
| 223 | 238 | const char *zAdd, |
| 224 | - int nAdd | |
| 239 | + int nAddX | |
| 225 | 240 | ){ |
| 241 | + int nAdd = TH1_LEN(nAddX); | |
| 226 | 242 | int nNew = (pBuffer->nBuf+nAdd)*2+32; |
| 227 | 243 | #if defined(TH_MEMDEBUG) |
| 228 | 244 | char *zNew = (char *)Th_Malloc(interp, nNew); |
| 245 | + TH1_SIZECHECK(nNew); | |
| 229 | 246 | th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf); |
| 230 | 247 | Th_Free(interp, pBuffer->zBuf); |
| 231 | 248 | pBuffer->zBuf = zNew; |
| 232 | 249 | #else |
| 233 | 250 | int nOld = pBuffer->nBufAlloc; |
| 251 | + TH1_SIZECHECK(nNew); | |
| 234 | 252 | pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew); |
| 235 | 253 | memset(pBuffer->zBuf+nOld, 0, nNew-nOld); |
| 236 | 254 | #endif |
| 237 | 255 | pBuffer->nBufAlloc = nNew; |
| 238 | 256 | th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd); |
| 239 | 257 | pBuffer->nBuf += nAdd; |
| 258 | + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); | |
| 240 | 259 | } |
| 241 | 260 | static void thBufferWriteFast( |
| 242 | 261 | Th_Interp *interp, |
| 243 | 262 | Buffer *pBuffer, |
| 244 | 263 | const char *zAdd, |
| 245 | - int nAdd | |
| 264 | + int nAddX | |
| 246 | 265 | ){ |
| 266 | + int nAdd = TH1_LEN(nAddX); | |
| 247 | 267 | if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){ |
| 248 | - thBufferWriteResize(interp, pBuffer, zAdd, nAdd); | |
| 268 | + thBufferWriteResize(interp, pBuffer, zAdd, nAddX); | |
| 249 | 269 | }else{ |
| 250 | 270 | if( pBuffer->zBuf ){ |
| 251 | 271 | memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd); |
| 252 | 272 | } |
| 253 | 273 | pBuffer->nBuf += nAdd; |
| 274 | + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); | |
| 254 | 275 | } |
| 255 | 276 | } |
| 256 | 277 | #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d) |
| 257 | 278 | |
| 258 | 279 | /* |
| @@ -704,24 +725,25 @@ | ||
| 704 | 725 | int nWord |
| 705 | 726 | ){ |
| 706 | 727 | int rc = TH_OK; |
| 707 | 728 | Buffer output; |
| 708 | 729 | int i; |
| 730 | + int nn = TH1_LEN(nWord); | |
| 709 | 731 | |
| 710 | 732 | thBufferInit(&output); |
| 711 | 733 | |
| 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); | |
| 714 | 736 | }else{ |
| 715 | 737 | |
| 716 | 738 | /* 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]=='"') ){ | |
| 718 | 740 | zWord++; |
| 719 | - nWord -= 2; | |
| 741 | + nn -= 2; | |
| 720 | 742 | } |
| 721 | 743 | |
| 722 | - for(i=0; rc==TH_OK && i<nWord; i++){ | |
| 744 | + for(i=0; rc==TH_OK && i<nn; i++){ | |
| 723 | 745 | int nGet; |
| 724 | 746 | |
| 725 | 747 | int (*xGet)(Th_Interp *, const char*, int, int *) = 0; |
| 726 | 748 | int (*xSubst)(Th_Interp *, const char*, int) = 0; |
| 727 | 749 | |
| @@ -743,11 +765,11 @@ | ||
| 743 | 765 | thBufferAddChar(interp, &output, zWord[i]); |
| 744 | 766 | continue; /* Go to the next iteration of the for(...) loop */ |
| 745 | 767 | } |
| 746 | 768 | } |
| 747 | 769 | |
| 748 | - rc = xGet(interp, &zWord[i], nWord-i, &nGet); | |
| 770 | + rc = xGet(interp, &zWord[i], nn-i, &nGet); | |
| 749 | 771 | if( rc==TH_OK ){ |
| 750 | 772 | rc = xSubst(interp, &zWord[i], nGet); |
| 751 | 773 | } |
| 752 | 774 | if( rc==TH_OK ){ |
| 753 | 775 | const char *zRes; |
| @@ -758,11 +780,11 @@ | ||
| 758 | 780 | } |
| 759 | 781 | } |
| 760 | 782 | } |
| 761 | 783 | |
| 762 | 784 | if( rc==TH_OK ){ |
| 763 | - Th_SetResult(interp, output.zBuf, output.nBuf); | |
| 785 | + Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint); | |
| 764 | 786 | } |
| 765 | 787 | thBufferFree(interp, &output); |
| 766 | 788 | return rc; |
| 767 | 789 | } |
| 768 | 790 | |
| @@ -826,11 +848,11 @@ | ||
| 826 | 848 | Buffer strbuf; |
| 827 | 849 | Buffer lenbuf; |
| 828 | 850 | int nCount = 0; |
| 829 | 851 | |
| 830 | 852 | const char *zInput = zList; |
| 831 | - int nInput = nList; | |
| 853 | + int nInput = TH1_LEN(nList); | |
| 832 | 854 | |
| 833 | 855 | thBufferInit(&strbuf); |
| 834 | 856 | thBufferInit(&lenbuf); |
| 835 | 857 | |
| 836 | 858 | while( nInput>0 ){ |
| @@ -837,19 +859,19 @@ | ||
| 837 | 859 | const char *zWord; |
| 838 | 860 | int nWord; |
| 839 | 861 | |
| 840 | 862 | thNextSpace(interp, zInput, nInput, &nWord); |
| 841 | 863 | zInput += nWord; |
| 842 | - nInput = nList-(zInput-zList); | |
| 864 | + nInput = TH1_LEN(nList)-(zInput-zList); | |
| 843 | 865 | |
| 844 | 866 | if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0)) |
| 845 | 867 | || TH_OK!=(rc = thSubstWord(interp, zInput, nWord)) |
| 846 | 868 | ){ |
| 847 | 869 | goto finish; |
| 848 | 870 | } |
| 849 | - zInput = &zInput[nWord]; | |
| 850 | - nInput = nList-(zInput-zList); | |
| 871 | + zInput = &zInput[TH1_LEN(nWord)]; | |
| 872 | + nInput = TH1_LEN(nList)-(zInput-zList); | |
| 851 | 873 | if( nWord>0 ){ |
| 852 | 874 | zWord = Th_GetResult(interp, &nWord); |
| 853 | 875 | thBufferWrite(interp, &strbuf, zWord, nWord); |
| 854 | 876 | thBufferAddChar(interp, &strbuf, 0); |
| 855 | 877 | thBufferWrite(interp, &lenbuf, &nWord, sizeof(int)); |
| @@ -872,11 +894,11 @@ | ||
| 872 | 894 | zElem = (char *)&anElem[nCount]; |
| 873 | 895 | th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf); |
| 874 | 896 | th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf); |
| 875 | 897 | for(i=0; i<nCount;i++){ |
| 876 | 898 | azElem[i] = zElem; |
| 877 | - zElem += (anElem[i] + 1); | |
| 899 | + zElem += (TH1_LEN(anElem[i]) + 1); | |
| 878 | 900 | } |
| 879 | 901 | *pazElem = azElem; |
| 880 | 902 | *panElem = anElem; |
| 881 | 903 | } |
| 882 | 904 | if( pnCount ){ |
| @@ -894,12 +916,17 @@ | ||
| 894 | 916 | ** in the current stack frame. |
| 895 | 917 | */ |
| 896 | 918 | static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){ |
| 897 | 919 | int rc = TH_OK; |
| 898 | 920 | const char *zInput = zProgram; |
| 899 | - int nInput = nProgram; | |
| 921 | + int nInput = TH1_LEN(nProgram); | |
| 900 | 922 | |
| 923 | + if( TH1_TAINTED(nProgram) | |
| 924 | + && Th_ReportTaint(interp, "script", zProgram, nProgram) | |
| 925 | + ){ | |
| 926 | + return TH_ERROR; | |
| 927 | + } | |
| 901 | 928 | while( rc==TH_OK && nInput ){ |
| 902 | 929 | Th_HashEntry *pEntry; |
| 903 | 930 | int nSpace; |
| 904 | 931 | const char *zFirst; |
| 905 | 932 | |
| @@ -949,13 +976,13 @@ | ||
| 949 | 976 | if( rc!=TH_OK ) continue; |
| 950 | 977 | |
| 951 | 978 | if( argc>0 ){ |
| 952 | 979 | |
| 953 | 980 | /* 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); | |
| 955 | 982 | 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])); | |
| 957 | 984 | rc = TH_ERROR; |
| 958 | 985 | } |
| 959 | 986 | |
| 960 | 987 | /* Call the command procedure. */ |
| 961 | 988 | if( rc==TH_OK ){ |
| @@ -1053,10 +1080,12 @@ | ||
| 1053 | 1080 | }else{ |
| 1054 | 1081 | int nInput = nProgram; |
| 1055 | 1082 | |
| 1056 | 1083 | if( nInput<0 ){ |
| 1057 | 1084 | nInput = th_strlen(zProgram); |
| 1085 | + }else{ | |
| 1086 | + nInput = TH1_LEN(nInput); | |
| 1058 | 1087 | } |
| 1059 | 1088 | rc = thEvalLocal(interp, zProgram, nInput); |
| 1060 | 1089 | } |
| 1061 | 1090 | |
| 1062 | 1091 | interp->pFrame = pSavedFrame; |
| @@ -1095,10 +1124,12 @@ | ||
| 1095 | 1124 | int isGlobal = 0; |
| 1096 | 1125 | int i; |
| 1097 | 1126 | |
| 1098 | 1127 | if( nVarname<0 ){ |
| 1099 | 1128 | nVarname = th_strlen(zVarname); |
| 1129 | + }else{ | |
| 1130 | + nVarname = TH1_LEN(nVarname); | |
| 1100 | 1131 | } |
| 1101 | 1132 | nOuter = nVarname; |
| 1102 | 1133 | |
| 1103 | 1134 | /* If the variable name starts with "::", then do the lookup is in the |
| 1104 | 1135 | ** uppermost (global) frame. |
| @@ -1271,31 +1302,10 @@ | ||
| 1271 | 1302 | } |
| 1272 | 1303 | |
| 1273 | 1304 | return Th_SetResult(interp, pValue->zData, pValue->nData); |
| 1274 | 1305 | } |
| 1275 | 1306 | |
| 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 | 1307 | /* |
| 1298 | 1308 | ** Return true if variable (zVar, nVar) exists. |
| 1299 | 1309 | */ |
| 1300 | 1310 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1301 | 1311 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| @@ -1324,28 +1334,32 @@ | ||
| 1324 | 1334 | int nVar, |
| 1325 | 1335 | const char *zValue, |
| 1326 | 1336 | int nValue |
| 1327 | 1337 | ){ |
| 1328 | 1338 | Th_Variable *pValue; |
| 1339 | + int nn; | |
| 1329 | 1340 | |
| 1341 | + nVar = TH1_LEN(nVar); | |
| 1330 | 1342 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1331 | 1343 | if( !pValue ){ |
| 1332 | 1344 | return TH_ERROR; |
| 1333 | 1345 | } |
| 1334 | 1346 | |
| 1335 | 1347 | if( nValue<0 ){ |
| 1336 | - nValue = th_strlen(zValue); | |
| 1348 | + nn = th_strlen(zValue); | |
| 1349 | + }else{ | |
| 1350 | + nn = TH1_LEN(nValue); | |
| 1337 | 1351 | } |
| 1338 | 1352 | if( pValue->zData ){ |
| 1339 | 1353 | Th_Free(interp, pValue->zData); |
| 1340 | 1354 | pValue->zData = 0; |
| 1341 | 1355 | } |
| 1342 | 1356 | |
| 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); | |
| 1347 | 1361 | pValue->nData = nValue; |
| 1348 | 1362 | |
| 1349 | 1363 | return TH_OK; |
| 1350 | 1364 | } |
| 1351 | 1365 | |
| @@ -1458,10 +1472,12 @@ | ||
| 1458 | 1472 | */ |
| 1459 | 1473 | char *th_strdup(Th_Interp *interp, const char *z, int n){ |
| 1460 | 1474 | char *zRes; |
| 1461 | 1475 | if( n<0 ){ |
| 1462 | 1476 | n = th_strlen(z); |
| 1477 | + }else{ | |
| 1478 | + n = TH1_LEN(n); | |
| 1463 | 1479 | } |
| 1464 | 1480 | zRes = Th_Malloc(interp, n+1); |
| 1465 | 1481 | th_memcpy(zRes, z, n); |
| 1466 | 1482 | zRes[n] = '\0'; |
| 1467 | 1483 | return zRes; |
| @@ -1519,13 +1535,14 @@ | ||
| 1519 | 1535 | n = th_strlen(z); |
| 1520 | 1536 | } |
| 1521 | 1537 | |
| 1522 | 1538 | if( z && n>0 ){ |
| 1523 | 1539 | 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'; | |
| 1527 | 1544 | pInterp->zResult = zResult; |
| 1528 | 1545 | pInterp->nResult = n; |
| 1529 | 1546 | } |
| 1530 | 1547 | |
| 1531 | 1548 | return TH_OK; |
| @@ -1777,15 +1794,19 @@ | ||
| 1777 | 1794 | int hasSpecialChar = 0; /* Whitespace or {}[]'" */ |
| 1778 | 1795 | int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */ |
| 1779 | 1796 | int nBrace = 0; |
| 1780 | 1797 | |
| 1781 | 1798 | output.zBuf = *pzList; |
| 1782 | - output.nBuf = *pnList; | |
| 1799 | + output.nBuf = TH1_LEN(*pnList); | |
| 1783 | 1800 | output.nBufAlloc = output.nBuf; |
| 1801 | + output.bTaint = 0; | |
| 1802 | + TH1_XFER_TAINT(output.bTaint, *pnList); | |
| 1784 | 1803 | |
| 1785 | 1804 | if( nElem<0 ){ |
| 1786 | 1805 | nElem = th_strlen(zElem); |
| 1806 | + }else{ | |
| 1807 | + nElem = TH1_LEN(nElem); | |
| 1787 | 1808 | } |
| 1788 | 1809 | if( output.nBuf>0 ){ |
| 1789 | 1810 | thBufferAddChar(interp, &output, ' '); |
| 1790 | 1811 | } |
| 1791 | 1812 | |
| @@ -1834,24 +1855,28 @@ | ||
| 1834 | 1855 | int *pnStr, /* IN/OUT: Current length of *pzStr */ |
| 1835 | 1856 | const char *zElem, /* Data to append */ |
| 1836 | 1857 | int nElem /* Length of nElem */ |
| 1837 | 1858 | ){ |
| 1838 | 1859 | char *zNew; |
| 1839 | - int nNew; | |
| 1860 | + long long int nNew; | |
| 1861 | + int nn; | |
| 1840 | 1862 | |
| 1841 | 1863 | if( nElem<0 ){ |
| 1842 | - nElem = th_strlen(zElem); | |
| 1864 | + nn = th_strlen(zElem); | |
| 1865 | + }else{ | |
| 1866 | + nn = TH1_LEN(nElem); | |
| 1843 | 1867 | } |
| 1844 | 1868 | |
| 1845 | - nNew = *pnStr + nElem; | |
| 1869 | + nNew = TH1_LEN(*pnStr) + nn; | |
| 1870 | + TH1_SIZECHECK(nNew); | |
| 1846 | 1871 | zNew = Th_Malloc(interp, nNew); |
| 1847 | 1872 | th_memcpy(zNew, *pzStr, *pnStr); |
| 1848 | - th_memcpy(&zNew[*pnStr], zElem, nElem); | |
| 1873 | + th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn); | |
| 1849 | 1874 | |
| 1850 | 1875 | Th_Free(interp, *pzStr); |
| 1851 | 1876 | *pzStr = zNew; |
| 1852 | - *pnStr = nNew; | |
| 1877 | + *pnStr = (int)nNew; | |
| 1853 | 1878 | |
| 1854 | 1879 | return TH_OK; |
| 1855 | 1880 | } |
| 1856 | 1881 | |
| 1857 | 1882 | /* |
| @@ -2106,16 +2131,18 @@ | ||
| 2106 | 2131 | /* Evaluate left and right arguments, if they exist. */ |
| 2107 | 2132 | if( pExpr->pLeft ){ |
| 2108 | 2133 | rc = exprEval(interp, pExpr->pLeft); |
| 2109 | 2134 | if( rc==TH_OK ){ |
| 2110 | 2135 | zLeft = Th_TakeResult(interp, &nLeft); |
| 2136 | + nLeft = TH1_LEN(nLeft); | |
| 2111 | 2137 | } |
| 2112 | 2138 | } |
| 2113 | 2139 | if( rc==TH_OK && pExpr->pRight ){ |
| 2114 | 2140 | rc = exprEval(interp, pExpr->pRight); |
| 2115 | 2141 | if( rc==TH_OK ){ |
| 2116 | 2142 | zRight = Th_TakeResult(interp, &nRight); |
| 2143 | + nRight = TH1_LEN(nRight); | |
| 2117 | 2144 | } |
| 2118 | 2145 | } |
| 2119 | 2146 | |
| 2120 | 2147 | /* Convert arguments to their required forms. */ |
| 2121 | 2148 | if( rc==TH_OK ){ |
| @@ -2456,10 +2483,12 @@ | ||
| 2456 | 2483 | int nToken = 0; |
| 2457 | 2484 | Expr **apToken = 0; |
| 2458 | 2485 | |
| 2459 | 2486 | if( nExpr<0 ){ |
| 2460 | 2487 | nExpr = th_strlen(zExpr); |
| 2488 | + }else{ | |
| 2489 | + nExpr = TH1_LEN(nExpr); | |
| 2461 | 2490 | } |
| 2462 | 2491 | |
| 2463 | 2492 | /* Parse the expression to a list of tokens. */ |
| 2464 | 2493 | rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); |
| 2465 | 2494 | |
| @@ -2567,10 +2596,12 @@ | ||
| 2567 | 2596 | Th_HashEntry *pRet; |
| 2568 | 2597 | Th_HashEntry **ppRet; |
| 2569 | 2598 | |
| 2570 | 2599 | if( nKey<0 ){ |
| 2571 | 2600 | nKey = th_strlen(zKey); |
| 2601 | + }else{ | |
| 2602 | + nKey = TH1_LEN(nKey); | |
| 2572 | 2603 | } |
| 2573 | 2604 | |
| 2574 | 2605 | for(i=0; i<nKey; i++){ |
| 2575 | 2606 | iKey = (iKey<<3) ^ iKey ^ zKey[i]; |
| 2576 | 2607 | } |
| @@ -2800,10 +2831,12 @@ | ||
| 2800 | 2831 | int base = 10; |
| 2801 | 2832 | int (*isdigit)(char) = th_isdigit; |
| 2802 | 2833 | |
| 2803 | 2834 | if( n<0 ){ |
| 2804 | 2835 | n = th_strlen(z); |
| 2836 | + }else{ | |
| 2837 | + n = TH1_LEN(n); | |
| 2805 | 2838 | } |
| 2806 | 2839 | |
| 2807 | 2840 | if( n>1 && (z[0]=='-' || z[0]=='+') ){ |
| 2808 | 2841 | i = 1; |
| 2809 | 2842 | } |
| @@ -2859,11 +2892,11 @@ | ||
| 2859 | 2892 | const char *z, |
| 2860 | 2893 | int n, |
| 2861 | 2894 | double *pfOut |
| 2862 | 2895 | ){ |
| 2863 | 2896 | 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)); | |
| 2865 | 2898 | return TH_ERROR; |
| 2866 | 2899 | } |
| 2867 | 2900 | |
| 2868 | 2901 | sqlite3AtoF((const char *)z, pfOut); |
| 2869 | 2902 | return TH_OK; |
| 2870 | 2903 |
| --- 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 |
M
src/th.h
+53
-14
| --- src/th.h | ||
| +++ src/th.h | ||
| @@ -1,10 +1,56 @@ | ||
| 1 | - | |
| 2 | 1 | /* This header file defines the external interface to the custom Scripting |
| 3 | 2 | ** Language (TH) interpreter. TH is very similar to Tcl but is not an |
| 4 | 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. | |
| 5 | 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); | |
| 6 | 52 | |
| 7 | 53 | /* |
| 8 | 54 | ** Before creating an interpreter, the application must allocate and |
| 9 | 55 | ** populate an instance of the following structure. It must remain valid |
| 10 | 56 | ** for the lifetime of the interpreter. |
| @@ -24,10 +70,16 @@ | ||
| 24 | 70 | ** Create and delete interpreters. |
| 25 | 71 | */ |
| 26 | 72 | Th_Interp * Th_CreateInterp(Th_Vtab *); |
| 27 | 73 | void Th_DeleteInterp(Th_Interp *); |
| 28 | 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 | + | |
| 29 | 81 | /* |
| 30 | 82 | ** Evaluate an TH program in the stack frame identified by parameter |
| 31 | 83 | ** iFrame, according to the following rules: |
| 32 | 84 | ** |
| 33 | 85 | ** * If iFrame is 0, this means the current frame. |
| @@ -56,23 +108,10 @@ | ||
| 56 | 108 | int Th_GetVar(Th_Interp *, const char *, int); |
| 57 | 109 | int Th_SetVar(Th_Interp *, const char *, int, const char *, int); |
| 58 | 110 | int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); |
| 59 | 111 | int Th_UnsetVar(Th_Interp *, const char *, int); |
| 60 | 112 | |
| 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 | 113 | typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); |
| 75 | 114 | |
| 76 | 115 | /* |
| 77 | 116 | ** Register new commands. |
| 78 | 117 | */ |
| 79 | 118 |
| --- 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 @@ | ||
| 39 | 39 | |
| 40 | 40 | rc = Th_Eval(interp, 0, argv[1], -1); |
| 41 | 41 | if( argc==3 ){ |
| 42 | 42 | int nResult; |
| 43 | 43 | 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); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | Th_SetResultInt(interp, rc); |
| 48 | 48 | return TH_OK; |
| 49 | 49 | } |
| @@ -180,20 +180,24 @@ | ||
| 180 | 180 | int nVar; |
| 181 | 181 | char **azValue = 0; |
| 182 | 182 | int *anValue; |
| 183 | 183 | int nValue; |
| 184 | 184 | int ii, jj; |
| 185 | + int bTaint = 0; | |
| 185 | 186 | |
| 186 | 187 | if( argc!=4 ){ |
| 187 | 188 | return Th_WrongNumArgs(interp, "foreach varlist list script"); |
| 188 | 189 | } |
| 189 | 190 | rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar); |
| 190 | 191 | if( rc ) return rc; |
| 192 | + TH1_XFER_TAINT(bTaint, argl[2]); | |
| 191 | 193 | rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue); |
| 192 | 194 | for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){ |
| 193 | 195 | 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); | |
| 195 | 199 | } |
| 196 | 200 | rc = eval_loopbody(interp, argv[3], argl[3]); |
| 197 | 201 | } |
| 198 | 202 | if( rc==TH_BREAK ) rc = TH_OK; |
| 199 | 203 | Th_Free(interp, azVar); |
| @@ -215,15 +219,18 @@ | ||
| 215 | 219 | int *argl |
| 216 | 220 | ){ |
| 217 | 221 | char *zList = 0; |
| 218 | 222 | int nList = 0; |
| 219 | 223 | int i; |
| 224 | + int bTaint = 0; | |
| 220 | 225 | |
| 221 | 226 | for(i=1; i<argc; i++){ |
| 227 | + TH1_XFER_TAINT(bTaint,argl[i]); | |
| 222 | 228 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 223 | 229 | } |
| 224 | 230 | |
| 231 | + TH1_XFER_TAINT(nList, bTaint); | |
| 225 | 232 | Th_SetResult(interp, zList, nList); |
| 226 | 233 | Th_Free(interp, zList); |
| 227 | 234 | |
| 228 | 235 | return TH_OK; |
| 229 | 236 | } |
| @@ -244,23 +251,27 @@ | ||
| 244 | 251 | int *argl |
| 245 | 252 | ){ |
| 246 | 253 | char *zList = 0; |
| 247 | 254 | int nList = 0; |
| 248 | 255 | int i, rc; |
| 256 | + int bTaint = 0; | |
| 249 | 257 | |
| 250 | 258 | if( argc<2 ){ |
| 251 | 259 | return Th_WrongNumArgs(interp, "lappend var ..."); |
| 252 | 260 | } |
| 253 | 261 | rc = Th_GetVar(interp, argv[1], argl[1]); |
| 254 | 262 | if( rc==TH_OK ){ |
| 255 | 263 | zList = Th_TakeResult(interp, &nList); |
| 256 | 264 | } |
| 257 | 265 | |
| 266 | + TH1_XFER_TAINT(bTaint, nList); | |
| 258 | 267 | for(i=2; i<argc; i++){ |
| 268 | + TH1_XFER_TAINT(bTaint, argl[i]); | |
| 259 | 269 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 260 | 270 | } |
| 261 | 271 | |
| 272 | + TH1_XFER_TAINT(nList, bTaint); | |
| 262 | 273 | Th_SetVar(interp, argv[1], argl[1], zList, nList); |
| 263 | 274 | Th_SetResult(interp, zList, nList); |
| 264 | 275 | Th_Free(interp, zList); |
| 265 | 276 | |
| 266 | 277 | return TH_OK; |
| @@ -283,23 +294,27 @@ | ||
| 283 | 294 | int rc; |
| 284 | 295 | |
| 285 | 296 | char **azElem; |
| 286 | 297 | int *anElem; |
| 287 | 298 | int nCount; |
| 299 | + int bTaint = 0; | |
| 288 | 300 | |
| 289 | 301 | if( argc!=3 ){ |
| 290 | 302 | return Th_WrongNumArgs(interp, "lindex list index"); |
| 291 | 303 | } |
| 292 | 304 | |
| 293 | 305 | if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){ |
| 294 | 306 | return TH_ERROR; |
| 295 | 307 | } |
| 296 | 308 | |
| 309 | + TH1_XFER_TAINT(bTaint, argl[1]); | |
| 297 | 310 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 298 | 311 | if( rc==TH_OK ){ |
| 299 | 312 | 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); | |
| 301 | 316 | }else{ |
| 302 | 317 | Th_SetResult(interp, 0, 0); |
| 303 | 318 | } |
| 304 | 319 | Th_Free(interp, azElem); |
| 305 | 320 | } |
| @@ -356,13 +371,14 @@ | ||
| 356 | 371 | return Th_WrongNumArgs(interp, "lsearch list string"); |
| 357 | 372 | } |
| 358 | 373 | |
| 359 | 374 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 360 | 375 | if( rc==TH_OK ){ |
| 376 | + int nn = TH1_LEN(argl[2]); | |
| 361 | 377 | Th_SetResultInt(interp, -1); |
| 362 | 378 | 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) ){ | |
| 364 | 380 | Th_SetResultInt(interp, i); |
| 365 | 381 | break; |
| 366 | 382 | } |
| 367 | 383 | } |
| 368 | 384 | Th_Free(interp, azElem); |
| @@ -561,28 +577,31 @@ | ||
| 561 | 577 | int nUsage = 0; /* Number of bytes at zUsage */ |
| 562 | 578 | |
| 563 | 579 | if( argc!=4 ){ |
| 564 | 580 | return Th_WrongNumArgs(interp, "proc name arglist code"); |
| 565 | 581 | } |
| 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) ){ | |
| 567 | 584 | return TH_ERROR; |
| 568 | 585 | } |
| 569 | 586 | |
| 570 | 587 | /* Allocate the new ProcDefn structure. */ |
| 571 | 588 | nByte = sizeof(ProcDefn) + /* ProcDefn structure */ |
| 572 | 589 | (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ |
| 573 | 590 | (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 */ | |
| 576 | 593 | p = (ProcDefn *)Th_Malloc(interp, nByte); |
| 577 | 594 | |
| 578 | 595 | /* If the last parameter in the parameter list is "args", then set the |
| 579 | 596 | ** ProcDefn.hasArgs flag. The "args" parameter does not require an |
| 580 | 597 | ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. |
| 581 | 598 | */ |
| 582 | 599 | 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 | + ){ | |
| 584 | 603 | p->hasArgs = 1; |
| 585 | 604 | nParam--; |
| 586 | 605 | } |
| 587 | 606 | } |
| 588 | 607 | |
| @@ -590,12 +609,12 @@ | ||
| 590 | 609 | p->azParam = (char **)&p[1]; |
| 591 | 610 | p->anParam = (int *)&p->azParam[nParam]; |
| 592 | 611 | p->azDefault = (char **)&p->anParam[nParam]; |
| 593 | 612 | p->anDefault = (int *)&p->azDefault[nParam]; |
| 594 | 613 | 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]); | |
| 597 | 616 | zSpace = &p->zProgram[p->nProgram]; |
| 598 | 617 | |
| 599 | 618 | for(i=0; i<nParam; i++){ |
| 600 | 619 | char **az; |
| 601 | 620 | int *an; |
| @@ -672,11 +691,12 @@ | ||
| 672 | 691 | int *argl |
| 673 | 692 | ){ |
| 674 | 693 | if( argc!=3 ){ |
| 675 | 694 | return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); |
| 676 | 695 | } |
| 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])); | |
| 678 | 698 | } |
| 679 | 699 | |
| 680 | 700 | /* |
| 681 | 701 | ** TH Syntax: |
| 682 | 702 | ** |
| @@ -746,13 +766,13 @@ | ||
| 746 | 766 | if( argc!=4 ){ |
| 747 | 767 | return Th_WrongNumArgs(interp, "string compare str1 str2"); |
| 748 | 768 | } |
| 749 | 769 | |
| 750 | 770 | zLeft = argv[2]; |
| 751 | - nLeft = argl[2]; | |
| 771 | + nLeft = TH1_LEN(argl[2]); | |
| 752 | 772 | zRight = argv[3]; |
| 753 | - nRight = argl[3]; | |
| 773 | + nRight = TH1_LEN(argl[3]); | |
| 754 | 774 | |
| 755 | 775 | for(i=0; iRes==0 && i<nLeft && i<nRight; i++){ |
| 756 | 776 | iRes = zLeft[i]-zRight[i]; |
| 757 | 777 | } |
| 758 | 778 | if( iRes==0 ){ |
| @@ -779,12 +799,12 @@ | ||
| 779 | 799 | |
| 780 | 800 | if( argc!=4 ){ |
| 781 | 801 | return Th_WrongNumArgs(interp, "string first needle haystack"); |
| 782 | 802 | } |
| 783 | 803 | |
| 784 | - nNeedle = argl[2]; | |
| 785 | - nHaystack = argl[3]; | |
| 804 | + nNeedle = TH1_LEN(argl[2]); | |
| 805 | + nHaystack = TH1_LEN(argl[3]); | |
| 786 | 806 | |
| 787 | 807 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 788 | 808 | const char *zNeedle = argv[2]; |
| 789 | 809 | const char *zHaystack = argv[3]; |
| 790 | 810 | int i; |
| @@ -812,20 +832,22 @@ | ||
| 812 | 832 | |
| 813 | 833 | if( argc!=4 ){ |
| 814 | 834 | return Th_WrongNumArgs(interp, "string index string index"); |
| 815 | 835 | } |
| 816 | 836 | |
| 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; | |
| 819 | 839 | }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ |
| 820 | 840 | Th_ErrorMessage( |
| 821 | 841 | interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); |
| 822 | 842 | return TH_ERROR; |
| 823 | 843 | } |
| 824 | 844 | |
| 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); | |
| 827 | 849 | }else{ |
| 828 | 850 | return Th_SetResult(interp, 0, 0); |
| 829 | 851 | } |
| 830 | 852 | } |
| 831 | 853 | |
| @@ -838,41 +860,44 @@ | ||
| 838 | 860 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 839 | 861 | ){ |
| 840 | 862 | if( argc!=4 ){ |
| 841 | 863 | return Th_WrongNumArgs(interp, "string is class string"); |
| 842 | 864 | } |
| 843 | - if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){ | |
| 865 | + if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){ | |
| 844 | 866 | int i; |
| 845 | 867 | int iRes = 1; |
| 846 | 868 | |
| 847 | - for(i=0; i<argl[3]; i++){ | |
| 869 | + for(i=0; i<TH1_LEN(argl[3]); i++){ | |
| 848 | 870 | if( !th_isalnum(argv[3][i]) ){ |
| 849 | 871 | iRes = 0; |
| 850 | 872 | } |
| 851 | 873 | } |
| 852 | 874 | |
| 853 | 875 | 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) ){ | |
| 855 | 877 | double fVal; |
| 856 | 878 | if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){ |
| 857 | 879 | return Th_SetResultInt(interp, 1); |
| 858 | 880 | } |
| 859 | 881 | 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) ){ | |
| 861 | 883 | int iVal; |
| 862 | 884 | if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){ |
| 863 | 885 | return Th_SetResultInt(interp, 1); |
| 864 | 886 | } |
| 865 | 887 | 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) ){ | |
| 867 | 889 | if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){ |
| 868 | 890 | return Th_SetResultInt(interp, 1); |
| 869 | 891 | } |
| 870 | 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])); | |
| 871 | 895 | }else{ |
| 872 | 896 | 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])); | |
| 874 | 899 | return TH_ERROR; |
| 875 | 900 | } |
| 876 | 901 | } |
| 877 | 902 | |
| 878 | 903 | /* |
| @@ -889,12 +914,12 @@ | ||
| 889 | 914 | |
| 890 | 915 | if( argc!=4 ){ |
| 891 | 916 | return Th_WrongNumArgs(interp, "string last needle haystack"); |
| 892 | 917 | } |
| 893 | 918 | |
| 894 | - nNeedle = argl[2]; | |
| 895 | - nHaystack = argl[3]; | |
| 919 | + nNeedle = TH1_LEN(argl[2]); | |
| 920 | + nHaystack = TH1_LEN(argl[3]); | |
| 896 | 921 | |
| 897 | 922 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 898 | 923 | const char *zNeedle = argv[2]; |
| 899 | 924 | const char *zHaystack = argv[3]; |
| 900 | 925 | int i; |
| @@ -919,11 +944,11 @@ | ||
| 919 | 944 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 920 | 945 | ){ |
| 921 | 946 | if( argc!=3 ){ |
| 922 | 947 | return Th_WrongNumArgs(interp, "string length string"); |
| 923 | 948 | } |
| 924 | - return Th_SetResultInt(interp, argl[2]); | |
| 949 | + return Th_SetResultInt(interp, TH1_LEN(argl[2])); | |
| 925 | 950 | } |
| 926 | 951 | |
| 927 | 952 | /* |
| 928 | 953 | ** TH Syntax: |
| 929 | 954 | ** |
| @@ -938,12 +963,12 @@ | ||
| 938 | 963 | char *zPat, *zStr; |
| 939 | 964 | int rc; |
| 940 | 965 | if( argc!=4 ){ |
| 941 | 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 942 | 967 | } |
| 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])); | |
| 945 | 970 | rc = sqlite3_strglob(zPat,zStr); |
| 946 | 971 | fossil_free(zPat); |
| 947 | 972 | fossil_free(zStr); |
| 948 | 973 | return Th_SetResultInt(interp, !rc); |
| 949 | 974 | } |
| @@ -956,31 +981,34 @@ | ||
| 956 | 981 | static int string_range_command( |
| 957 | 982 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 958 | 983 | ){ |
| 959 | 984 | int iStart; |
| 960 | 985 | int iEnd; |
| 986 | + int sz; | |
| 961 | 987 | |
| 962 | 988 | if( argc!=5 ){ |
| 963 | 989 | return Th_WrongNumArgs(interp, "string range string first last"); |
| 964 | 990 | } |
| 965 | 991 | |
| 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]); | |
| 968 | 994 | }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ |
| 969 | 995 | 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])); | |
| 971 | 997 | return TH_ERROR; |
| 972 | 998 | } |
| 973 | 999 | if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ |
| 974 | 1000 | return TH_ERROR; |
| 975 | 1001 | } |
| 976 | 1002 | |
| 977 | 1003 | 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; | |
| 979 | 1005 | if( iStart>iEnd ) iEnd = iStart-1; |
| 1006 | + sz = iEnd - iStart + 1; | |
| 1007 | + TH1_XFER_TAINT(sz, argl[2]); | |
| 980 | 1008 | |
| 981 | - return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1); | |
| 1009 | + return Th_SetResult(interp, &argv[2][iStart], sz); | |
| 982 | 1010 | } |
| 983 | 1011 | |
| 984 | 1012 | /* |
| 985 | 1013 | ** TH Syntax: |
| 986 | 1014 | ** |
| @@ -989,27 +1017,33 @@ | ||
| 989 | 1017 | static int string_repeat_command( |
| 990 | 1018 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 991 | 1019 | ){ |
| 992 | 1020 | int n; |
| 993 | 1021 | int i; |
| 994 | - int nByte; | |
| 1022 | + int sz; | |
| 1023 | + long long int nByte; | |
| 995 | 1024 | char *zByte; |
| 996 | 1025 | |
| 997 | 1026 | if( argc!=4 ){ |
| 998 | 1027 | return Th_WrongNumArgs(interp, "string repeat string n"); |
| 999 | 1028 | } |
| 1000 | 1029 | if( Th_ToInt(interp, argv[3], argl[3], &n) ){ |
| 1001 | 1030 | return TH_ERROR; |
| 1002 | 1031 | } |
| 1003 | 1032 | |
| 1004 | - nByte = argl[2] * n; | |
| 1033 | + nByte = n; | |
| 1034 | + sz = TH1_LEN(argl[2]); | |
| 1035 | + nByte *= sz; | |
| 1036 | + TH1_SIZECHECK(nByte+1); | |
| 1005 | 1037 | 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); | |
| 1008 | 1040 | } |
| 1009 | 1041 | |
| 1010 | - Th_SetResult(interp, zByte, nByte); | |
| 1042 | + n = nByte; | |
| 1043 | + TH1_XFER_TAINT(n, argl[2]); | |
| 1044 | + Th_SetResult(interp, zByte, n); | |
| 1011 | 1045 | Th_Free(interp, zByte); |
| 1012 | 1046 | return TH_OK; |
| 1013 | 1047 | } |
| 1014 | 1048 | |
| 1015 | 1049 | /* |
| @@ -1027,17 +1061,18 @@ | ||
| 1027 | 1061 | |
| 1028 | 1062 | if( argc!=3 ){ |
| 1029 | 1063 | return Th_WrongNumArgs(interp, "string trim string"); |
| 1030 | 1064 | } |
| 1031 | 1065 | 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' ){ | |
| 1034 | 1068 | while( n && th_isspace(z[0]) ){ z++; n--; } |
| 1035 | 1069 | } |
| 1036 | - if( argl[1]<5 || argv[1][4]=='r' ){ | |
| 1070 | + if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){ | |
| 1037 | 1071 | while( n && th_isspace(z[n-1]) ){ n--; } |
| 1038 | 1072 | } |
| 1073 | + TH1_XFER_TAINT(n, argl[2]); | |
| 1039 | 1074 | Th_SetResult(interp, z, n); |
| 1040 | 1075 | return TH_OK; |
| 1041 | 1076 | } |
| 1042 | 1077 | |
| 1043 | 1078 | /* |
| @@ -1051,11 +1086,11 @@ | ||
| 1051 | 1086 | int rc; |
| 1052 | 1087 | |
| 1053 | 1088 | if( argc!=3 ){ |
| 1054 | 1089 | return Th_WrongNumArgs(interp, "info exists var"); |
| 1055 | 1090 | } |
| 1056 | - rc = Th_ExistsVar(interp, argv[2], argl[2]); | |
| 1091 | + rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2])); | |
| 1057 | 1092 | Th_SetResultInt(interp, rc); |
| 1058 | 1093 | return TH_OK; |
| 1059 | 1094 | } |
| 1060 | 1095 | |
| 1061 | 1096 | /* |
| @@ -1117,11 +1152,11 @@ | ||
| 1117 | 1152 | int rc; |
| 1118 | 1153 | |
| 1119 | 1154 | if( argc!=3 ){ |
| 1120 | 1155 | return Th_WrongNumArgs(interp, "array exists var"); |
| 1121 | 1156 | } |
| 1122 | - rc = Th_ExistsArrayVar(interp, argv[2], argl[2]); | |
| 1157 | + rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2])); | |
| 1123 | 1158 | Th_SetResultInt(interp, rc); |
| 1124 | 1159 | return TH_OK; |
| 1125 | 1160 | } |
| 1126 | 1161 | |
| 1127 | 1162 | /* |
| @@ -1137,11 +1172,11 @@ | ||
| 1137 | 1172 | int nElem = 0; |
| 1138 | 1173 | |
| 1139 | 1174 | if( argc!=3 ){ |
| 1140 | 1175 | return Th_WrongNumArgs(interp, "array names varname"); |
| 1141 | 1176 | } |
| 1142 | - rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem); | |
| 1177 | + rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem); | |
| 1143 | 1178 | if( rc!=TH_OK ){ |
| 1144 | 1179 | return rc; |
| 1145 | 1180 | } |
| 1146 | 1181 | Th_SetResult(interp, zElem, nElem); |
| 1147 | 1182 | if( zElem ) Th_Free(interp, zElem); |
| @@ -1161,11 +1196,11 @@ | ||
| 1161 | 1196 | int *argl |
| 1162 | 1197 | ){ |
| 1163 | 1198 | if( argc!=2 ){ |
| 1164 | 1199 | return Th_WrongNumArgs(interp, "unset var"); |
| 1165 | 1200 | } |
| 1166 | - return Th_UnsetVar(interp, argv[1], argl[1]); | |
| 1201 | + return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1])); | |
| 1167 | 1202 | } |
| 1168 | 1203 | |
| 1169 | 1204 | int Th_CallSubCommand( |
| 1170 | 1205 | Th_Interp *interp, |
| 1171 | 1206 | void *ctx, |
| @@ -1176,19 +1211,22 @@ | ||
| 1176 | 1211 | ){ |
| 1177 | 1212 | if( argc>1 ){ |
| 1178 | 1213 | int i; |
| 1179 | 1214 | for(i=0; aSub[i].zName; i++){ |
| 1180 | 1215 | 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])) ){ | |
| 1182 | 1218 | return aSub[i].xProc(interp, ctx, argc, argv, argl); |
| 1183 | 1219 | } |
| 1184 | 1220 | } |
| 1185 | 1221 | } |
| 1186 | 1222 | 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])); | |
| 1188 | 1225 | }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])); | |
| 1190 | 1228 | } |
| 1191 | 1229 | return TH_ERROR; |
| 1192 | 1230 | } |
| 1193 | 1231 | |
| 1194 | 1232 | /* |
| @@ -1319,11 +1357,11 @@ | ||
| 1319 | 1357 | int iFrame = -1; |
| 1320 | 1358 | |
| 1321 | 1359 | if( argc!=2 && argc!=3 ){ |
| 1322 | 1360 | return Th_WrongNumArgs(interp, "uplevel ?level? script..."); |
| 1323 | 1361 | } |
| 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) ){ | |
| 1325 | 1363 | return TH_ERROR; |
| 1326 | 1364 | } |
| 1327 | 1365 | return Th_Eval(interp, iFrame, argv[argc-1], -1); |
| 1328 | 1366 | } |
| 1329 | 1367 | |
| @@ -1342,19 +1380,20 @@ | ||
| 1342 | 1380 | int iVar = 1; |
| 1343 | 1381 | int iFrame = -1; |
| 1344 | 1382 | int rc = TH_OK; |
| 1345 | 1383 | int i; |
| 1346 | 1384 | |
| 1347 | - if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){ | |
| 1385 | + if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){ | |
| 1348 | 1386 | iVar++; |
| 1349 | 1387 | } |
| 1350 | 1388 | if( argc==iVar || (argc-iVar)%2 ){ |
| 1351 | 1389 | return Th_WrongNumArgs(interp, |
| 1352 | 1390 | "upvar frame othervar myvar ?othervar myvar...?"); |
| 1353 | 1391 | } |
| 1354 | 1392 | 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])); | |
| 1356 | 1395 | } |
| 1357 | 1396 | return rc; |
| 1358 | 1397 | } |
| 1359 | 1398 | |
| 1360 | 1399 | /* |
| 1361 | 1400 |
| --- 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 @@ | ||
| 31 | 31 | #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ |
| 32 | 32 | #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ |
| 33 | 33 | #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ |
| 34 | 34 | #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ |
| 35 | 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. */ | |
| 36 | +#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */ | |
| 39 | 37 | |
| 40 | 38 | /* |
| 41 | 39 | ** Useful and/or "well-known" combinations of flag values. |
| 42 | 40 | */ |
| 43 | 41 | #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ |
| @@ -262,11 +260,11 @@ | ||
| 262 | 260 | ){ |
| 263 | 261 | char *zOut; |
| 264 | 262 | if( argc!=2 ){ |
| 265 | 263 | return Th_WrongNumArgs(interp, "httpize STRING"); |
| 266 | 264 | } |
| 267 | - zOut = httpize((char*)argv[1], argl[1]); | |
| 265 | + zOut = httpize((char*)argv[1], TH1_LEN(argl[1])); | |
| 268 | 266 | Th_SetResult(interp, zOut, -1); |
| 269 | 267 | free(zOut); |
| 270 | 268 | return TH_OK; |
| 271 | 269 | } |
| 272 | 270 | |
| @@ -291,51 +289,12 @@ | ||
| 291 | 289 | if( argc<2 || argc>3 ){ |
| 292 | 290 | return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); |
| 293 | 291 | } |
| 294 | 292 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); |
| 295 | 293 | 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); | |
| 337 | 296 | } |
| 338 | 297 | return rc; |
| 339 | 298 | } |
| 340 | 299 | |
| 341 | 300 | /* |
| @@ -375,25 +334,25 @@ | ||
| 375 | 334 | |
| 376 | 335 | /* |
| 377 | 336 | ** Send text to the appropriate output: If pOut is not NULL, it is |
| 378 | 337 | ** appended there, else to the console or to the CGI reply buffer. |
| 379 | 338 | ** 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. | |
| 382 | 340 | ** |
| 383 | 341 | ** If pOut is NULL and the global pThOut is not then that blob |
| 384 | 342 | ** is used for output. |
| 385 | 343 | */ |
| 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){ | |
| 387 | 345 | if(0==pOut && pThOut!=0){ |
| 388 | 346 | pOut = pThOut; |
| 389 | 347 | } |
| 390 | - if(TH_INIT_NO_ENCODE & g.th1Flags){ | |
| 391 | - encode = 0; | |
| 392 | - } | |
| 393 | 348 | 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 | + } | |
| 395 | 354 | if( encode ){ |
| 396 | 355 | z = htmlize(z, n); |
| 397 | 356 | n = strlen(z); |
| 398 | 357 | } |
| 399 | 358 | if(pOut!=0){ |
| @@ -525,14 +484,22 @@ | ||
| 525 | 484 | void *pConvert, |
| 526 | 485 | int argc, |
| 527 | 486 | const char **argv, |
| 528 | 487 | int *argl |
| 529 | 488 | ){ |
| 489 | + int encode = *(unsigned int*)pConvert; | |
| 490 | + int n; | |
| 530 | 491 | if( argc!=2 ){ |
| 531 | 492 | return Th_WrongNumArgs(interp, "puts STRING"); |
| 532 | 493 | } |
| 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); | |
| 534 | 501 | return TH_OK; |
| 535 | 502 | } |
| 536 | 503 | |
| 537 | 504 | /* |
| 538 | 505 | ** TH1 command: redirect URL ?withMethod? |
| @@ -557,10 +524,15 @@ | ||
| 557 | 524 | } |
| 558 | 525 | if( argc==3 ){ |
| 559 | 526 | if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ |
| 560 | 527 | return TH_ERROR; |
| 561 | 528 | } |
| 529 | + } | |
| 530 | + if( TH1_TAINTED(argl[1]) | |
| 531 | + && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1]) | |
| 532 | + ){ | |
| 533 | + return TH_ERROR; | |
| 562 | 534 | } |
| 563 | 535 | if( withMethod ){ |
| 564 | 536 | cgi_redirect_with_method(argv[1]); |
| 565 | 537 | }else{ |
| 566 | 538 | cgi_redirect(argv[1]); |
| @@ -660,11 +632,11 @@ | ||
| 660 | 632 | int nValue = 0; |
| 661 | 633 | if( argc!=2 ){ |
| 662 | 634 | return Th_WrongNumArgs(interp, "markdown STRING"); |
| 663 | 635 | } |
| 664 | 636 | blob_zero(&src); |
| 665 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 637 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 666 | 638 | blob_zero(&title); blob_zero(&body); |
| 667 | 639 | markdown_to_html(&src, &title, &body); |
| 668 | 640 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); |
| 669 | 641 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); |
| 670 | 642 | Th_SetResult(interp, zValue, nValue); |
| @@ -690,11 +662,11 @@ | ||
| 690 | 662 | if( argc!=2 ){ |
| 691 | 663 | return Th_WrongNumArgs(interp, "wiki STRING"); |
| 692 | 664 | } |
| 693 | 665 | if( enableOutput ){ |
| 694 | 666 | Blob src; |
| 695 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 667 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 696 | 668 | wiki_convert(&src, 0, flags); |
| 697 | 669 | blob_reset(&src); |
| 698 | 670 | } |
| 699 | 671 | return TH_OK; |
| 700 | 672 | } |
| @@ -735,11 +707,11 @@ | ||
| 735 | 707 | ){ |
| 736 | 708 | char *zOut; |
| 737 | 709 | if( argc!=2 ){ |
| 738 | 710 | return Th_WrongNumArgs(interp, "htmlize STRING"); |
| 739 | 711 | } |
| 740 | - zOut = htmlize((char*)argv[1], argl[1]); | |
| 712 | + zOut = htmlize((char*)argv[1], TH1_LEN(argl[1])); | |
| 741 | 713 | Th_SetResult(interp, zOut, -1); |
| 742 | 714 | free(zOut); |
| 743 | 715 | return TH_OK; |
| 744 | 716 | } |
| 745 | 717 | |
| @@ -757,11 +729,11 @@ | ||
| 757 | 729 | ){ |
| 758 | 730 | char *zOut; |
| 759 | 731 | if( argc!=2 ){ |
| 760 | 732 | return Th_WrongNumArgs(interp, "encode64 STRING"); |
| 761 | 733 | } |
| 762 | - zOut = encode64((char*)argv[1], argl[1]); | |
| 734 | + zOut = encode64((char*)argv[1], TH1_LEN(argl[1])); | |
| 763 | 735 | Th_SetResult(interp, zOut, -1); |
| 764 | 736 | free(zOut); |
| 765 | 737 | return TH_OK; |
| 766 | 738 | } |
| 767 | 739 | |
| @@ -778,11 +750,11 @@ | ||
| 778 | 750 | int argc, |
| 779 | 751 | const char **argv, |
| 780 | 752 | int *argl |
| 781 | 753 | ){ |
| 782 | 754 | 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 ){ | |
| 784 | 756 | zOut = db_text("??", "SELECT datetime('now',toLocal())"); |
| 785 | 757 | }else{ |
| 786 | 758 | zOut = db_text("??", "SELECT datetime('now')"); |
| 787 | 759 | } |
| 788 | 760 | Th_SetResult(interp, zOut, -1); |
| @@ -810,13 +782,13 @@ | ||
| 810 | 782 | if( argc<2 ){ |
| 811 | 783 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 812 | 784 | } |
| 813 | 785 | for(i=1; rc==1 && i<argc; i++){ |
| 814 | 786 | 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])); | |
| 816 | 788 | } |
| 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); | |
| 818 | 790 | } |
| 819 | 791 | if( g.thTrace ){ |
| 820 | 792 | Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); |
| 821 | 793 | Th_Free(interp, zCapList); |
| 822 | 794 | } |
| @@ -858,11 +830,11 @@ | ||
| 858 | 830 | int i; |
| 859 | 831 | |
| 860 | 832 | if( argc!=2 ){ |
| 861 | 833 | return Th_WrongNumArgs(interp, "capexpr EXPR"); |
| 862 | 834 | } |
| 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); | |
| 864 | 836 | if( rc ) return rc; |
| 865 | 837 | rc = 0; |
| 866 | 838 | for(i=0; i<nCap; i++){ |
| 867 | 839 | if( azCap[i][0]=='!' ){ |
| 868 | 840 | rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); |
| @@ -921,11 +893,12 @@ | ||
| 921 | 893 | if( argc<2 ){ |
| 922 | 894 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 923 | 895 | } |
| 924 | 896 | for(i=1; i<argc && rc; i++){ |
| 925 | 897 | 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++){ | |
| 927 | 900 | switch( argv[i][j] ){ |
| 928 | 901 | case 'c': match |= searchCap & SRCH_CKIN; break; |
| 929 | 902 | case 'd': match |= searchCap & SRCH_DOC; break; |
| 930 | 903 | case 't': match |= searchCap & SRCH_TKT; break; |
| 931 | 904 | case 'w': match |= searchCap & SRCH_WIKI; break; |
| @@ -932,11 +905,11 @@ | ||
| 932 | 905 | } |
| 933 | 906 | } |
| 934 | 907 | if( !match ) rc = 0; |
| 935 | 908 | } |
| 936 | 909 | 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); | |
| 938 | 911 | } |
| 939 | 912 | Th_SetResultInt(interp, rc); |
| 940 | 913 | return TH_OK; |
| 941 | 914 | } |
| 942 | 915 | |
| @@ -1051,11 +1024,11 @@ | ||
| 1051 | 1024 | #endif |
| 1052 | 1025 | else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ |
| 1053 | 1026 | rc = 1; |
| 1054 | 1027 | } |
| 1055 | 1028 | 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); | |
| 1057 | 1030 | } |
| 1058 | 1031 | Th_SetResultInt(interp, rc); |
| 1059 | 1032 | return TH_OK; |
| 1060 | 1033 | } |
| 1061 | 1034 | |
| @@ -1104,18 +1077,20 @@ | ||
| 1104 | 1077 | const char **argv, |
| 1105 | 1078 | int *argl |
| 1106 | 1079 | ){ |
| 1107 | 1080 | int rc = 0; |
| 1108 | 1081 | int i; |
| 1082 | + int nn; | |
| 1109 | 1083 | if( argc!=2 ){ |
| 1110 | 1084 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 1111 | 1085 | } |
| 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++){ | |
| 1113 | 1088 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 1114 | 1089 | } |
| 1115 | 1090 | 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); | |
| 1117 | 1092 | } |
| 1118 | 1093 | Th_SetResultInt(interp, rc); |
| 1119 | 1094 | return TH_OK; |
| 1120 | 1095 | } |
| 1121 | 1096 | |
| @@ -1140,22 +1115,23 @@ | ||
| 1140 | 1115 | return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); |
| 1141 | 1116 | } |
| 1142 | 1117 | if( enableOutput ){ |
| 1143 | 1118 | int height; |
| 1144 | 1119 | Blob name; |
| 1145 | - int nValue; | |
| 1120 | + int nValue = 0; | |
| 1146 | 1121 | const char *zValue; |
| 1147 | 1122 | char *z, *zH; |
| 1148 | 1123 | int nElem; |
| 1149 | 1124 | int *aszElem; |
| 1150 | 1125 | char **azElem; |
| 1151 | 1126 | int i; |
| 1152 | 1127 | |
| 1153 | 1128 | 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])); | |
| 1156 | 1131 | zValue = Th_Fetch(blob_str(&name), &nValue); |
| 1132 | + nValue = TH1_LEN(nValue); | |
| 1157 | 1133 | zH = htmlize(blob_buffer(&name), blob_size(&name)); |
| 1158 | 1134 | z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); |
| 1159 | 1135 | free(zH); |
| 1160 | 1136 | sendText(0,z, -1, 0); |
| 1161 | 1137 | free(z); |
| @@ -1247,11 +1223,11 @@ | ||
| 1247 | 1223 | return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); |
| 1248 | 1224 | } |
| 1249 | 1225 | if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; |
| 1250 | 1226 | if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; |
| 1251 | 1227 | z = argv[1]; |
| 1252 | - size = argl[1]; | |
| 1228 | + size = TH1_LEN(argl[1]); | |
| 1253 | 1229 | for(n=1, i=0; i<size; i++){ |
| 1254 | 1230 | if( z[i]=='\n' ){ |
| 1255 | 1231 | n++; |
| 1256 | 1232 | if( n>=iMax ) break; |
| 1257 | 1233 | } |
| @@ -1407,11 +1383,12 @@ | ||
| 1407 | 1383 | return TH_OK; |
| 1408 | 1384 | }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ |
| 1409 | 1385 | Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); |
| 1410 | 1386 | return TH_OK; |
| 1411 | 1387 | }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])); | |
| 1413 | 1390 | return TH_ERROR; |
| 1414 | 1391 | } |
| 1415 | 1392 | } |
| 1416 | 1393 | |
| 1417 | 1394 | /* |
| @@ -1426,17 +1403,21 @@ | ||
| 1426 | 1403 | int argc, |
| 1427 | 1404 | const char **argv, |
| 1428 | 1405 | int *argl |
| 1429 | 1406 | ){ |
| 1430 | 1407 | const char *zDefault = 0; |
| 1408 | + const char *zVal; | |
| 1409 | + int sz; | |
| 1431 | 1410 | if( argc!=2 && argc!=3 ){ |
| 1432 | 1411 | return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); |
| 1433 | 1412 | } |
| 1434 | 1413 | if( argc==3 ){ |
| 1435 | 1414 | zDefault = argv[2]; |
| 1436 | 1415 | } |
| 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)); | |
| 1438 | 1419 | return TH_OK; |
| 1439 | 1420 | } |
| 1440 | 1421 | |
| 1441 | 1422 | /* |
| 1442 | 1423 | ** TH1 command: setParameter NAME VALUE |
| @@ -1848,10 +1829,47 @@ | ||
| 1848 | 1829 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); |
| 1849 | 1830 | Th_SetResult(interp, zUTime, -1); |
| 1850 | 1831 | return TH_OK; |
| 1851 | 1832 | } |
| 1852 | 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 | +} | |
| 1853 | 1871 | |
| 1854 | 1872 | /* |
| 1855 | 1873 | ** TH1 command: randhex N |
| 1856 | 1874 | ** |
| 1857 | 1875 | ** Return N*2 random hexadecimal digits with N<50. If N is omitted, |
| @@ -1923,11 +1941,13 @@ | ||
| 1923 | 1941 | int res = TH_OK; |
| 1924 | 1942 | int nVar; |
| 1925 | 1943 | char *zErr = 0; |
| 1926 | 1944 | int noComplain = 0; |
| 1927 | 1945 | |
| 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 | + ){ | |
| 1929 | 1949 | argc--; |
| 1930 | 1950 | argv++; |
| 1931 | 1951 | argl++; |
| 1932 | 1952 | noComplain = 1; |
| 1933 | 1953 | } |
| @@ -1939,15 +1959,22 @@ | ||
| 1939 | 1959 | Th_ErrorMessage(interp, "database is not open", 0, 0); |
| 1940 | 1960 | return TH_ERROR; |
| 1941 | 1961 | } |
| 1942 | 1962 | zSql = argv[1]; |
| 1943 | 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 | + | |
| 1944 | 1971 | while( res==TH_OK && nSql>0 ){ |
| 1945 | 1972 | zErr = 0; |
| 1946 | 1973 | report_restrict_sql(&zErr); |
| 1947 | 1974 | 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); | |
| 1949 | 1976 | g.dbIgnoreErrors--; |
| 1950 | 1977 | report_unrestrict_sql(); |
| 1951 | 1978 | if( rc!=0 || zErr!=0 ){ |
| 1952 | 1979 | if( noComplain ) return TH_OK; |
| 1953 | 1980 | Th_ErrorMessage(interp, "SQL error: ", |
| @@ -1964,31 +1991,31 @@ | ||
| 1964 | 1991 | int szVar = zVar ? th_strlen(zVar) : 0; |
| 1965 | 1992 | if( szVar>1 && zVar[0]=='$' |
| 1966 | 1993 | && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ |
| 1967 | 1994 | int nVal; |
| 1968 | 1995 | 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); | |
| 1970 | 1997 | } |
| 1971 | 1998 | } |
| 1972 | 1999 | while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ |
| 1973 | 2000 | int nCol = sqlite3_column_count(pStmt); |
| 1974 | 2001 | for(i=0; i<nCol; i++){ |
| 1975 | 2002 | const char *zCol = sqlite3_column_name(pStmt, i); |
| 1976 | 2003 | int szCol = th_strlen(zCol); |
| 1977 | 2004 | const char *zVal = (const char*)sqlite3_column_text(pStmt, i); |
| 1978 | 2005 | 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)); | |
| 1980 | 2007 | } |
| 1981 | 2008 | 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]); | |
| 1983 | 2010 | } |
| 1984 | - res = Th_Eval(interp, 0, argv[2], argl[2]); | |
| 2011 | + res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2])); | |
| 1985 | 2012 | if( g.thTrace ){ |
| 1986 | 2013 | int nTrRes; |
| 1987 | 2014 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 1988 | 2015 | Th_Trace("[query_eval] => %h {%#h}<br>\n", |
| 1989 | - Th_ReturnCodeName(res, 0), nTrRes, zTrRes); | |
| 2016 | + Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes); | |
| 1990 | 2017 | } |
| 1991 | 2018 | if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; |
| 1992 | 2019 | } |
| 1993 | 2020 | rc = sqlite3_finalize(pStmt); |
| 1994 | 2021 | if( rc!=SQLITE_OK ){ |
| @@ -2038,11 +2065,11 @@ | ||
| 2038 | 2065 | Th_SetResult(interp, 0, 0); |
| 2039 | 2066 | rc = TH_OK; |
| 2040 | 2067 | } |
| 2041 | 2068 | if( g.thTrace ){ |
| 2042 | 2069 | Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", |
| 2043 | - argl[nArg], argv[nArg], rc); | |
| 2070 | + TH1_LEN(argl[nArg]), argv[nArg], rc); | |
| 2044 | 2071 | } |
| 2045 | 2072 | return rc; |
| 2046 | 2073 | } |
| 2047 | 2074 | |
| 2048 | 2075 | /* |
| @@ -2121,11 +2148,11 @@ | ||
| 2121 | 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2122 | 2149 | } |
| 2123 | 2150 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2124 | 2151 | if( !zErr ){ |
| 2125 | 2152 | 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]))); | |
| 2127 | 2154 | rc = TH_OK; |
| 2128 | 2155 | }else{ |
| 2129 | 2156 | Th_SetResult(interp, zErr, -1); |
| 2130 | 2157 | rc = TH_ERROR; |
| 2131 | 2158 | } |
| @@ -2160,11 +2187,11 @@ | ||
| 2160 | 2187 | UrlData urlData; |
| 2161 | 2188 | |
| 2162 | 2189 | if( argc<2 || argc>5 ){ |
| 2163 | 2190 | return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); |
| 2164 | 2191 | } |
| 2165 | - if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ | |
| 2192 | + if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){ | |
| 2166 | 2193 | fAsynchronous = 1; nArg++; |
| 2167 | 2194 | } |
| 2168 | 2195 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2169 | 2196 | if( nArg+1!=argc && nArg+2!=argc ){ |
| 2170 | 2197 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| @@ -2189,11 +2216,11 @@ | ||
| 2189 | 2216 | return TH_ERROR; |
| 2190 | 2217 | } |
| 2191 | 2218 | re_free(pRe); |
| 2192 | 2219 | blob_zero(&payload); |
| 2193 | 2220 | 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])); | |
| 2195 | 2222 | zType = "POST"; |
| 2196 | 2223 | }else{ |
| 2197 | 2224 | zType = "GET"; |
| 2198 | 2225 | } |
| 2199 | 2226 | if( fAsynchronous ){ |
| @@ -2268,11 +2295,11 @@ | ||
| 2268 | 2295 | if( argc!=2 ){ |
| 2269 | 2296 | return Th_WrongNumArgs(interp, "captureTh1 STRING"); |
| 2270 | 2297 | } |
| 2271 | 2298 | pOrig = Th_SetOutputBlob(&out); |
| 2272 | 2299 | zStr = argv[1]; |
| 2273 | - nStr = argl[1]; | |
| 2300 | + nStr = TH1_LEN(argl[1]); | |
| 2274 | 2301 | rc = Th_Eval(g.interp, 0, zStr, nStr); |
| 2275 | 2302 | Th_SetOutputBlob(pOrig); |
| 2276 | 2303 | if(0==rc){ |
| 2277 | 2304 | Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); |
| 2278 | 2305 | } |
| @@ -2356,11 +2383,10 @@ | ||
| 2356 | 2383 | {"copybtn", copybtnCmd, 0}, |
| 2357 | 2384 | {"date", dateCmd, 0}, |
| 2358 | 2385 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 2359 | 2386 | {"defHeader", defHeaderCmd, 0}, |
| 2360 | 2387 | {"dir", dirCmd, 0}, |
| 2361 | - {"enable_htmlify",enableHtmlifyCmd, 0}, | |
| 2362 | 2388 | {"enable_output", enableOutputCmd, 0}, |
| 2363 | 2389 | {"encode64", encode64Cmd, 0}, |
| 2364 | 2390 | {"getParameter", getParameterCmd, 0}, |
| 2365 | 2391 | {"glob_match", globMatchCmd, 0}, |
| 2366 | 2392 | {"globalState", globalStateCmd, 0}, |
| @@ -2387,13 +2413,15 @@ | ||
| 2387 | 2413 | {"setting", settingCmd, 0}, |
| 2388 | 2414 | {"styleFooter", styleFooterCmd, 0}, |
| 2389 | 2415 | {"styleHeader", styleHeaderCmd, 0}, |
| 2390 | 2416 | {"styleScript", styleScriptCmd, 0}, |
| 2391 | 2417 | {"submenu", submenuCmd, 0}, |
| 2418 | + {"taint", taintCmd, 0}, | |
| 2392 | 2419 | {"tclReady", tclReadyCmd, 0}, |
| 2393 | 2420 | {"trace", traceCmd, 0}, |
| 2394 | 2421 | {"stime", stimeCmd, 0}, |
| 2422 | + {"untaint", untaintCmd, 0}, | |
| 2395 | 2423 | {"unversioned", unversionedCmd, 0}, |
| 2396 | 2424 | {"utime", utimeCmd, 0}, |
| 2397 | 2425 | {"verifyCsrf", verifyCsrfCmd, 0}, |
| 2398 | 2426 | {"verifyLogin", verifyLoginCmd, 0}, |
| 2399 | 2427 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -2494,10 +2522,26 @@ | ||
| 2494 | 2522 | Th_Trace("set %h {%h}<br>\n", zName, zValue); |
| 2495 | 2523 | } |
| 2496 | 2524 | Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); |
| 2497 | 2525 | } |
| 2498 | 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 | +} | |
| 2499 | 2543 | |
| 2500 | 2544 | /* |
| 2501 | 2545 | ** Appends an element to a TH1 list value. This function is called by the |
| 2502 | 2546 | ** transfer subsystem; therefore, it must be very careful to avoid doing |
| 2503 | 2547 | ** any unnecessary work. To that end, the TH1 subsystem will not be called |
| @@ -2680,10 +2724,11 @@ | ||
| 2680 | 2724 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2681 | 2725 | /* |
| 2682 | 2726 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2683 | 2727 | ** command hook handler as that is not actually an error condition. |
| 2684 | 2728 | */ |
| 2729 | + nResult = TH1_LEN(nResult); | |
| 2685 | 2730 | if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ |
| 2686 | 2731 | sendError(0,zResult, nResult, 0); |
| 2687 | 2732 | }else{ |
| 2688 | 2733 | /* |
| 2689 | 2734 | ** There is no command hook handler "installed". This situation |
| @@ -2767,10 +2812,11 @@ | ||
| 2767 | 2812 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2768 | 2813 | /* |
| 2769 | 2814 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2770 | 2815 | ** webpage hook handler as that is not actually an error condition. |
| 2771 | 2816 | */ |
| 2817 | + nResult = TH1_LEN(nResult); | |
| 2772 | 2818 | if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ |
| 2773 | 2819 | sendError(0,zResult, nResult, 1); |
| 2774 | 2820 | }else{ |
| 2775 | 2821 | /* |
| 2776 | 2822 | ** There is no webpage hook handler "installed". This situation |
| @@ -2894,11 +2940,16 @@ | ||
| 2894 | 2940 | } |
| 2895 | 2941 | rc = Th_GetVar(g.interp, (char*)zVar, nVar); |
| 2896 | 2942 | z += i+1+n; |
| 2897 | 2943 | i = 0; |
| 2898 | 2944 | 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 | + } | |
| 2900 | 2951 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 2901 | 2952 | sendText(pOut,z, i, 0); |
| 2902 | 2953 | z += i+5; |
| 2903 | 2954 | for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} |
| 2904 | 2955 | if( g.thTrace ){ |
| @@ -2907,11 +2958,11 @@ | ||
| 2907 | 2958 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 2908 | 2959 | if( g.thTrace ){ |
| 2909 | 2960 | int nTrRes; |
| 2910 | 2961 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2911 | 2962 | Th_Trace("[render_eval] => %h {%#h}<br>\n", |
| 2912 | - Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); | |
| 2963 | + Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes); | |
| 2913 | 2964 | } |
| 2914 | 2965 | if( rc!=TH_OK ) break; |
| 2915 | 2966 | z += i; |
| 2916 | 2967 | if( z[0] ){ z += 6; } |
| 2917 | 2968 | i = 0; |
| @@ -2949,14 +3000,81 @@ | ||
| 2949 | 3000 | ** e.g. via the "render" script function binding, need to use the |
| 2950 | 3001 | ** pThOut blob in order to avoid out-of-order output if |
| 2951 | 3002 | ** Th_SetOutputBlob() has been called. If it has not been called, |
| 2952 | 3003 | ** pThOut will be 0, which will redirect the output to CGI/stdout, |
| 2953 | 3004 | ** 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. | |
| 2956 | 3006 | */; |
| 2957 | 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 | +} | |
| 2958 | 3076 | |
| 2959 | 3077 | /* |
| 2960 | 3078 | ** COMMAND: test-th-render |
| 2961 | 3079 | ** |
| 2962 | 3080 | ** Usage: %fossil test-th-render FILE |
| @@ -2992,10 +3110,11 @@ | ||
| 2992 | 3110 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 2993 | 3111 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 2994 | 3112 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 2995 | 3113 | g.useLocalauth = 1; |
| 2996 | 3114 | } |
| 3115 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 2997 | 3116 | verify_all_options(); |
| 2998 | 3117 | if( g.argc<3 ){ |
| 2999 | 3118 | usage("FILE"); |
| 3000 | 3119 | } |
| 3001 | 3120 | blob_zero(&in); |
| @@ -3044,10 +3163,11 @@ | ||
| 3044 | 3163 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3045 | 3164 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3046 | 3165 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3047 | 3166 | g.useLocalauth = 1; |
| 3048 | 3167 | } |
| 3168 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 3049 | 3169 | verify_all_options(); |
| 3050 | 3170 | if( g.argc!=3 ){ |
| 3051 | 3171 | usage("script"); |
| 3052 | 3172 | } |
| 3053 | 3173 | if(file_isfile(g.argv[2], ExtFILE)){ |
| 3054 | 3174 |
| --- 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 @@ | ||
| 31 | 31 | #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ |
| 32 | 32 | #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ |
| 33 | 33 | #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ |
| 34 | 34 | #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ |
| 35 | 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. */ | |
| 36 | +#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */ | |
| 39 | 37 | |
| 40 | 38 | /* |
| 41 | 39 | ** Useful and/or "well-known" combinations of flag values. |
| 42 | 40 | */ |
| 43 | 41 | #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ |
| @@ -262,11 +260,11 @@ | ||
| 262 | 260 | ){ |
| 263 | 261 | char *zOut; |
| 264 | 262 | if( argc!=2 ){ |
| 265 | 263 | return Th_WrongNumArgs(interp, "httpize STRING"); |
| 266 | 264 | } |
| 267 | - zOut = httpize((char*)argv[1], argl[1]); | |
| 265 | + zOut = httpize((char*)argv[1], TH1_LEN(argl[1])); | |
| 268 | 266 | Th_SetResult(interp, zOut, -1); |
| 269 | 267 | free(zOut); |
| 270 | 268 | return TH_OK; |
| 271 | 269 | } |
| 272 | 270 | |
| @@ -291,51 +289,12 @@ | ||
| 291 | 289 | if( argc<2 || argc>3 ){ |
| 292 | 290 | return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); |
| 293 | 291 | } |
| 294 | 292 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); |
| 295 | 293 | 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); | |
| 337 | 296 | } |
| 338 | 297 | return rc; |
| 339 | 298 | } |
| 340 | 299 | |
| 341 | 300 | /* |
| @@ -375,25 +334,25 @@ | ||
| 375 | 334 | |
| 376 | 335 | /* |
| 377 | 336 | ** Send text to the appropriate output: If pOut is not NULL, it is |
| 378 | 337 | ** appended there, else to the console or to the CGI reply buffer. |
| 379 | 338 | ** 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. | |
| 382 | 340 | ** |
| 383 | 341 | ** If pOut is NULL and the global pThOut is not then that blob |
| 384 | 342 | ** is used for output. |
| 385 | 343 | */ |
| 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){ | |
| 387 | 345 | if(0==pOut && pThOut!=0){ |
| 388 | 346 | pOut = pThOut; |
| 389 | 347 | } |
| 390 | - if(TH_INIT_NO_ENCODE & g.th1Flags){ | |
| 391 | - encode = 0; | |
| 392 | - } | |
| 393 | 348 | 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 | + } | |
| 395 | 354 | if( encode ){ |
| 396 | 355 | z = htmlize(z, n); |
| 397 | 356 | n = strlen(z); |
| 398 | 357 | } |
| 399 | 358 | if(pOut!=0){ |
| @@ -525,14 +484,22 @@ | ||
| 525 | 484 | void *pConvert, |
| 526 | 485 | int argc, |
| 527 | 486 | const char **argv, |
| 528 | 487 | int *argl |
| 529 | 488 | ){ |
| 489 | + int encode = *(unsigned int*)pConvert; | |
| 490 | + int n; | |
| 530 | 491 | if( argc!=2 ){ |
| 531 | 492 | return Th_WrongNumArgs(interp, "puts STRING"); |
| 532 | 493 | } |
| 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); | |
| 534 | 501 | return TH_OK; |
| 535 | 502 | } |
| 536 | 503 | |
| 537 | 504 | /* |
| 538 | 505 | ** TH1 command: redirect URL ?withMethod? |
| @@ -557,10 +524,15 @@ | ||
| 557 | 524 | } |
| 558 | 525 | if( argc==3 ){ |
| 559 | 526 | if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ |
| 560 | 527 | return TH_ERROR; |
| 561 | 528 | } |
| 529 | + } | |
| 530 | + if( TH1_TAINTED(argl[1]) | |
| 531 | + && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1]) | |
| 532 | + ){ | |
| 533 | + return TH_ERROR; | |
| 562 | 534 | } |
| 563 | 535 | if( withMethod ){ |
| 564 | 536 | cgi_redirect_with_method(argv[1]); |
| 565 | 537 | }else{ |
| 566 | 538 | cgi_redirect(argv[1]); |
| @@ -660,11 +632,11 @@ | ||
| 660 | 632 | int nValue = 0; |
| 661 | 633 | if( argc!=2 ){ |
| 662 | 634 | return Th_WrongNumArgs(interp, "markdown STRING"); |
| 663 | 635 | } |
| 664 | 636 | blob_zero(&src); |
| 665 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 637 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 666 | 638 | blob_zero(&title); blob_zero(&body); |
| 667 | 639 | markdown_to_html(&src, &title, &body); |
| 668 | 640 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); |
| 669 | 641 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); |
| 670 | 642 | Th_SetResult(interp, zValue, nValue); |
| @@ -690,11 +662,11 @@ | ||
| 690 | 662 | if( argc!=2 ){ |
| 691 | 663 | return Th_WrongNumArgs(interp, "wiki STRING"); |
| 692 | 664 | } |
| 693 | 665 | if( enableOutput ){ |
| 694 | 666 | Blob src; |
| 695 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 667 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 696 | 668 | wiki_convert(&src, 0, flags); |
| 697 | 669 | blob_reset(&src); |
| 698 | 670 | } |
| 699 | 671 | return TH_OK; |
| 700 | 672 | } |
| @@ -735,11 +707,11 @@ | ||
| 735 | 707 | ){ |
| 736 | 708 | char *zOut; |
| 737 | 709 | if( argc!=2 ){ |
| 738 | 710 | return Th_WrongNumArgs(interp, "htmlize STRING"); |
| 739 | 711 | } |
| 740 | - zOut = htmlize((char*)argv[1], argl[1]); | |
| 712 | + zOut = htmlize((char*)argv[1], TH1_LEN(argl[1])); | |
| 741 | 713 | Th_SetResult(interp, zOut, -1); |
| 742 | 714 | free(zOut); |
| 743 | 715 | return TH_OK; |
| 744 | 716 | } |
| 745 | 717 | |
| @@ -757,11 +729,11 @@ | ||
| 757 | 729 | ){ |
| 758 | 730 | char *zOut; |
| 759 | 731 | if( argc!=2 ){ |
| 760 | 732 | return Th_WrongNumArgs(interp, "encode64 STRING"); |
| 761 | 733 | } |
| 762 | - zOut = encode64((char*)argv[1], argl[1]); | |
| 734 | + zOut = encode64((char*)argv[1], TH1_LEN(argl[1])); | |
| 763 | 735 | Th_SetResult(interp, zOut, -1); |
| 764 | 736 | free(zOut); |
| 765 | 737 | return TH_OK; |
| 766 | 738 | } |
| 767 | 739 | |
| @@ -778,11 +750,11 @@ | ||
| 778 | 750 | int argc, |
| 779 | 751 | const char **argv, |
| 780 | 752 | int *argl |
| 781 | 753 | ){ |
| 782 | 754 | 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 ){ | |
| 784 | 756 | zOut = db_text("??", "SELECT datetime('now',toLocal())"); |
| 785 | 757 | }else{ |
| 786 | 758 | zOut = db_text("??", "SELECT datetime('now')"); |
| 787 | 759 | } |
| 788 | 760 | Th_SetResult(interp, zOut, -1); |
| @@ -810,13 +782,13 @@ | ||
| 810 | 782 | if( argc<2 ){ |
| 811 | 783 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 812 | 784 | } |
| 813 | 785 | for(i=1; rc==1 && i<argc; i++){ |
| 814 | 786 | 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])); | |
| 816 | 788 | } |
| 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); | |
| 818 | 790 | } |
| 819 | 791 | if( g.thTrace ){ |
| 820 | 792 | Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); |
| 821 | 793 | Th_Free(interp, zCapList); |
| 822 | 794 | } |
| @@ -858,11 +830,11 @@ | ||
| 858 | 830 | int i; |
| 859 | 831 | |
| 860 | 832 | if( argc!=2 ){ |
| 861 | 833 | return Th_WrongNumArgs(interp, "capexpr EXPR"); |
| 862 | 834 | } |
| 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); | |
| 864 | 836 | if( rc ) return rc; |
| 865 | 837 | rc = 0; |
| 866 | 838 | for(i=0; i<nCap; i++){ |
| 867 | 839 | if( azCap[i][0]=='!' ){ |
| 868 | 840 | rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); |
| @@ -921,11 +893,12 @@ | ||
| 921 | 893 | if( argc<2 ){ |
| 922 | 894 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 923 | 895 | } |
| 924 | 896 | for(i=1; i<argc && rc; i++){ |
| 925 | 897 | 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++){ | |
| 927 | 900 | switch( argv[i][j] ){ |
| 928 | 901 | case 'c': match |= searchCap & SRCH_CKIN; break; |
| 929 | 902 | case 'd': match |= searchCap & SRCH_DOC; break; |
| 930 | 903 | case 't': match |= searchCap & SRCH_TKT; break; |
| 931 | 904 | case 'w': match |= searchCap & SRCH_WIKI; break; |
| @@ -932,11 +905,11 @@ | ||
| 932 | 905 | } |
| 933 | 906 | } |
| 934 | 907 | if( !match ) rc = 0; |
| 935 | 908 | } |
| 936 | 909 | 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); | |
| 938 | 911 | } |
| 939 | 912 | Th_SetResultInt(interp, rc); |
| 940 | 913 | return TH_OK; |
| 941 | 914 | } |
| 942 | 915 | |
| @@ -1051,11 +1024,11 @@ | ||
| 1051 | 1024 | #endif |
| 1052 | 1025 | else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ |
| 1053 | 1026 | rc = 1; |
| 1054 | 1027 | } |
| 1055 | 1028 | 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); | |
| 1057 | 1030 | } |
| 1058 | 1031 | Th_SetResultInt(interp, rc); |
| 1059 | 1032 | return TH_OK; |
| 1060 | 1033 | } |
| 1061 | 1034 | |
| @@ -1104,18 +1077,20 @@ | ||
| 1104 | 1077 | const char **argv, |
| 1105 | 1078 | int *argl |
| 1106 | 1079 | ){ |
| 1107 | 1080 | int rc = 0; |
| 1108 | 1081 | int i; |
| 1082 | + int nn; | |
| 1109 | 1083 | if( argc!=2 ){ |
| 1110 | 1084 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 1111 | 1085 | } |
| 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++){ | |
| 1113 | 1088 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 1114 | 1089 | } |
| 1115 | 1090 | 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); | |
| 1117 | 1092 | } |
| 1118 | 1093 | Th_SetResultInt(interp, rc); |
| 1119 | 1094 | return TH_OK; |
| 1120 | 1095 | } |
| 1121 | 1096 | |
| @@ -1140,22 +1115,23 @@ | ||
| 1140 | 1115 | return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); |
| 1141 | 1116 | } |
| 1142 | 1117 | if( enableOutput ){ |
| 1143 | 1118 | int height; |
| 1144 | 1119 | Blob name; |
| 1145 | - int nValue; | |
| 1120 | + int nValue = 0; | |
| 1146 | 1121 | const char *zValue; |
| 1147 | 1122 | char *z, *zH; |
| 1148 | 1123 | int nElem; |
| 1149 | 1124 | int *aszElem; |
| 1150 | 1125 | char **azElem; |
| 1151 | 1126 | int i; |
| 1152 | 1127 | |
| 1153 | 1128 | 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])); | |
| 1156 | 1131 | zValue = Th_Fetch(blob_str(&name), &nValue); |
| 1132 | + nValue = TH1_LEN(nValue); | |
| 1157 | 1133 | zH = htmlize(blob_buffer(&name), blob_size(&name)); |
| 1158 | 1134 | z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); |
| 1159 | 1135 | free(zH); |
| 1160 | 1136 | sendText(0,z, -1, 0); |
| 1161 | 1137 | free(z); |
| @@ -1247,11 +1223,11 @@ | ||
| 1247 | 1223 | return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); |
| 1248 | 1224 | } |
| 1249 | 1225 | if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; |
| 1250 | 1226 | if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; |
| 1251 | 1227 | z = argv[1]; |
| 1252 | - size = argl[1]; | |
| 1228 | + size = TH1_LEN(argl[1]); | |
| 1253 | 1229 | for(n=1, i=0; i<size; i++){ |
| 1254 | 1230 | if( z[i]=='\n' ){ |
| 1255 | 1231 | n++; |
| 1256 | 1232 | if( n>=iMax ) break; |
| 1257 | 1233 | } |
| @@ -1407,11 +1383,12 @@ | ||
| 1407 | 1383 | return TH_OK; |
| 1408 | 1384 | }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ |
| 1409 | 1385 | Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); |
| 1410 | 1386 | return TH_OK; |
| 1411 | 1387 | }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])); | |
| 1413 | 1390 | return TH_ERROR; |
| 1414 | 1391 | } |
| 1415 | 1392 | } |
| 1416 | 1393 | |
| 1417 | 1394 | /* |
| @@ -1426,17 +1403,21 @@ | ||
| 1426 | 1403 | int argc, |
| 1427 | 1404 | const char **argv, |
| 1428 | 1405 | int *argl |
| 1429 | 1406 | ){ |
| 1430 | 1407 | const char *zDefault = 0; |
| 1408 | + const char *zVal; | |
| 1409 | + int sz; | |
| 1431 | 1410 | if( argc!=2 && argc!=3 ){ |
| 1432 | 1411 | return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); |
| 1433 | 1412 | } |
| 1434 | 1413 | if( argc==3 ){ |
| 1435 | 1414 | zDefault = argv[2]; |
| 1436 | 1415 | } |
| 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)); | |
| 1438 | 1419 | return TH_OK; |
| 1439 | 1420 | } |
| 1440 | 1421 | |
| 1441 | 1422 | /* |
| 1442 | 1423 | ** TH1 command: setParameter NAME VALUE |
| @@ -1848,10 +1829,47 @@ | ||
| 1848 | 1829 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); |
| 1849 | 1830 | Th_SetResult(interp, zUTime, -1); |
| 1850 | 1831 | return TH_OK; |
| 1851 | 1832 | } |
| 1852 | 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 | +} | |
| 1853 | 1871 | |
| 1854 | 1872 | /* |
| 1855 | 1873 | ** TH1 command: randhex N |
| 1856 | 1874 | ** |
| 1857 | 1875 | ** Return N*2 random hexadecimal digits with N<50. If N is omitted, |
| @@ -1923,11 +1941,13 @@ | ||
| 1923 | 1941 | int res = TH_OK; |
| 1924 | 1942 | int nVar; |
| 1925 | 1943 | char *zErr = 0; |
| 1926 | 1944 | int noComplain = 0; |
| 1927 | 1945 | |
| 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 | + ){ | |
| 1929 | 1949 | argc--; |
| 1930 | 1950 | argv++; |
| 1931 | 1951 | argl++; |
| 1932 | 1952 | noComplain = 1; |
| 1933 | 1953 | } |
| @@ -1939,15 +1959,22 @@ | ||
| 1939 | 1959 | Th_ErrorMessage(interp, "database is not open", 0, 0); |
| 1940 | 1960 | return TH_ERROR; |
| 1941 | 1961 | } |
| 1942 | 1962 | zSql = argv[1]; |
| 1943 | 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 | + | |
| 1944 | 1971 | while( res==TH_OK && nSql>0 ){ |
| 1945 | 1972 | zErr = 0; |
| 1946 | 1973 | report_restrict_sql(&zErr); |
| 1947 | 1974 | 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); | |
| 1949 | 1976 | g.dbIgnoreErrors--; |
| 1950 | 1977 | report_unrestrict_sql(); |
| 1951 | 1978 | if( rc!=0 || zErr!=0 ){ |
| 1952 | 1979 | if( noComplain ) return TH_OK; |
| 1953 | 1980 | Th_ErrorMessage(interp, "SQL error: ", |
| @@ -1964,31 +1991,31 @@ | ||
| 1964 | 1991 | int szVar = zVar ? th_strlen(zVar) : 0; |
| 1965 | 1992 | if( szVar>1 && zVar[0]=='$' |
| 1966 | 1993 | && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ |
| 1967 | 1994 | int nVal; |
| 1968 | 1995 | 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); | |
| 1970 | 1997 | } |
| 1971 | 1998 | } |
| 1972 | 1999 | while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ |
| 1973 | 2000 | int nCol = sqlite3_column_count(pStmt); |
| 1974 | 2001 | for(i=0; i<nCol; i++){ |
| 1975 | 2002 | const char *zCol = sqlite3_column_name(pStmt, i); |
| 1976 | 2003 | int szCol = th_strlen(zCol); |
| 1977 | 2004 | const char *zVal = (const char*)sqlite3_column_text(pStmt, i); |
| 1978 | 2005 | 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)); | |
| 1980 | 2007 | } |
| 1981 | 2008 | 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]); | |
| 1983 | 2010 | } |
| 1984 | - res = Th_Eval(interp, 0, argv[2], argl[2]); | |
| 2011 | + res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2])); | |
| 1985 | 2012 | if( g.thTrace ){ |
| 1986 | 2013 | int nTrRes; |
| 1987 | 2014 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 1988 | 2015 | Th_Trace("[query_eval] => %h {%#h}<br>\n", |
| 1989 | - Th_ReturnCodeName(res, 0), nTrRes, zTrRes); | |
| 2016 | + Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes); | |
| 1990 | 2017 | } |
| 1991 | 2018 | if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; |
| 1992 | 2019 | } |
| 1993 | 2020 | rc = sqlite3_finalize(pStmt); |
| 1994 | 2021 | if( rc!=SQLITE_OK ){ |
| @@ -2038,11 +2065,11 @@ | ||
| 2038 | 2065 | Th_SetResult(interp, 0, 0); |
| 2039 | 2066 | rc = TH_OK; |
| 2040 | 2067 | } |
| 2041 | 2068 | if( g.thTrace ){ |
| 2042 | 2069 | Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", |
| 2043 | - argl[nArg], argv[nArg], rc); | |
| 2070 | + TH1_LEN(argl[nArg]), argv[nArg], rc); | |
| 2044 | 2071 | } |
| 2045 | 2072 | return rc; |
| 2046 | 2073 | } |
| 2047 | 2074 | |
| 2048 | 2075 | /* |
| @@ -2121,11 +2148,11 @@ | ||
| 2121 | 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2122 | 2149 | } |
| 2123 | 2150 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2124 | 2151 | if( !zErr ){ |
| 2125 | 2152 | 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]))); | |
| 2127 | 2154 | rc = TH_OK; |
| 2128 | 2155 | }else{ |
| 2129 | 2156 | Th_SetResult(interp, zErr, -1); |
| 2130 | 2157 | rc = TH_ERROR; |
| 2131 | 2158 | } |
| @@ -2160,11 +2187,11 @@ | ||
| 2160 | 2187 | UrlData urlData; |
| 2161 | 2188 | |
| 2162 | 2189 | if( argc<2 || argc>5 ){ |
| 2163 | 2190 | return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); |
| 2164 | 2191 | } |
| 2165 | - if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ | |
| 2192 | + if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){ | |
| 2166 | 2193 | fAsynchronous = 1; nArg++; |
| 2167 | 2194 | } |
| 2168 | 2195 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2169 | 2196 | if( nArg+1!=argc && nArg+2!=argc ){ |
| 2170 | 2197 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| @@ -2189,11 +2216,11 @@ | ||
| 2189 | 2216 | return TH_ERROR; |
| 2190 | 2217 | } |
| 2191 | 2218 | re_free(pRe); |
| 2192 | 2219 | blob_zero(&payload); |
| 2193 | 2220 | 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])); | |
| 2195 | 2222 | zType = "POST"; |
| 2196 | 2223 | }else{ |
| 2197 | 2224 | zType = "GET"; |
| 2198 | 2225 | } |
| 2199 | 2226 | if( fAsynchronous ){ |
| @@ -2268,11 +2295,11 @@ | ||
| 2268 | 2295 | if( argc!=2 ){ |
| 2269 | 2296 | return Th_WrongNumArgs(interp, "captureTh1 STRING"); |
| 2270 | 2297 | } |
| 2271 | 2298 | pOrig = Th_SetOutputBlob(&out); |
| 2272 | 2299 | zStr = argv[1]; |
| 2273 | - nStr = argl[1]; | |
| 2300 | + nStr = TH1_LEN(argl[1]); | |
| 2274 | 2301 | rc = Th_Eval(g.interp, 0, zStr, nStr); |
| 2275 | 2302 | Th_SetOutputBlob(pOrig); |
| 2276 | 2303 | if(0==rc){ |
| 2277 | 2304 | Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); |
| 2278 | 2305 | } |
| @@ -2356,11 +2383,10 @@ | ||
| 2356 | 2383 | {"copybtn", copybtnCmd, 0}, |
| 2357 | 2384 | {"date", dateCmd, 0}, |
| 2358 | 2385 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 2359 | 2386 | {"defHeader", defHeaderCmd, 0}, |
| 2360 | 2387 | {"dir", dirCmd, 0}, |
| 2361 | - {"enable_htmlify",enableHtmlifyCmd, 0}, | |
| 2362 | 2388 | {"enable_output", enableOutputCmd, 0}, |
| 2363 | 2389 | {"encode64", encode64Cmd, 0}, |
| 2364 | 2390 | {"getParameter", getParameterCmd, 0}, |
| 2365 | 2391 | {"glob_match", globMatchCmd, 0}, |
| 2366 | 2392 | {"globalState", globalStateCmd, 0}, |
| @@ -2387,13 +2413,15 @@ | ||
| 2387 | 2413 | {"setting", settingCmd, 0}, |
| 2388 | 2414 | {"styleFooter", styleFooterCmd, 0}, |
| 2389 | 2415 | {"styleHeader", styleHeaderCmd, 0}, |
| 2390 | 2416 | {"styleScript", styleScriptCmd, 0}, |
| 2391 | 2417 | {"submenu", submenuCmd, 0}, |
| 2418 | + {"taint", taintCmd, 0}, | |
| 2392 | 2419 | {"tclReady", tclReadyCmd, 0}, |
| 2393 | 2420 | {"trace", traceCmd, 0}, |
| 2394 | 2421 | {"stime", stimeCmd, 0}, |
| 2422 | + {"untaint", untaintCmd, 0}, | |
| 2395 | 2423 | {"unversioned", unversionedCmd, 0}, |
| 2396 | 2424 | {"utime", utimeCmd, 0}, |
| 2397 | 2425 | {"verifyCsrf", verifyCsrfCmd, 0}, |
| 2398 | 2426 | {"verifyLogin", verifyLoginCmd, 0}, |
| 2399 | 2427 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -2494,10 +2522,26 @@ | ||
| 2494 | 2522 | Th_Trace("set %h {%h}<br>\n", zName, zValue); |
| 2495 | 2523 | } |
| 2496 | 2524 | Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); |
| 2497 | 2525 | } |
| 2498 | 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 | +} | |
| 2499 | 2543 | |
| 2500 | 2544 | /* |
| 2501 | 2545 | ** Appends an element to a TH1 list value. This function is called by the |
| 2502 | 2546 | ** transfer subsystem; therefore, it must be very careful to avoid doing |
| 2503 | 2547 | ** any unnecessary work. To that end, the TH1 subsystem will not be called |
| @@ -2680,10 +2724,11 @@ | ||
| 2680 | 2724 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2681 | 2725 | /* |
| 2682 | 2726 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2683 | 2727 | ** command hook handler as that is not actually an error condition. |
| 2684 | 2728 | */ |
| 2729 | + nResult = TH1_LEN(nResult); | |
| 2685 | 2730 | if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ |
| 2686 | 2731 | sendError(0,zResult, nResult, 0); |
| 2687 | 2732 | }else{ |
| 2688 | 2733 | /* |
| 2689 | 2734 | ** There is no command hook handler "installed". This situation |
| @@ -2767,10 +2812,11 @@ | ||
| 2767 | 2812 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2768 | 2813 | /* |
| 2769 | 2814 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2770 | 2815 | ** webpage hook handler as that is not actually an error condition. |
| 2771 | 2816 | */ |
| 2817 | + nResult = TH1_LEN(nResult); | |
| 2772 | 2818 | if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ |
| 2773 | 2819 | sendError(0,zResult, nResult, 1); |
| 2774 | 2820 | }else{ |
| 2775 | 2821 | /* |
| 2776 | 2822 | ** There is no webpage hook handler "installed". This situation |
| @@ -2894,11 +2940,16 @@ | ||
| 2894 | 2940 | } |
| 2895 | 2941 | rc = Th_GetVar(g.interp, (char*)zVar, nVar); |
| 2896 | 2942 | z += i+1+n; |
| 2897 | 2943 | i = 0; |
| 2898 | 2944 | 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 | + } | |
| 2900 | 2951 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 2901 | 2952 | sendText(pOut,z, i, 0); |
| 2902 | 2953 | z += i+5; |
| 2903 | 2954 | for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} |
| 2904 | 2955 | if( g.thTrace ){ |
| @@ -2907,11 +2958,11 @@ | ||
| 2907 | 2958 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 2908 | 2959 | if( g.thTrace ){ |
| 2909 | 2960 | int nTrRes; |
| 2910 | 2961 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2911 | 2962 | Th_Trace("[render_eval] => %h {%#h}<br>\n", |
| 2912 | - Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); | |
| 2963 | + Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes); | |
| 2913 | 2964 | } |
| 2914 | 2965 | if( rc!=TH_OK ) break; |
| 2915 | 2966 | z += i; |
| 2916 | 2967 | if( z[0] ){ z += 6; } |
| 2917 | 2968 | i = 0; |
| @@ -2949,14 +3000,81 @@ | ||
| 2949 | 3000 | ** e.g. via the "render" script function binding, need to use the |
| 2950 | 3001 | ** pThOut blob in order to avoid out-of-order output if |
| 2951 | 3002 | ** Th_SetOutputBlob() has been called. If it has not been called, |
| 2952 | 3003 | ** pThOut will be 0, which will redirect the output to CGI/stdout, |
| 2953 | 3004 | ** 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. | |
| 2956 | 3006 | */; |
| 2957 | 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 | +} | |
| 2958 | 3076 | |
| 2959 | 3077 | /* |
| 2960 | 3078 | ** COMMAND: test-th-render |
| 2961 | 3079 | ** |
| 2962 | 3080 | ** Usage: %fossil test-th-render FILE |
| @@ -2992,10 +3110,11 @@ | ||
| 2992 | 3110 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 2993 | 3111 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 2994 | 3112 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 2995 | 3113 | g.useLocalauth = 1; |
| 2996 | 3114 | } |
| 3115 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 2997 | 3116 | verify_all_options(); |
| 2998 | 3117 | if( g.argc<3 ){ |
| 2999 | 3118 | usage("FILE"); |
| 3000 | 3119 | } |
| 3001 | 3120 | blob_zero(&in); |
| @@ -3044,10 +3163,11 @@ | ||
| 3044 | 3163 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3045 | 3164 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3046 | 3165 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3047 | 3166 | g.useLocalauth = 1; |
| 3048 | 3167 | } |
| 3168 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 3049 | 3169 | verify_all_options(); |
| 3050 | 3170 | if( g.argc!=3 ){ |
| 3051 | 3171 | usage("script"); |
| 3052 | 3172 | } |
| 3053 | 3173 | if(file_isfile(g.argv[2], ExtFILE)){ |
| 3054 | 3174 |
| --- 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 @@ | ||
| 41 | 41 | #define USE_ARGV_TO_OBJV() \ |
| 42 | 42 | int objc; \ |
| 43 | 43 | Tcl_Obj **objv; \ |
| 44 | 44 | int obji; |
| 45 | 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]); \ | |
| 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 | 52 | } |
| 53 | 53 | |
| 54 | 54 | #define FREE_ARGV_TO_OBJV() \ |
| 55 | 55 | for(obji=1; obji<argc; obji++){ \ |
| 56 | 56 | Tcl_DecrRefCount(objv[obji-1]); \ |
| @@ -183,11 +183,11 @@ | ||
| 183 | 183 | ** the only Tcl API functions that MUST be called prior to being able to call |
| 184 | 184 | ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete |
| 185 | 185 | ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp |
| 186 | 186 | ** and Tcl_Finalize function types are also required. |
| 187 | 187 | */ |
| 188 | -typedef void (tcl_FindExecutableProc) (const char *); | |
| 188 | +typedef const char *(tcl_FindExecutableProc) (const char *); | |
| 189 | 189 | typedef Tcl_Interp *(tcl_CreateInterpProc) (void); |
| 190 | 190 | typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); |
| 191 | 191 | typedef void (tcl_FinalizeProc) (void); |
| 192 | 192 | |
| 193 | 193 | /* |
| @@ -321,27 +321,10 @@ | ||
| 321 | 321 | ** by the caller. This must be declared here because quite a few functions in |
| 322 | 322 | ** this file need to use it before it can be defined. |
| 323 | 323 | */ |
| 324 | 324 | static int createTclInterp(Th_Interp *interp, void *pContext); |
| 325 | 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 | 326 | /* |
| 344 | 327 | ** Returns the Tcl return code corresponding to the specified TH1 |
| 345 | 328 | ** return code. |
| 346 | 329 | */ |
| 347 | 330 | static int getTclReturnCode( |
| @@ -387,10 +370,12 @@ | ||
| 387 | 370 | static char *getTclResult( |
| 388 | 371 | Tcl_Interp *pInterp, |
| 389 | 372 | int *pN |
| 390 | 373 | ){ |
| 391 | 374 | Tcl_Obj *resultPtr; |
| 375 | + Tcl_Size n; | |
| 376 | + char *zRes; | |
| 392 | 377 | |
| 393 | 378 | if( !pInterp ){ /* This should not happen. */ |
| 394 | 379 | if( pN ) *pN = 0; |
| 395 | 380 | return 0; |
| 396 | 381 | } |
| @@ -397,11 +382,13 @@ | ||
| 397 | 382 | resultPtr = Tcl_GetObjResult(pInterp); |
| 398 | 383 | if( !resultPtr ){ /* This should not happen either? */ |
| 399 | 384 | if( pN ) *pN = 0; |
| 400 | 385 | return 0; |
| 401 | 386 | } |
| 402 | - return Tcl_GetStringFromObj(resultPtr, pN); | |
| 387 | + zRes = Tcl_GetStringFromObj(resultPtr, &n); | |
| 388 | + *pN = (int)n; | |
| 389 | + return zRes; | |
| 403 | 390 | } |
| 404 | 391 | |
| 405 | 392 | /* |
| 406 | 393 | ** Tcl context information used by TH1. This structure definition has been |
| 407 | 394 | ** copied from and should be kept in sync with the one in "main.c". |
| @@ -416,48 +403,12 @@ | ||
| 416 | 403 | tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */ |
| 417 | 404 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 418 | 405 | int useObjProc; /* Non-zero if an objProc can be called directly. */ |
| 419 | 406 | int useTip285; /* Non-zero if TIP #285 is available. */ |
| 420 | 407 | 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 | 408 | }; |
| 426 | 409 | |
| 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 | 410 | /* |
| 460 | 411 | ** TH1 command: tclEval arg ?arg ...? |
| 461 | 412 | ** |
| 462 | 413 | ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script |
| 463 | 414 | ** error is generated, it will be transformed into a TH1 script error. The |
| @@ -485,17 +436,13 @@ | ||
| 485 | 436 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 486 | 437 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 487 | 438 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 488 | 439 | return TH_ERROR; |
| 489 | 440 | } |
| 490 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 491 | - if( rc!=TH_OK ){ | |
| 492 | - return rc; | |
| 493 | - } | |
| 494 | 441 | Tcl_Preserve((ClientData)tclInterp); |
| 495 | 442 | if( argc==2 ){ |
| 496 | - objPtr = Tcl_NewStringObj(argv[1], argl[1]); | |
| 443 | + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); | |
| 497 | 444 | Tcl_IncrRefCount(objPtr); |
| 498 | 445 | rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); |
| 499 | 446 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 500 | 447 | }else{ |
| 501 | 448 | USE_ARGV_TO_OBJV(); |
| @@ -507,12 +454,10 @@ | ||
| 507 | 454 | FREE_ARGV_TO_OBJV(); |
| 508 | 455 | } |
| 509 | 456 | zResult = getTclResult(tclInterp, &nResult); |
| 510 | 457 | Th_SetResult(interp, zResult, nResult); |
| 511 | 458 | Tcl_Release((ClientData)tclInterp); |
| 512 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 513 | - getTh1ReturnCode(rc)); | |
| 514 | 459 | return rc; |
| 515 | 460 | } |
| 516 | 461 | |
| 517 | 462 | /* |
| 518 | 463 | ** TH1 command: tclExpr arg ?arg ...? |
| @@ -545,17 +490,13 @@ | ||
| 545 | 490 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 546 | 491 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 547 | 492 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 548 | 493 | return TH_ERROR; |
| 549 | 494 | } |
| 550 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 551 | - if( rc!=TH_OK ){ | |
| 552 | - return rc; | |
| 553 | - } | |
| 554 | 495 | Tcl_Preserve((ClientData)tclInterp); |
| 555 | 496 | if( argc==2 ){ |
| 556 | - objPtr = Tcl_NewStringObj(argv[1], argl[1]); | |
| 497 | + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); | |
| 557 | 498 | Tcl_IncrRefCount(objPtr); |
| 558 | 499 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 559 | 500 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 560 | 501 | }else{ |
| 561 | 502 | USE_ARGV_TO_OBJV(); |
| @@ -565,21 +506,21 @@ | ||
| 565 | 506 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 566 | 507 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 567 | 508 | FREE_ARGV_TO_OBJV(); |
| 568 | 509 | } |
| 569 | 510 | 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; | |
| 571 | 514 | }else{ |
| 572 | 515 | zResult = getTclResult(tclInterp, &nResult); |
| 573 | 516 | } |
| 574 | - Th_SetResult(interp, zResult, nResult); | |
| 517 | + Th_SetResult(interp, zResult, (int)nResult); | |
| 575 | 518 | if( rc==TCL_OK ){ |
| 576 | 519 | Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; |
| 577 | 520 | } |
| 578 | 521 | Tcl_Release((ClientData)tclInterp); |
| 579 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 580 | - getTh1ReturnCode(rc)); | |
| 581 | 522 | return rc; |
| 582 | 523 | } |
| 583 | 524 | |
| 584 | 525 | /* |
| 585 | 526 | ** TH1 command: tclInvoke command ?arg ...? |
| @@ -610,20 +551,16 @@ | ||
| 610 | 551 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 611 | 552 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 612 | 553 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 613 | 554 | return TH_ERROR; |
| 614 | 555 | } |
| 615 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 616 | - if( rc!=TH_OK ){ | |
| 617 | - return rc; | |
| 618 | - } | |
| 619 | 556 | Tcl_Preserve((ClientData)tclInterp); |
| 620 | 557 | #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV |
| 621 | 558 | if( GET_CTX_TCL_USEOBJPROC(ctx) ){ |
| 622 | 559 | Tcl_Command command; |
| 623 | 560 | 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])); | |
| 625 | 562 | Tcl_IncrRefCount(objPtr); |
| 626 | 563 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 627 | 564 | if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ |
| 628 | 565 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| 629 | 566 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| @@ -649,12 +586,10 @@ | ||
| 649 | 586 | FREE_ARGV_TO_OBJV(); |
| 650 | 587 | } |
| 651 | 588 | zResult = getTclResult(tclInterp, &nResult); |
| 652 | 589 | Th_SetResult(interp, zResult, nResult); |
| 653 | 590 | Tcl_Release((ClientData)tclInterp); |
| 654 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 655 | - getTh1ReturnCode(rc)); | |
| 656 | 591 | return rc; |
| 657 | 592 | } |
| 658 | 593 | |
| 659 | 594 | /* |
| 660 | 595 | ** TH1 command: tclIsSafe |
| @@ -767,10 +702,11 @@ | ||
| 767 | 702 | int objc, |
| 768 | 703 | Tcl_Obj *const objv[] |
| 769 | 704 | ){ |
| 770 | 705 | Th_Interp *th1Interp; |
| 771 | 706 | int nArg; |
| 707 | + Tcl_Size szArg; | |
| 772 | 708 | const char *arg; |
| 773 | 709 | int rc; |
| 774 | 710 | |
| 775 | 711 | if( objc!=2 ){ |
| 776 | 712 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -779,14 +715,15 @@ | ||
| 779 | 715 | th1Interp = (Th_Interp *)clientData; |
| 780 | 716 | if( !th1Interp ){ |
| 781 | 717 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 782 | 718 | return TCL_ERROR; |
| 783 | 719 | } |
| 784 | - arg = Tcl_GetStringFromObj(objv[1], &nArg); | |
| 720 | + arg = Tcl_GetStringFromObj(objv[1], &szArg); | |
| 721 | + nArg = (int)szArg; | |
| 785 | 722 | rc = Th_Eval(th1Interp, 0, arg, nArg); |
| 786 | 723 | arg = Th_GetResult(th1Interp, &nArg); |
| 787 | - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); | |
| 724 | + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); | |
| 788 | 725 | return getTclReturnCode(rc); |
| 789 | 726 | } |
| 790 | 727 | |
| 791 | 728 | /* |
| 792 | 729 | ** Tcl command: th1Expr arg |
| @@ -800,10 +737,11 @@ | ||
| 800 | 737 | int objc, |
| 801 | 738 | Tcl_Obj *const objv[] |
| 802 | 739 | ){ |
| 803 | 740 | Th_Interp *th1Interp; |
| 804 | 741 | int nArg; |
| 742 | + Tcl_Size szArg; | |
| 805 | 743 | const char *arg; |
| 806 | 744 | int rc; |
| 807 | 745 | |
| 808 | 746 | if( objc!=2 ){ |
| 809 | 747 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -812,14 +750,14 @@ | ||
| 812 | 750 | th1Interp = (Th_Interp *)clientData; |
| 813 | 751 | if( !th1Interp ){ |
| 814 | 752 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 815 | 753 | return TCL_ERROR; |
| 816 | 754 | } |
| 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); | |
| 819 | 757 | arg = Th_GetResult(th1Interp, &nArg); |
| 820 | - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); | |
| 758 | + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); | |
| 821 | 759 | return getTclReturnCode(rc); |
| 822 | 760 | } |
| 823 | 761 | |
| 824 | 762 | /* |
| 825 | 763 | ** Array of Tcl integration commands. Used when adding or removing the Tcl |
| 826 | 764 |
| --- 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 @@ | ||
| 1888 | 1888 | if( zTagName ){ |
| 1889 | 1889 | zType = "ci"; |
| 1890 | 1890 | if( matchStyle==MS_EXACT ){ |
| 1891 | 1891 | /* For exact maching, inhibit links to the selected tag. */ |
| 1892 | 1892 | zThisTag = zTagName; |
| 1893 | - Th_Store("current_checkin", zTagName); | |
| 1893 | + Th_StoreUnsafe("current_checkin", zTagName); | |
| 1894 | 1894 | } |
| 1895 | 1895 | |
| 1896 | 1896 | /* Display a checkbox to enable/disable display of related check-ins. */ |
| 1897 | 1897 | if( advancedMenu ){ |
| 1898 | 1898 | style_submenu_checkbox("rel", "Related", 0, 0); |
| 1899 | 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_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 @@ | ||
| 188 | 188 | */ |
| 189 | 189 | static void initializeVariablesFromDb(void){ |
| 190 | 190 | const char *zName; |
| 191 | 191 | Stmt q; |
| 192 | 192 | int i, n, size, j; |
| 193 | + const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime"; | |
| 193 | 194 | |
| 194 | 195 | zName = PD("name","-none-"); |
| 195 | 196 | 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, " | |
| 197 | 198 | "julianday('now') - tkt_mtime, " |
| 198 | - "julianday('now') - tkt_ctime, *" | |
| 199 | + "julianday('now') - %s, *" | |
| 199 | 200 | " FROM ticket WHERE tkt_uuid GLOB '%q*'", |
| 201 | + zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/, | |
| 200 | 202 | zName); |
| 201 | 203 | if( db_step(&q)==SQLITE_ROW ){ |
| 202 | 204 | n = db_column_count(&q); |
| 203 | 205 | for(i=0; i<n; i++){ |
| 204 | 206 | const char *zVal = db_column_text(&q, i); |
| @@ -210,10 +212,11 @@ | ||
| 210 | 212 | zVal = zRevealed = db_reveal(zVal); |
| 211 | 213 | } |
| 212 | 214 | if( (j = fieldId(zName))>=0 ){ |
| 213 | 215 | aField[j].zValue = mprintf("%s", zVal); |
| 214 | 216 | }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){ |
| 217 | + /* TICKET table columns that begin with "tkt_" are always safe */ | |
| 215 | 218 | Th_Store(zName, zVal); |
| 216 | 219 | } |
| 217 | 220 | free(zRevealed); |
| 218 | 221 | } |
| 219 | 222 | Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2))); |
| @@ -220,11 +223,11 @@ | ||
| 220 | 223 | Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3))); |
| 221 | 224 | } |
| 222 | 225 | db_finalize(&q); |
| 223 | 226 | for(i=0; i<nField; i++){ |
| 224 | 227 | 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); | |
| 226 | 229 | } |
| 227 | 230 | } |
| 228 | 231 | } |
| 229 | 232 | |
| 230 | 233 | /* |
| @@ -233,11 +236,11 @@ | ||
| 233 | 236 | static void initializeVariablesFromCGI(void){ |
| 234 | 237 | int i; |
| 235 | 238 | const char *z; |
| 236 | 239 | |
| 237 | 240 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 238 | - Th_Store(z, P(z)); | |
| 241 | + Th_StoreUnsafe(z, P(z)); | |
| 239 | 242 | } |
| 240 | 243 | } |
| 241 | 244 | |
| 242 | 245 | /* |
| 243 | 246 | ** Information about a single J-card |
| @@ -818,15 +821,15 @@ | ||
| 818 | 821 | if( argc!=3 ){ |
| 819 | 822 | return Th_WrongNumArgs(interp, "append_field FIELD STRING"); |
| 820 | 823 | } |
| 821 | 824 | if( g.thTrace ){ |
| 822 | 825 | 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]); | |
| 824 | 827 | } |
| 825 | 828 | 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 ){ | |
| 828 | 831 | break; |
| 829 | 832 | } |
| 830 | 833 | } |
| 831 | 834 | if( idx>=nField ){ |
| 832 | 835 | Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); |
| @@ -938,10 +941,11 @@ | ||
| 938 | 941 | const char *zValue; |
| 939 | 942 | int nValue; |
| 940 | 943 | if( aField[i].zAppend ) continue; |
| 941 | 944 | zValue = Th_Fetch(aField[i].zName, &nValue); |
| 942 | 945 | if( zValue ){ |
| 946 | + nValue = TH1_LEN(nValue); | |
| 943 | 947 | while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } |
| 944 | 948 | if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) |
| 945 | 949 | || memcmp(zValue, aField[i].zValue, nValue)!=0 |
| 946 | 950 | ||(int)strlen(aField[i].zValue)!=nValue |
| 947 | 951 | ){ |
| @@ -1040,16 +1044,16 @@ | ||
| 1040 | 1044 | if( uid ){ |
| 1041 | 1045 | char * zEmail = |
| 1042 | 1046 | db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d", |
| 1043 | 1047 | uid); |
| 1044 | 1048 | if( zEmail ){ |
| 1045 | - Th_Store("private_contact", zEmail); | |
| 1049 | + Th_StoreUnsafe("private_contact", zEmail); | |
| 1046 | 1050 | fossil_free(zEmail); |
| 1047 | 1051 | } |
| 1048 | 1052 | } |
| 1049 | 1053 | } |
| 1050 | - Th_Store("login", login_name()); | |
| 1054 | + Th_StoreUnsafe("login", login_name()); | |
| 1051 | 1055 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1052 | 1056 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, |
| 1053 | 1057 | (void*)&zNewUuid, 0); |
| 1054 | 1058 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1); |
| 1055 | 1059 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ |
| @@ -1120,11 +1124,11 @@ | ||
| 1120 | 1124 | initializeVariablesFromDb(); |
| 1121 | 1125 | if( g.zPath[0]=='d' ) showAllFields(); |
| 1122 | 1126 | form_begin(0, "%R/%s", g.zPath); |
| 1123 | 1127 | @ <input type="hidden" name="name" value="%s(zName)"> |
| 1124 | 1128 | zScript = ticket_editpage_code(); |
| 1125 | - Th_Store("login", login_name()); | |
| 1129 | + Th_StoreUnsafe("login", login_name()); | |
| 1126 | 1130 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1127 | 1131 | Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); |
| 1128 | 1132 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); |
| 1129 | 1133 | if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1); |
| 1130 | 1134 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ |
| 1131 | 1135 |
| --- 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 @@ | ||
| 481 | 481 | @ <th1> |
| 482 | 482 | @ if {[info exists tkt_uuid]} { |
| 483 | 483 | @ html "<td class='tktDspValue' colspan='3'>" |
| 484 | 484 | @ copybtn hash-tk 0 $tkt_uuid 2 |
| 485 | 485 | @ if {[hascap s]} { |
| 486 | -@ html " ($tkt_id)" | |
| 486 | +@ puts " ($tkt_id)" | |
| 487 | 487 | @ } |
| 488 | 488 | @ html "</td></tr>\n" |
| 489 | 489 | @ } else { |
| 490 | 490 | @ if {[hascap s]} { |
| 491 | 491 | @ html "<td class='tktDspValue' colspan='3'>Deleted " |
| @@ -522,24 +522,24 @@ | ||
| 522 | 522 | @ $<resolution> |
| 523 | 523 | @ </td></tr> |
| 524 | 524 | @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> |
| 525 | 525 | @ <th1> |
| 526 | 526 | @ if {[info exists tkt_datetime]} { |
| 527 | -@ html $tkt_datetime | |
| 527 | +@ puts $tkt_datetime | |
| 528 | 528 | @ } |
| 529 | 529 | @ if {[info exists tkt_mage]} { |
| 530 | -@ html "<br>$tkt_mage" | |
| 530 | +@ html "<br>[htmlize $tkt_mage] ago" | |
| 531 | 531 | @ } |
| 532 | 532 | @ </th1> |
| 533 | 533 | @ </td> |
| 534 | 534 | @ <td class="tktDspLabel">Created:</td><td class="tktDspValue"> |
| 535 | 535 | @ <th1> |
| 536 | 536 | @ if {[info exists tkt_datetime_creation]} { |
| 537 | -@ html $tkt_datetime_creation | |
| 537 | +@ puts $tkt_datetime_creation | |
| 538 | 538 | @ } |
| 539 | 539 | @ if {[info exists tkt_cage]} { |
| 540 | -@ html "<br>$tkt_cage" | |
| 540 | +@ html "<br>[htmlize $tkt_cage] ago" | |
| 541 | 541 | @ } |
| 542 | 542 | @ </th1> |
| 543 | 543 | @ </td></tr> |
| 544 | 544 | @ <th1>enable_output [hascap e]</th1> |
| 545 | 545 | @ <tr> |
| @@ -555,18 +555,18 @@ | ||
| 555 | 555 | @ set urlfoundin [httpize $foundin] |
| 556 | 556 | @ set tagpattern {^[-0-9A-Za-z_\\.]+$} |
| 557 | 557 | @ if [regexp $tagpattern $foundin] { |
| 558 | 558 | @ query {SELECT count(*) AS match FROM tag |
| 559 | 559 | @ WHERE tagname=concat('sym-',$foundin)} { |
| 560 | -@ if {$match} {set versionlink "/timeline?t=$urlfoundin"} | |
| 560 | +@ if {$match} {set versionlink "timeline?t=$urlfoundin"} | |
| 561 | 561 | @ } |
| 562 | 562 | @ } |
| 563 | 563 | @ set hashpattern {^[0-9a-f]+$} |
| 564 | 564 | @ if [regexp $hashpattern $foundin] { |
| 565 | 565 | @ set pattern $foundin* |
| 566 | 566 | @ 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"} | |
| 568 | 568 | @ } |
| 569 | 569 | @ } |
| 570 | 570 | @ if {$versionlink eq ""} { |
| 571 | 571 | @ puts $foundin |
| 572 | 572 | @ } else { |
| @@ -614,19 +614,19 @@ | ||
| 614 | 614 | @ html "User Comments:</td></tr>\n" |
| 615 | 615 | @ html "<tr><td colspan='5' class='tktDspValue'>\n" |
| 616 | 616 | @ set seenRow 1 |
| 617 | 617 | @ } |
| 618 | 618 | @ html "<span class='tktDspCommenter'>" |
| 619 | -@ html "[htmlize $xlogin]" | |
| 619 | +@ puts $xlogin | |
| 620 | 620 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 621 | -@ html " (claiming to be [htmlize $xusername])" | |
| 621 | +@ puts " (claiming to be $xusername)" | |
| 622 | 622 | @ } |
| 623 | -@ html " added on $xdate:" | |
| 623 | +@ puts " added on $xdate:" | |
| 624 | 624 | @ html "</span>\n" |
| 625 | 625 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 626 | 626 | @ set r [randhex] |
| 627 | -@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} | |
| 627 | +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} | |
| 628 | 628 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 629 | 629 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 630 | 630 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 631 | 631 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 632 | 632 | @ html [lindex [markdown $xcomment] 1] |
| @@ -801,19 +801,19 @@ | ||
| 801 | 801 | @ html "Previous User Comments:</td></tr>\n" |
| 802 | 802 | @ html "<tr><td colspan='2' class='tktDspValue'>\n" |
| 803 | 803 | @ set seenRow 1 |
| 804 | 804 | @ } |
| 805 | 805 | @ html "<span class='tktDspCommenter'>" |
| 806 | -@ html "[htmlize $xlogin]" | |
| 806 | +@ puts $xlogin | |
| 807 | 807 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 808 | -@ html " (claiming to be [htmlize $xusername])" | |
| 808 | +@ puts " (claiming to be $xusername)" | |
| 809 | 809 | @ } |
| 810 | -@ html " added on $xdate:" | |
| 810 | +@ puts " added on $xdate:" | |
| 811 | 811 | @ html "</span>\n" |
| 812 | 812 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 813 | 813 | @ set r [randhex] |
| 814 | -@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} | |
| 814 | +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} | |
| 815 | 815 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 816 | 816 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 817 | 817 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 818 | 818 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 819 | 819 | @ html [lindex [markdown $xcomment] 1] |
| 820 | 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 | @ 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 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 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 @@ | ||
| 326 | 326 | ** |
| 327 | 327 | ** > fossil user contact USERNAME ?CONTACT-INFO? |
| 328 | 328 | ** |
| 329 | 329 | ** Query or set contact information for user USERNAME |
| 330 | 330 | ** |
| 331 | -** > fossil user default ?USERNAME? | |
| 331 | +** > fossil user default ?OPTIONS? ?USERNAME? | |
| 332 | 332 | ** |
| 333 | 333 | ** 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 | |
| 335 | 342 | ** |
| 336 | 343 | ** > fossil user list | ls |
| 337 | 344 | ** |
| 338 | 345 | ** List all users known to the repository |
| 339 | 346 | ** |
| @@ -385,21 +392,50 @@ | ||
| 385 | 392 | &login, zPw, &caps, &contact |
| 386 | 393 | ); |
| 387 | 394 | db_protect_pop(); |
| 388 | 395 | free(zPw); |
| 389 | 396 | }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); | |
| 401 | 437 | } |
| 402 | 438 | } |
| 403 | 439 | }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) || |
| 404 | 440 | ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){ |
| 405 | 441 | Stmt q; |
| @@ -496,52 +532,54 @@ | ||
| 496 | 532 | /* |
| 497 | 533 | ** Figure out what user is at the controls. |
| 498 | 534 | ** |
| 499 | 535 | ** (1) Use the --user and -U command-line options. |
| 500 | 536 | ** |
| 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. | |
| 514 | 552 | ** |
| 515 | 553 | ** The user name is stored in g.zLogin. The uid is in g.userUid. |
| 516 | 554 | */ |
| 517 | -void user_select(void){ | |
| 555 | +int user_select(void){ | |
| 518 | 556 | UrlData url; |
| 519 | - if( g.userUid ) return; | |
| 557 | + if( g.userUid ) return 1; | |
| 520 | 558 | if( g.zLogin ){ |
| 521 | 559 | if( attempt_user(g.zLogin)==0 ){ |
| 522 | 560 | fossil_fatal("no such user: %s", g.zLogin); |
| 523 | 561 | }else{ |
| 524 | - return; | |
| 562 | + return 2; | |
| 525 | 563 | } |
| 526 | 564 | } |
| 527 | 565 | |
| 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; | |
| 539 | 577 | |
| 540 | 578 | memset(&url, 0, sizeof(url)); |
| 541 | 579 | 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; | |
| 543 | 581 | |
| 544 | 582 | fossil_print( |
| 545 | 583 | "Cannot figure out who you are! Consider using the --user\n" |
| 546 | 584 | "command line option, setting your USER environment variable,\n" |
| 547 | 585 | "or setting a default user with \"fossil user default USER\".\n" |
| 548 | 586 |
| --- 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 @@ | ||
| 1116 | 1116 | blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1117 | 1117 | zName, mtime, zHash, sz); |
| 1118 | 1118 | } |
| 1119 | 1119 | db_finalize(&uvq); |
| 1120 | 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 | +} | |
| 1121 | 1133 | |
| 1122 | 1134 | /* |
| 1123 | 1135 | ** Called when there is an attempt to transfer private content to and |
| 1124 | 1136 | ** from a server without authorization. |
| 1125 | 1137 | */ |
| 1126 | 1138 | 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()) | |
| 1128 | 1140 | } |
| 1129 | 1141 | |
| 1130 | 1142 | /* |
| 1131 | 1143 | ** Return the common TH1 code to evaluate prior to evaluating any other |
| 1132 | 1144 | ** TH1 transfer notification scripts. |
| @@ -1316,11 +1328,11 @@ | ||
| 1316 | 1328 | ** Server accepts a file from the client. |
| 1317 | 1329 | */ |
| 1318 | 1330 | if( blob_eq(&xfer.aToken[0], "file") ){ |
| 1319 | 1331 | if( !isPush ){ |
| 1320 | 1332 | cgi_reset_content(); |
| 1321 | - @ error not\sauthorized\sto\swrite | |
| 1333 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1322 | 1334 | nErr++; |
| 1323 | 1335 | break; |
| 1324 | 1336 | } |
| 1325 | 1337 | xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList); |
| 1326 | 1338 | if( blob_size(&xfer.err) ){ |
| @@ -1337,11 +1349,11 @@ | ||
| 1337 | 1349 | ** Server accepts a compressed file from the client. |
| 1338 | 1350 | */ |
| 1339 | 1351 | if( blob_eq(&xfer.aToken[0], "cfile") ){ |
| 1340 | 1352 | if( !isPush ){ |
| 1341 | 1353 | cgi_reset_content(); |
| 1342 | - @ error not\sauthorized\sto\swrite | |
| 1354 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1343 | 1355 | nErr++; |
| 1344 | 1356 | break; |
| 1345 | 1357 | } |
| 1346 | 1358 | xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList); |
| 1347 | 1359 | if( blob_size(&xfer.err) ){ |
| @@ -1461,23 +1473,23 @@ | ||
| 1461 | 1473 | } |
| 1462 | 1474 | login_check_credentials(); |
| 1463 | 1475 | if( blob_eq(&xfer.aToken[0], "pull") ){ |
| 1464 | 1476 | if( !g.perm.Read ){ |
| 1465 | 1477 | cgi_reset_content(); |
| 1466 | - @ error not\sauthorized\sto\sread | |
| 1478 | + @ error not\sauthorized\sto\sread%s(whyNotAuth()) | |
| 1467 | 1479 | nErr++; |
| 1468 | 1480 | break; |
| 1469 | 1481 | } |
| 1470 | 1482 | isPull = 1; |
| 1471 | 1483 | }else{ |
| 1472 | 1484 | if( !g.perm.Write ){ |
| 1473 | 1485 | if( !isPull ){ |
| 1474 | 1486 | cgi_reset_content(); |
| 1475 | - @ error not\sauthorized\sto\swrite | |
| 1487 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1476 | 1488 | nErr++; |
| 1477 | 1489 | }else{ |
| 1478 | - @ message pull\sonly\s-\snot\sauthorized\sto\spush | |
| 1490 | + @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth()) | |
| 1479 | 1491 | } |
| 1480 | 1492 | }else{ |
| 1481 | 1493 | isPush = 1; |
| 1482 | 1494 | } |
| 1483 | 1495 | } |
| @@ -1491,11 +1503,11 @@ | ||
| 1491 | 1503 | int iVers; |
| 1492 | 1504 | login_check_credentials(); |
| 1493 | 1505 | if( !g.perm.Clone ){ |
| 1494 | 1506 | cgi_reset_content(); |
| 1495 | 1507 | @ 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()) | |
| 1497 | 1509 | nErr++; |
| 1498 | 1510 | break; |
| 1499 | 1511 | } |
| 1500 | 1512 | if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){ |
| 1501 | 1513 | @ pragma uv-pull-only |
| @@ -1592,11 +1604,11 @@ | ||
| 1592 | 1604 | } |
| 1593 | 1605 | blob_zero(&content); |
| 1594 | 1606 | blob_extract(xfer.pIn, size, &content); |
| 1595 | 1607 | if( !g.perm.Admin ){ |
| 1596 | 1608 | cgi_reset_content(); |
| 1597 | - @ error not\sauthorized\sto\spush\sconfiguration | |
| 1609 | + @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth()) | |
| 1598 | 1610 | nErr++; |
| 1599 | 1611 | break; |
| 1600 | 1612 | } |
| 1601 | 1613 | configure_receive(zName, &content, CONFIGSET_ALL); |
| 1602 | 1614 | blob_reset(&content); |
| 1603 | 1615 |
| --- 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 |
+7
| --- test/tester.tcl | ||
| +++ test/tester.tcl | ||
| @@ -356,10 +356,11 @@ | ||
| 356 | 356 | mtime-changes \ |
| 357 | 357 | mv-rm-files \ |
| 358 | 358 | pgp-command \ |
| 359 | 359 | preferred-diff-type \ |
| 360 | 360 | proxy \ |
| 361 | + raw-bgcolor \ | |
| 361 | 362 | redirect-to-https \ |
| 362 | 363 | relative-paths \ |
| 363 | 364 | repo-cksum \ |
| 364 | 365 | repolist-skin \ |
| 365 | 366 | robot-restrict \ |
| @@ -373,13 +374,19 @@ | ||
| 373 | 374 | ssl-identity \ |
| 374 | 375 | tclsh \ |
| 375 | 376 | th1-setup \ |
| 376 | 377 | th1-uri-regexp \ |
| 377 | 378 | ticket-default-report \ |
| 379 | + timeline-hard-newlines \ | |
| 380 | + timeline-plaintext \ | |
| 381 | + timeline-truncate-at-blank \ | |
| 382 | + timeline-tslink-info \ | |
| 378 | 383 | timeline-utc \ |
| 379 | 384 | user-color-map \ |
| 385 | + verify-comments \ | |
| 380 | 386 | uv-sync \ |
| 387 | + vuln-report \ | |
| 381 | 388 | web-browser] |
| 382 | 389 | |
| 383 | 390 | fossil test-th-eval "hasfeature legacyMvRm" |
| 384 | 391 | |
| 385 | 392 | if {[normalize_result] eq "1"} { |
| 386 | 393 | |
| 387 | 394 | 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 |
+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 |
| --- 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 @@ | ||
| 795 | 795 | rpage-\$requested_page\ |
| 796 | 796 | cpage-\$canonical_page\">" [normalize_result]]} |
| 797 | 797 | |
| 798 | 798 | ############################################################################### |
| 799 | 799 | |
| 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}} | |
| 802 | 802 | |
| 803 | 803 | ############################################################################### |
| 804 | 804 | |
| 805 | 805 | test_in_checkout th1-header-2 { |
| 806 | 806 | fossil test-th-eval --open-config "styleHeader {Page Title Here}" |
| 807 | 807 | } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 808 | 808 | |
| 809 | 809 | ############################################################################### |
| 810 | 810 | |
| 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}} | |
| 813 | 813 | |
| 814 | 814 | ############################################################################### |
| 815 | 815 | |
| 816 | 816 | fossil test-th-eval --open-config "styleFooter" |
| 817 | 817 | test th1-footer-2 {$RESULT eq {}} |
| @@ -879,44 +879,44 @@ | ||
| 879 | 879 | test th1-artifact-1 {$RESULT eq \ |
| 880 | 880 | {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} |
| 881 | 881 | |
| 882 | 882 | ############################################################################### |
| 883 | 883 | |
| 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}} | |
| 886 | 886 | |
| 887 | 887 | ############################################################################### |
| 888 | 888 | |
| 889 | 889 | test_in_checkout th1-artifact-3 { |
| 890 | 890 | fossil test-th-eval --open-config "artifact tip" |
| 891 | 891 | } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]} |
| 892 | 892 | |
| 893 | 893 | ############################################################################### |
| 894 | 894 | |
| 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}} | |
| 897 | 897 | |
| 898 | 898 | ############################################################################### |
| 899 | 899 | |
| 900 | 900 | fossil test-th-eval --open-config "artifact 0000000000" |
| 901 | 901 | test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} |
| 902 | 902 | |
| 903 | 903 | ############################################################################### |
| 904 | 904 | |
| 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}} | |
| 907 | 907 | |
| 908 | 908 | ############################################################################### |
| 909 | 909 | |
| 910 | 910 | test_in_checkout th1-artifact-7 { |
| 911 | 911 | fossil test-th-eval --open-config "artifact tip test/th1.test" |
| 912 | 912 | } {[regexp -- {th1-artifact-7} $RESULT]} |
| 913 | 913 | |
| 914 | 914 | ############################################################################### |
| 915 | 915 | |
| 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}} | |
| 918 | 918 | |
| 919 | 919 | ############################################################################### |
| 920 | 920 | |
| 921 | 921 | fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" |
| 922 | 922 | test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} |
| @@ -947,12 +947,12 @@ | ||
| 947 | 947 | } |
| 948 | 948 | } |
| 949 | 949 | |
| 950 | 950 | ############################################################################### |
| 951 | 951 | |
| 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} | |
| 954 | 954 | |
| 955 | 955 | ############################################################################### |
| 956 | 956 | |
| 957 | 957 | fossil test-th-eval --open-config "globalState configuration" |
| 958 | 958 | test th1-globalState-4 {[string length $RESULT] > 0} |
| @@ -1041,12 +1041,12 @@ | ||
| 1041 | 1041 | fossil test-th-eval "globalState flags" |
| 1042 | 1042 | test th1-globalState-16 {$RESULT eq "0"} |
| 1043 | 1043 | |
| 1044 | 1044 | ############################################################################### |
| 1045 | 1045 | |
| 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 ""} | |
| 1048 | 1048 | |
| 1049 | 1049 | ############################################################################### |
| 1050 | 1050 | |
| 1051 | 1051 | fossil test-th-eval "reinitialize 1; globalState configuration" |
| 1052 | 1052 | test th1-reinitialize-2 {$RESULT ne ""} |
| @@ -1056,29 +1056,29 @@ | ||
| 1056 | 1056 | # |
| 1057 | 1057 | # NOTE: This test will fail if the command names are added to TH1, or |
| 1058 | 1058 | # moved from Tcl builds to plain or the reverse. Sorting the |
| 1059 | 1059 | # command lists eliminates a dependence on order. |
| 1060 | 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 | -} | |
| 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 | 1080 | |
| 1081 | 1081 | ############################################################################### |
| 1082 | 1082 | |
| 1083 | 1083 | fossil test-th-eval "info vars" |
| 1084 | 1084 | |
| @@ -1326,11 +1326,11 @@ | ||
| 1326 | 1326 | |
| 1327 | 1327 | ############################################################################### |
| 1328 | 1328 | |
| 1329 | 1329 | fossil test-th-eval {string is other 123} |
| 1330 | 1330 | 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"} | |
| 1332 | 1332 | |
| 1333 | 1333 | ############################################################################### |
| 1334 | 1334 | |
| 1335 | 1335 | fossil test-th-eval {string is alnum 123} |
| 1336 | 1336 | test th1-string-is-5 {$RESULT eq "1"} |
| 1337 | 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 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 @@ | ||
| 795 | 795 | rpage-\$requested_page\ |
| 796 | 796 | cpage-\$canonical_page\">" [normalize_result]]} |
| 797 | 797 | |
| 798 | 798 | ############################################################################### |
| 799 | 799 | |
| 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}} | |
| 802 | 802 | |
| 803 | 803 | ############################################################################### |
| 804 | 804 | |
| 805 | 805 | test_in_checkout th1-header-2 { |
| 806 | 806 | fossil test-th-eval --open-config "styleHeader {Page Title Here}" |
| 807 | 807 | } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 808 | 808 | |
| 809 | 809 | ############################################################################### |
| 810 | 810 | |
| 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}} | |
| 813 | 813 | |
| 814 | 814 | ############################################################################### |
| 815 | 815 | |
| 816 | 816 | fossil test-th-eval --open-config "styleFooter" |
| 817 | 817 | test th1-footer-2 {$RESULT eq {}} |
| @@ -879,44 +879,44 @@ | ||
| 879 | 879 | test th1-artifact-1 {$RESULT eq \ |
| 880 | 880 | {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} |
| 881 | 881 | |
| 882 | 882 | ############################################################################### |
| 883 | 883 | |
| 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}} | |
| 886 | 886 | |
| 887 | 887 | ############################################################################### |
| 888 | 888 | |
| 889 | 889 | test_in_checkout th1-artifact-3 { |
| 890 | 890 | fossil test-th-eval --open-config "artifact tip" |
| 891 | 891 | } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]} |
| 892 | 892 | |
| 893 | 893 | ############################################################################### |
| 894 | 894 | |
| 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}} | |
| 897 | 897 | |
| 898 | 898 | ############################################################################### |
| 899 | 899 | |
| 900 | 900 | fossil test-th-eval --open-config "artifact 0000000000" |
| 901 | 901 | test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} |
| 902 | 902 | |
| 903 | 903 | ############################################################################### |
| 904 | 904 | |
| 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}} | |
| 907 | 907 | |
| 908 | 908 | ############################################################################### |
| 909 | 909 | |
| 910 | 910 | test_in_checkout th1-artifact-7 { |
| 911 | 911 | fossil test-th-eval --open-config "artifact tip test/th1.test" |
| 912 | 912 | } {[regexp -- {th1-artifact-7} $RESULT]} |
| 913 | 913 | |
| 914 | 914 | ############################################################################### |
| 915 | 915 | |
| 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}} | |
| 918 | 918 | |
| 919 | 919 | ############################################################################### |
| 920 | 920 | |
| 921 | 921 | fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" |
| 922 | 922 | test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} |
| @@ -947,12 +947,12 @@ | ||
| 947 | 947 | } |
| 948 | 948 | } |
| 949 | 949 | |
| 950 | 950 | ############################################################################### |
| 951 | 951 | |
| 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} | |
| 954 | 954 | |
| 955 | 955 | ############################################################################### |
| 956 | 956 | |
| 957 | 957 | fossil test-th-eval --open-config "globalState configuration" |
| 958 | 958 | test th1-globalState-4 {[string length $RESULT] > 0} |
| @@ -1041,12 +1041,12 @@ | ||
| 1041 | 1041 | fossil test-th-eval "globalState flags" |
| 1042 | 1042 | test th1-globalState-16 {$RESULT eq "0"} |
| 1043 | 1043 | |
| 1044 | 1044 | ############################################################################### |
| 1045 | 1045 | |
| 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 ""} | |
| 1048 | 1048 | |
| 1049 | 1049 | ############################################################################### |
| 1050 | 1050 | |
| 1051 | 1051 | fossil test-th-eval "reinitialize 1; globalState configuration" |
| 1052 | 1052 | test th1-reinitialize-2 {$RESULT ne ""} |
| @@ -1056,29 +1056,29 @@ | ||
| 1056 | 1056 | # |
| 1057 | 1057 | # NOTE: This test will fail if the command names are added to TH1, or |
| 1058 | 1058 | # moved from Tcl builds to plain or the reverse. Sorting the |
| 1059 | 1059 | # command lists eliminates a dependence on order. |
| 1060 | 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 | -} | |
| 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 | 1080 | |
| 1081 | 1081 | ############################################################################### |
| 1082 | 1082 | |
| 1083 | 1083 | fossil test-th-eval "info vars" |
| 1084 | 1084 | |
| @@ -1326,11 +1326,11 @@ | ||
| 1326 | 1326 | |
| 1327 | 1327 | ############################################################################### |
| 1328 | 1328 | |
| 1329 | 1329 | fossil test-th-eval {string is other 123} |
| 1330 | 1330 | 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"} | |
| 1332 | 1332 | |
| 1333 | 1333 | ############################################################################### |
| 1334 | 1334 | |
| 1335 | 1335 | fossil test-th-eval {string is alnum 123} |
| 1336 | 1336 | test th1-string-is-5 {$RESULT eq "1"} |
| 1337 | 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 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 @@ | ||
| 81 | 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | 82 | environment variable. The variable can be defined in the CGI |
| 83 | 83 | control file using the [#setenv|<tt>setenv:</tt>] statement. |
| 84 | 84 | |
| 85 | 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 | |
| 86 | +are optional. They are hidden by default. Show them by | |
| 87 | +etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to | |
| 89 | 88 | a string that contains substrings "description" and/or "login-group". |
| 90 | 89 | |
| 91 | 90 | The repolist-generated page recurses into subdirectories and will list |
| 92 | 91 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 93 | 92 | |
| 94 | 93 |
| --- 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 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 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: | |
| 6 | 5 | <ol type="a"> |
| 7 | 6 | <li> The --from can optionally accept a directory name as its argument, |
| 8 | 7 | and uses files under that directory as the baseline for the diff. |
| 9 | 8 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 10 | 9 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| @@ -12,32 +11,35 @@ | ||
| 12 | 11 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 13 | 12 | diff up to date with the latest changes on disk. |
| 14 | 13 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | 14 | diffs of multiple files. |
| 16 | 15 | </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 | |
| 18 | 17 | 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: | |
| 20 | 19 | <ol type="a"> |
| 21 | 20 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 22 | 21 | start page. Or, if the new "--from PATH" option is present, the |
| 23 | 22 | default start page becomes "/ckout?exbase=PATH". |
| 24 | 23 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 25 | 24 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 26 | 25 | person editing this change log has |
| 27 | 26 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 28 | 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. | |
| 29 | 31 | </ol> |
| 30 | - * Enhancements to [/help?cmd=merge|fossil merge]: | |
| 32 | + <li>Enhancements to [/help?cmd=merge|fossil merge]: | |
| 31 | 33 | <ol type="a"> |
| 32 | 34 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 33 | 35 | especially the --tk option to that command, to provide analysis |
| 34 | 36 | of the most recent merge or update operation. |
| 35 | 37 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 36 | 38 | text that shows Fossil's suggested resolution to the conflict. |
| 37 | 39 | </ol> |
| 38 | - * Enhancements to [/help?cmd=commit|fossil commit]: | |
| 40 | + <li>Enhancements to [/help?cmd=commit|fossil commit]: | |
| 39 | 41 | <ol type="a"> |
| 40 | 42 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 41 | 43 | in the check-in comment, it will alert the developer and give |
| 42 | 44 | him or her the opportunity to edit the comment before continuing. |
| 43 | 45 | This feature is controllable by the |
| @@ -49,17 +51,17 @@ | ||
| 49 | 51 | branch has been changed. |
| 50 | 52 | <li> The interactive checkin comment prompt shows the formatting rules |
| 51 | 53 | set for that repository. |
| 52 | 54 | <li> Add the "--editor" option. |
| 53 | 55 | </ol> |
| 54 | - * Deprecate the --comfmtflags and --comment-format global options and | |
| 56 | + <li>Deprecate the --comfmtflags and --comment-format global options and | |
| 55 | 57 | no longer list them in the built-in help, but keep them working for |
| 56 | 58 | backwards compatibility. |
| 57 | 59 | Alternative TTY comment formatting can still be specified using the |
| 58 | 60 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 59 | 61 | 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]: | |
| 61 | 63 | <ol type="a"> |
| 62 | 64 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 63 | 65 | like "rl=" ("Related List") but adds "mionly" style related |
| 64 | 66 | check-ins instead of the full "rel" style. |
| 65 | 67 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -90,27 +92,27 @@ | ||
| 90 | 92 | <li> The saturation and intensity of user-specified checkin and branch |
| 91 | 93 | background colors are automatically adjusted to keep the colors |
| 92 | 94 | compatible with the current skin, unless the |
| 93 | 95 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 94 | 96 | </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 | |
| 96 | 98 | /doc but keeps the title of markdown documents with the document rather |
| 97 | 99 | 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 | |
| 99 | 101 | 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 | |
| 101 | 103 | 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: | |
| 103 | 105 | <ol type="a"> |
| 104 | 106 | <li> Fix a bug in "fossil patch create" that causes |
| 105 | 107 | [/help?cmd=revert|fossil revert] operations that happened |
| 106 | 108 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 107 | 109 | to be omitted from the patch. |
| 108 | 110 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 109 | 111 | aliases for remote checkout names. |
| 110 | 112 | </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: | |
| 112 | 114 | <ol type="a"> |
| 113 | 115 | <li> Add the ability to search the help text, either in the UI |
| 114 | 116 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 115 | 117 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 116 | 118 | <li> Accepts an optional SUBCOMMAND argument following the |
| @@ -117,11 +119,11 @@ | ||
| 117 | 119 | COMMAND argument and only shows results for the specified |
| 118 | 120 | subcommand, not the entire command. |
| 119 | 121 | <li> The -u (--usage) option shows only the command-line syntax |
| 120 | 122 | <li> The -o (--options) option shows only the command-line options |
| 121 | 123 | </ol> |
| 122 | - * Enhancements to the ticket system: | |
| 124 | + <li>Enhancements to the ticket system: | |
| 123 | 125 | <ol type="a"> |
| 124 | 126 | <li> Added the ability to attach wiki pages to a ticket for extended |
| 125 | 127 | descriptions. |
| 126 | 128 | <li> Added submenu to the 'View Ticket' page, to use it as |
| 127 | 129 | template for a new ticket. |
| @@ -129,21 +131,35 @@ | ||
| 129 | 131 | in a row. |
| 130 | 132 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 131 | 133 | <li> Show creation time in report and ticket view. |
| 132 | 134 | <li> Show previous comments in edit ticket as reference. |
| 133 | 135 | </ol> |
| 134 | - * Added the "hash" query parameter to the | |
| 136 | + <li>Added the "hash" query parameter to the | |
| 135 | 137 | [/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] | |
| 137 | 139 | which alerts subscribers when an admin creates a new user or |
| 138 | 140 | 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 | |
| 141 | 143 | the frequency of reconnection attempts over time and providing feedback |
| 142 | 144 | 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> | |
| 145 | 161 | |
| 146 | 162 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 147 | 163 | |
| 148 | 164 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 149 | 165 | that have non-ASCII filenames |
| 150 | 166 |
| --- 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 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 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: | |
| 6 | 5 | <ol type="a"> |
| 7 | 6 | <li> The --from can optionally accept a directory name as its argument, |
| 8 | 7 | and uses files under that directory as the baseline for the diff. |
| 9 | 8 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 10 | 9 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| @@ -12,32 +11,35 @@ | ||
| 12 | 11 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 13 | 12 | diff up to date with the latest changes on disk. |
| 14 | 13 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | 14 | diffs of multiple files. |
| 16 | 15 | </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 | |
| 18 | 17 | 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: | |
| 20 | 19 | <ol type="a"> |
| 21 | 20 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 22 | 21 | start page. Or, if the new "--from PATH" option is present, the |
| 23 | 22 | default start page becomes "/ckout?exbase=PATH". |
| 24 | 23 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 25 | 24 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 26 | 25 | person editing this change log has |
| 27 | 26 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 28 | 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. | |
| 29 | 31 | </ol> |
| 30 | - * Enhancements to [/help?cmd=merge|fossil merge]: | |
| 32 | + <li>Enhancements to [/help?cmd=merge|fossil merge]: | |
| 31 | 33 | <ol type="a"> |
| 32 | 34 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 33 | 35 | especially the --tk option to that command, to provide analysis |
| 34 | 36 | of the most recent merge or update operation. |
| 35 | 37 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 36 | 38 | text that shows Fossil's suggested resolution to the conflict. |
| 37 | 39 | </ol> |
| 38 | - * Enhancements to [/help?cmd=commit|fossil commit]: | |
| 40 | + <li>Enhancements to [/help?cmd=commit|fossil commit]: | |
| 39 | 41 | <ol type="a"> |
| 40 | 42 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 41 | 43 | in the check-in comment, it will alert the developer and give |
| 42 | 44 | him or her the opportunity to edit the comment before continuing. |
| 43 | 45 | This feature is controllable by the |
| @@ -49,17 +51,17 @@ | ||
| 49 | 51 | branch has been changed. |
| 50 | 52 | <li> The interactive checkin comment prompt shows the formatting rules |
| 51 | 53 | set for that repository. |
| 52 | 54 | <li> Add the "--editor" option. |
| 53 | 55 | </ol> |
| 54 | - * Deprecate the --comfmtflags and --comment-format global options and | |
| 56 | + <li>Deprecate the --comfmtflags and --comment-format global options and | |
| 55 | 57 | no longer list them in the built-in help, but keep them working for |
| 56 | 58 | backwards compatibility. |
| 57 | 59 | Alternative TTY comment formatting can still be specified using the |
| 58 | 60 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 59 | 61 | 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]: | |
| 61 | 63 | <ol type="a"> |
| 62 | 64 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 63 | 65 | like "rl=" ("Related List") but adds "mionly" style related |
| 64 | 66 | check-ins instead of the full "rel" style. |
| 65 | 67 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -90,27 +92,27 @@ | ||
| 90 | 92 | <li> The saturation and intensity of user-specified checkin and branch |
| 91 | 93 | background colors are automatically adjusted to keep the colors |
| 92 | 94 | compatible with the current skin, unless the |
| 93 | 95 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 94 | 96 | </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 | |
| 96 | 98 | /doc but keeps the title of markdown documents with the document rather |
| 97 | 99 | 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 | |
| 99 | 101 | 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 | |
| 101 | 103 | 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: | |
| 103 | 105 | <ol type="a"> |
| 104 | 106 | <li> Fix a bug in "fossil patch create" that causes |
| 105 | 107 | [/help?cmd=revert|fossil revert] operations that happened |
| 106 | 108 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 107 | 109 | to be omitted from the patch. |
| 108 | 110 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 109 | 111 | aliases for remote checkout names. |
| 110 | 112 | </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: | |
| 112 | 114 | <ol type="a"> |
| 113 | 115 | <li> Add the ability to search the help text, either in the UI |
| 114 | 116 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 115 | 117 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 116 | 118 | <li> Accepts an optional SUBCOMMAND argument following the |
| @@ -117,11 +119,11 @@ | ||
| 117 | 119 | COMMAND argument and only shows results for the specified |
| 118 | 120 | subcommand, not the entire command. |
| 119 | 121 | <li> The -u (--usage) option shows only the command-line syntax |
| 120 | 122 | <li> The -o (--options) option shows only the command-line options |
| 121 | 123 | </ol> |
| 122 | - * Enhancements to the ticket system: | |
| 124 | + <li>Enhancements to the ticket system: | |
| 123 | 125 | <ol type="a"> |
| 124 | 126 | <li> Added the ability to attach wiki pages to a ticket for extended |
| 125 | 127 | descriptions. |
| 126 | 128 | <li> Added submenu to the 'View Ticket' page, to use it as |
| 127 | 129 | template for a new ticket. |
| @@ -129,21 +131,35 @@ | ||
| 129 | 131 | in a row. |
| 130 | 132 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 131 | 133 | <li> Show creation time in report and ticket view. |
| 132 | 134 | <li> Show previous comments in edit ticket as reference. |
| 133 | 135 | </ol> |
| 134 | - * Added the "hash" query parameter to the | |
| 136 | + <li>Added the "hash" query parameter to the | |
| 135 | 137 | [/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] | |
| 137 | 139 | which alerts subscribers when an admin creates a new user or |
| 138 | 140 | 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 | |
| 141 | 143 | the frequency of reconnection attempts over time and providing feedback |
| 142 | 144 | 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> | |
| 145 | 161 | |
| 146 | 162 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 147 | 163 | |
| 148 | 164 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 149 | 165 | that have non-ASCII filenames |
| 150 | 166 |
| --- 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 |
+75
-65
| --- www/quickstart.wiki | ||
| +++ www/quickstart.wiki | ||
| @@ -19,15 +19,13 @@ | ||
| 19 | 19 | This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC |
| 20 | 20 | </b></pre> |
| 21 | 21 | |
| 22 | 22 | <h2 id="workflow" name="fslclone">General Work Flow</h2> |
| 23 | 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: | |
| 24 | +Fossil works with [./glossary.md#repository | repository files] | |
| 25 | +and [./glossary.md#check-out | check-out directories] using a | |
| 26 | +workflow like this: | |
| 29 | 27 | |
| 30 | 28 | <ul> |
| 31 | 29 | <li>Create or clone a repository file. ([/help/init|fossil init] or |
| 32 | 30 | [/help/clone | fossil clone]) |
| 33 | 31 | <li>Check out a local tree. ([/help/open | fossil open]) |
| @@ -41,12 +39,11 @@ | ||
| 41 | 39 | The following sections give a brief overview of these |
| 42 | 40 | operations. |
| 43 | 41 | |
| 44 | 42 | <h2 id="new">Starting A New Project</h2> |
| 45 | 43 | |
| 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]: | |
| 48 | 45 | |
| 49 | 46 | <pre><b>fossil init</b> <i>repository-filename</i> |
| 50 | 47 | </pre> |
| 51 | 48 | |
| 52 | 49 | You can name the database anything you like, and you can place it anywhere in the filesystem. |
| @@ -82,14 +79,14 @@ | ||
| 82 | 79 | <h2 id="clone">Cloning An Existing Repository</h2> |
| 83 | 80 | |
| 84 | 81 | Most fossil operations interact with a repository that is on the |
| 85 | 82 | local disk drive, not on a remote system. Hence, before accessing |
| 86 | 83 | 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]". | |
| 89 | 86 | |
| 90 | -Clone a remote repository as follows: ([/help/clone | more info]) | |
| 87 | +This is done as follows: | |
| 91 | 88 | |
| 92 | 89 | <pre><b>fossil clone</b> <i>URL repository-filename</i> |
| 93 | 90 | </pre> |
| 94 | 91 | |
| 95 | 92 | The <i>URL</i> specifies the fossil repository |
| @@ -107,12 +104,20 @@ | ||
| 107 | 104 | 100% complete... |
| 108 | 105 | Extra delta compression... |
| 109 | 106 | Vacuuming the database... |
| 110 | 107 | project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 |
| 111 | 108 | server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 |
| 112 | -admin-user: exampleuser (password is "yoWgDR42iv")> | |
| 109 | +admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")> | |
| 113 | 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. | |
| 114 | 119 | |
| 115 | 120 | If the remote repository requires a login, include a |
| 116 | 121 | userid in the URL like this: |
| 117 | 122 | |
| 118 | 123 | <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre> |
| @@ -153,26 +158,23 @@ | ||
| 153 | 158 | |
| 154 | 159 | <h2 id="checkout">Checking Out A Local Tree</h2> |
| 155 | 160 | |
| 156 | 161 | To work on a project in fossil, you need to check out a local |
| 157 | 162 | 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: | |
| 160 | 164 | |
| 161 | 165 | <pre><b>fossil open</b> <i>repository-filename</i></pre> |
| 162 | 166 | |
| 163 | -for example: | |
| 167 | +For example: | |
| 164 | 168 | |
| 165 | 169 | <pre><b>fossil open ../myclone.fossil |
| 166 | 170 | BUILD.txt |
| 167 | 171 | COPYRIGHT-BSD2.txt |
| 168 | 172 | README.md |
| 169 | 173 | ︙ |
| 170 | 174 | </tt></b></pre> |
| 171 | 175 | |
| 172 | -(or "fossil open ..\myclone.fossil" on Windows). | |
| 173 | - | |
| 174 | 176 | This leaves you with the newest version of the tree |
| 175 | 177 | checked out. |
| 176 | 178 | From anywhere underneath the root of your local tree, you |
| 177 | 179 | can type commands like the following to find out the status of |
| 178 | 180 | your local tree: |
| @@ -320,41 +322,60 @@ | ||
| 320 | 322 | |
| 321 | 323 | This will get you started on identifying checkins. The |
| 322 | 324 | <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including |
| 323 | 325 | how timestamps can also be used. |
| 324 | 326 | |
| 325 | -<h2 id="config">Configuring Your Local Repository</h2> | |
| 327 | +<h2 id="config">Accessing Your Local Repository's Web User Interface</h2> | |
| 326 | 328 | |
| 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: | |
| 332 | 332 | |
| 333 | 333 | <pre> |
| 334 | 334 | <b>fossil ui</b> <i>repository-filename</i> |
| 335 | 335 | </pre> |
| 336 | 336 | |
| 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>] | |
| 338 | 338 | if you are inside a checked-out local tree. |
| 339 | 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: | |
| 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 | 345 | |
| 346 | 346 | <pre> |
| 347 | 347 | <b>fossil setting web-browser</b> <i>path-to-web-browser</i> |
| 348 | 348 | </pre> |
| 349 | 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. | |
| 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.) | |
| 353 | 371 | |
| 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 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. | |
| 356 | 377 | |
| 357 | 378 | <h2 id="sharing">Sharing Changes</h2> |
| 358 | 379 | |
| 359 | 380 | When [./concepts.wiki#workflow|autosync] is turned off, |
| 360 | 381 | the changes you [/help/commit | commit] are only |
| @@ -464,55 +485,44 @@ | ||
| 464 | 485 | level of undo/redo. |
| 465 | 486 | |
| 466 | 487 | |
| 467 | 488 | <h2 id="server">Setting Up A Server</h2> |
| 468 | 489 | |
| 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: | |
| 471 | 492 | |
| 472 | 493 | <pre> |
| 473 | 494 | <b>[/help/server | fossil server]</b> <i>repository-filename</i> |
| 474 | -<b>[/help/ui | fossil ui]</b> <i>repository-filename</i> | |
| 475 | 495 | </pre> |
| 476 | 496 | |
| 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: | |
| 498 | 510 | |
| 499 | 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] | |
| 500 | 515 | <li>[./server/any/inetd.md|inetd] |
| 501 | 516 | <li>[./server/debian/service.md|systemd] |
| 502 | -<li>[./server/any/cgi.md|CGI] | |
| 503 | -<li>[./server/any/scgi.md|SCGI] | |
| 504 | 517 | </ul> |
| 505 | 518 | |
| 506 | 519 | …along with [./server/#matrix | several other options]. |
| 507 | 520 | |
| 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. | |
| 514 | 524 | |
| 515 | 525 | <h2 id="proxy">HTTP Proxies</h2> |
| 516 | 526 | |
| 517 | 527 | If you are behind a restrictive firewall that requires you to use |
| 518 | 528 | an HTTP proxy to reach the internet, then you can configure the proxy |
| 519 | 529 |
| --- 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 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 @@ | ||
| 68 | 68 | are removed from each token by the command parser.) The third token |
| 69 | 69 | is the `puts "hello"`, with its whitespace and newlines. The fourth token |
| 70 | 70 | is `else` and the fifth and last token is `puts "world"`. |
| 71 | 71 | |
| 72 | 72 | 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 | |
| 74 | 74 | second argument (the third token) as a TH1 script. |
| 75 | 75 | If the expression is false and the third argument is `else`, then |
| 76 | 76 | the fourth argument is evaluated as a TH1 expression. |
| 77 | 77 | |
| 78 | 78 | So, you see, even though the example above spans five lines, it is really |
| @@ -106,10 +106,49 @@ | ||
| 106 | 106 | $repository "" info trunk]]] end] |
| 107 | 107 | |
| 108 | 108 | Those backslashes allow the command to wrap nicely within a standard |
| 109 | 109 | terminal width while telling the interpreter to consider those three |
| 110 | 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". | |
| 111 | 150 | |
| 112 | 151 | |
| 113 | 152 | Summary of Core TH1 Commands |
| 114 | 153 | ---------------------------- |
| 115 | 154 | |
| @@ -147,10 +186,13 @@ | ||
| 147 | 186 | * string last NEEDLE HAYSTACK ?START-INDEX? |
| 148 | 187 | * string match PATTERN STRING |
| 149 | 188 | * string length STRING |
| 150 | 189 | * string range STRING FIRST LAST |
| 151 | 190 | * string repeat STRING COUNT |
| 191 | + * string trim STRING | |
| 192 | + * string trimleft STRING | |
| 193 | + * string trimright STRING | |
| 152 | 194 | * unset VARNAME |
| 153 | 195 | * uplevel ?LEVEL? SCRIPT |
| 154 | 196 | * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? |
| 155 | 197 | |
| 156 | 198 | All of the above commands work as in the original Tcl. Refer to the |
| @@ -182,11 +224,10 @@ | ||
| 182 | 224 | * [copybtn](#copybtn) |
| 183 | 225 | * [date](#date) |
| 184 | 226 | * [decorate](#decorate) |
| 185 | 227 | * [defHeader](#defHeader) |
| 186 | 228 | * [dir](#dir) |
| 187 | - * [enable\_htmlify](#enable_htmlify) | |
| 188 | 229 | * [enable\_output](#enable_output) |
| 189 | 230 | * [encode64](#encode64) |
| 190 | 231 | * [getParameter](#getParameter) |
| 191 | 232 | * [glob\_match](#glob_match) |
| 192 | 233 | * [globalState](#globalState) |
| @@ -214,17 +255,19 @@ | ||
| 214 | 255 | * [stime](#stime) |
| 215 | 256 | * [styleHeader](#styleHeader) |
| 216 | 257 | * [styleFooter](#styleFooter) |
| 217 | 258 | * [styleScript](#styleScript) |
| 218 | 259 | * [submenu](#submenu) |
| 260 | + * [taint](#taintCmd) | |
| 219 | 261 | * [tclEval](#tclEval) |
| 220 | 262 | * [tclExpr](#tclExpr) |
| 221 | 263 | * [tclInvoke](#tclInvoke) |
| 222 | 264 | * [tclIsSafe](#tclIsSafe) |
| 223 | 265 | * [tclMakeSafe](#tclMakeSafe) |
| 224 | 266 | * [tclReady](#tclReady) |
| 225 | 267 | * [trace](#trace) |
| 268 | + * [untaint](#untaintCmd) | |
| 226 | 269 | * [unversioned content](#unversioned_content) |
| 227 | 270 | * [unversioned list](#unversioned_list) |
| 228 | 271 | * [utime](#utime) |
| 229 | 272 | * [verifyCsrf](#verifyCsrf) |
| 230 | 273 | * [verifyLogin](#verifyLogin) |
| @@ -413,28 +456,10 @@ | ||
| 413 | 456 | the files matching the pattern GLOB within CHECKIN will be returned. |
| 414 | 457 | If DETAILS is non-zero, the result will be a list-of-lists, with each |
| 415 | 458 | element containing at least three elements: the file name, the file |
| 416 | 459 | size (in bytes), and the file last modification time (relative to the |
| 417 | 460 | 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 | 461 | |
| 436 | 462 | <a id="enable_output"></a>TH1 enable\_output Command |
| 437 | 463 | ------------------------------------------------------ |
| 438 | 464 | |
| 439 | 465 | * enable\_output BOOLEAN |
| @@ -528,22 +553,24 @@ | ||
| 528 | 553 | ----------------------------------- |
| 529 | 554 | |
| 530 | 555 | * html STRING |
| 531 | 556 | |
| 532 | 557 | 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 | |
| 534 | 559 | 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 | |
| 536 | 561 | [puts](#puts) command to output text that might contain unescaped |
| 537 | 562 | HTML markup. |
| 538 | 563 | |
| 539 | 564 | **Beware of XSS attacks!** If the STRING value to the html command |
| 540 | 565 | can be controlled by a hostile user, then he might be able to sneak |
| 541 | 566 | in malicious HTML or Javascript which could result in a |
| 542 | 567 | cross-site scripting (XSS) attack. Be careful that all text that |
| 543 | 568 | 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. | |
| 545 | 572 | |
| 546 | 573 | <a id="htmlize"></a>TH1 htmlize Command |
| 547 | 574 | ----------------------------------------- |
| 548 | 575 | |
| 549 | 576 | * htmlize STRING |
| @@ -610,11 +637,13 @@ | ||
| 610 | 637 | * puts STRING |
| 611 | 638 | |
| 612 | 639 | Outputs STRING. Characters within STRING that have special meaning |
| 613 | 640 | in HTML are escaped prior to being output. Thus is it safe for STRING |
| 614 | 641 | 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. | |
| 616 | 645 | |
| 617 | 646 | <a id="query"></a>TH1 query Command |
| 618 | 647 | ------------------------------------- |
| 619 | 648 | |
| 620 | 649 | * query ?-nocomplain? SQL CODE |
| @@ -622,11 +651,14 @@ | ||
| 622 | 651 | Runs the SQL query given by the SQL argument. For each row in the result |
| 623 | 652 | set, run CODE. |
| 624 | 653 | |
| 625 | 654 | In SQL, parameters such as $var are filled in using the value of variable |
| 626 | 655 | "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). | |
| 628 | 660 | |
| 629 | 661 | **Beware of SQL injections in the `query` command!** |
| 630 | 662 | The SQL argument to the query command should always be literal SQL |
| 631 | 663 | text enclosed in {...}. The SQL argument should never be a double-quoted |
| 632 | 664 | string or the value of a \$variable, as those constructs can lead to |
| @@ -649,10 +681,14 @@ | ||
| 649 | 681 | ~~~ |
| 650 | 682 | |
| 651 | 683 | In this second example, TH1 does the expansion of `$mykey` prior to passing |
| 652 | 684 | the text down into SQLite. So if `$mykey` contains a single-quote character, |
| 653 | 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. | |
| 654 | 690 | |
| 655 | 691 | <a id="randhex"></a>TH1 randhex Command |
| 656 | 692 | ----------------------------------------- |
| 657 | 693 | |
| 658 | 694 | * randhex N |
| @@ -783,10 +819,24 @@ | ||
| 783 | 819 | |
| 784 | 820 | * submenu link LABEL URL |
| 785 | 821 | |
| 786 | 822 | Add hyperlink to the submenu of the current page. |
| 787 | 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 | + | |
| 788 | 838 | <a id="tclEval"></a>TH1 tclEval Command |
| 789 | 839 | ----------------------------------------- |
| 790 | 840 | |
| 791 | 841 | **This command requires the Tcl integration feature.** |
| 792 | 842 | |
| @@ -854,10 +904,22 @@ | ||
| 854 | 904 | |
| 855 | 905 | * trace STRING |
| 856 | 906 | |
| 857 | 907 | Generates a TH1 trace message if TH1 tracing is enabled. |
| 858 | 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 | + | |
| 859 | 921 | <a id="unversioned_content"></a>TH1 unversioned content Command |
| 860 | 922 | ----------------------------------------------------------------- |
| 861 | 923 | |
| 862 | 924 | * unversioned content FILENAME |
| 863 | 925 | |
| 864 | 926 |
| --- 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 @@ | ||
| 68 | 68 | are removed from each token by the command parser.) The third token |
| 69 | 69 | is the `puts "hello"`, with its whitespace and newlines. The fourth token |
| 70 | 70 | is `else` and the fifth and last token is `puts "world"`. |
| 71 | 71 | |
| 72 | 72 | 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 | |
| 74 | 74 | second argument (the third token) as a TH1 script. |
| 75 | 75 | If the expression is false and the third argument is `else`, then |
| 76 | 76 | the fourth argument is evaluated as a TH1 expression. |
| 77 | 77 | |
| 78 | 78 | So, you see, even though the example above spans five lines, it is really |
| @@ -106,10 +106,49 @@ | ||
| 106 | 106 | $repository "" info trunk]]] end] |
| 107 | 107 | |
| 108 | 108 | Those backslashes allow the command to wrap nicely within a standard |
| 109 | 109 | terminal width while telling the interpreter to consider those three |
| 110 | 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". | |
| 111 | 150 | |
| 112 | 151 | |
| 113 | 152 | Summary of Core TH1 Commands |
| 114 | 153 | ---------------------------- |
| 115 | 154 | |
| @@ -147,10 +186,13 @@ | ||
| 147 | 186 | * string last NEEDLE HAYSTACK ?START-INDEX? |
| 148 | 187 | * string match PATTERN STRING |
| 149 | 188 | * string length STRING |
| 150 | 189 | * string range STRING FIRST LAST |
| 151 | 190 | * string repeat STRING COUNT |
| 191 | + * string trim STRING | |
| 192 | + * string trimleft STRING | |
| 193 | + * string trimright STRING | |
| 152 | 194 | * unset VARNAME |
| 153 | 195 | * uplevel ?LEVEL? SCRIPT |
| 154 | 196 | * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? |
| 155 | 197 | |
| 156 | 198 | All of the above commands work as in the original Tcl. Refer to the |
| @@ -182,11 +224,10 @@ | ||
| 182 | 224 | * [copybtn](#copybtn) |
| 183 | 225 | * [date](#date) |
| 184 | 226 | * [decorate](#decorate) |
| 185 | 227 | * [defHeader](#defHeader) |
| 186 | 228 | * [dir](#dir) |
| 187 | - * [enable\_htmlify](#enable_htmlify) | |
| 188 | 229 | * [enable\_output](#enable_output) |
| 189 | 230 | * [encode64](#encode64) |
| 190 | 231 | * [getParameter](#getParameter) |
| 191 | 232 | * [glob\_match](#glob_match) |
| 192 | 233 | * [globalState](#globalState) |
| @@ -214,17 +255,19 @@ | ||
| 214 | 255 | * [stime](#stime) |
| 215 | 256 | * [styleHeader](#styleHeader) |
| 216 | 257 | * [styleFooter](#styleFooter) |
| 217 | 258 | * [styleScript](#styleScript) |
| 218 | 259 | * [submenu](#submenu) |
| 260 | + * [taint](#taintCmd) | |
| 219 | 261 | * [tclEval](#tclEval) |
| 220 | 262 | * [tclExpr](#tclExpr) |
| 221 | 263 | * [tclInvoke](#tclInvoke) |
| 222 | 264 | * [tclIsSafe](#tclIsSafe) |
| 223 | 265 | * [tclMakeSafe](#tclMakeSafe) |
| 224 | 266 | * [tclReady](#tclReady) |
| 225 | 267 | * [trace](#trace) |
| 268 | + * [untaint](#untaintCmd) | |
| 226 | 269 | * [unversioned content](#unversioned_content) |
| 227 | 270 | * [unversioned list](#unversioned_list) |
| 228 | 271 | * [utime](#utime) |
| 229 | 272 | * [verifyCsrf](#verifyCsrf) |
| 230 | 273 | * [verifyLogin](#verifyLogin) |
| @@ -413,28 +456,10 @@ | ||
| 413 | 456 | the files matching the pattern GLOB within CHECKIN will be returned. |
| 414 | 457 | If DETAILS is non-zero, the result will be a list-of-lists, with each |
| 415 | 458 | element containing at least three elements: the file name, the file |
| 416 | 459 | size (in bytes), and the file last modification time (relative to the |
| 417 | 460 | 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 | 461 | |
| 436 | 462 | <a id="enable_output"></a>TH1 enable\_output Command |
| 437 | 463 | ------------------------------------------------------ |
| 438 | 464 | |
| 439 | 465 | * enable\_output BOOLEAN |
| @@ -528,22 +553,24 @@ | ||
| 528 | 553 | ----------------------------------- |
| 529 | 554 | |
| 530 | 555 | * html STRING |
| 531 | 556 | |
| 532 | 557 | 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 | |
| 534 | 559 | 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 | |
| 536 | 561 | [puts](#puts) command to output text that might contain unescaped |
| 537 | 562 | HTML markup. |
| 538 | 563 | |
| 539 | 564 | **Beware of XSS attacks!** If the STRING value to the html command |
| 540 | 565 | can be controlled by a hostile user, then he might be able to sneak |
| 541 | 566 | in malicious HTML or Javascript which could result in a |
| 542 | 567 | cross-site scripting (XSS) attack. Be careful that all text that |
| 543 | 568 | 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. | |
| 545 | 572 | |
| 546 | 573 | <a id="htmlize"></a>TH1 htmlize Command |
| 547 | 574 | ----------------------------------------- |
| 548 | 575 | |
| 549 | 576 | * htmlize STRING |
| @@ -610,11 +637,13 @@ | ||
| 610 | 637 | * puts STRING |
| 611 | 638 | |
| 612 | 639 | Outputs STRING. Characters within STRING that have special meaning |
| 613 | 640 | in HTML are escaped prior to being output. Thus is it safe for STRING |
| 614 | 641 | 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. | |
| 616 | 645 | |
| 617 | 646 | <a id="query"></a>TH1 query Command |
| 618 | 647 | ------------------------------------- |
| 619 | 648 | |
| 620 | 649 | * query ?-nocomplain? SQL CODE |
| @@ -622,11 +651,14 @@ | ||
| 622 | 651 | Runs the SQL query given by the SQL argument. For each row in the result |
| 623 | 652 | set, run CODE. |
| 624 | 653 | |
| 625 | 654 | In SQL, parameters such as $var are filled in using the value of variable |
| 626 | 655 | "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). | |
| 628 | 660 | |
| 629 | 661 | **Beware of SQL injections in the `query` command!** |
| 630 | 662 | The SQL argument to the query command should always be literal SQL |
| 631 | 663 | text enclosed in {...}. The SQL argument should never be a double-quoted |
| 632 | 664 | string or the value of a \$variable, as those constructs can lead to |
| @@ -649,10 +681,14 @@ | ||
| 649 | 681 | ~~~ |
| 650 | 682 | |
| 651 | 683 | In this second example, TH1 does the expansion of `$mykey` prior to passing |
| 652 | 684 | the text down into SQLite. So if `$mykey` contains a single-quote character, |
| 653 | 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. | |
| 654 | 690 | |
| 655 | 691 | <a id="randhex"></a>TH1 randhex Command |
| 656 | 692 | ----------------------------------------- |
| 657 | 693 | |
| 658 | 694 | * randhex N |
| @@ -783,10 +819,24 @@ | ||
| 783 | 819 | |
| 784 | 820 | * submenu link LABEL URL |
| 785 | 821 | |
| 786 | 822 | Add hyperlink to the submenu of the current page. |
| 787 | 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 | + | |
| 788 | 838 | <a id="tclEval"></a>TH1 tclEval Command |
| 789 | 839 | ----------------------------------------- |
| 790 | 840 | |
| 791 | 841 | **This command requires the Tcl integration feature.** |
| 792 | 842 | |
| @@ -854,10 +904,22 @@ | ||
| 854 | 904 | |
| 855 | 905 | * trace STRING |
| 856 | 906 | |
| 857 | 907 | Generates a TH1 trace message if TH1 tracing is enabled. |
| 858 | 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 | + | |
| 859 | 921 | <a id="unversioned_content"></a>TH1 unversioned content Command |
| 860 | 922 | ----------------------------------------------------------------- |
| 861 | 923 | |
| 862 | 924 | * unversioned content FILENAME |
| 863 | 925 | |
| 864 | 926 |
| --- 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 |