Fossil SCM

Merge in trunk (check-in [f741baa6be])

george 2020-08-26 15:52 wiki-history merge
Commit 5d4a57f2c04f5fe08fe119c30db813f47ddadd6d367a714d66a05d0fe47e7539
89 files changed +1 -1 +5 -2 +3 +1 -1 +2 -2 +2 -1 +7 -5 +6 +1 -1 +2 -1 +4 +2 +32 -6 +6 -12 +5 +45 -16 +1 -1 +327 -120 +158 -23 +2 -2 +49 -40 +451 -530 +12 +7 -4 +3 +97 -2 +7 +2 -2 +1 -1 +2 -2 +40 +1 -1 +6 -23 +8 +2 +1 +140 -47 +26 -3 +6 +3 -2 +21 -2 +3 -3 +15 +15 +1 +5 -6 +1 -1 +7 -3 +6 +7 -1 +2 +3 +14 +1 -1 +10 +27 +6 +14 +1 -1 +40 +2 -2 +9 -2 +1 -2 +29 -18 +3 -3 +5 +11 -1 +6 -1 +6 -1 +11 -3 +58 -48 +6 +121 +10 -4 +15 +22 -13 +16 +13 -11 +1 -1 +12 -9 +1 -1 +2 -2 +104 +443 -129 +1 +2 +7 -18 +1 -1
~ VERSION ~ auto.def ~ skins/ardoise/css.txt ~ skins/eagle/css.txt ~ skins/xekri/css.txt ~ src/ajax.c ~ src/alerts.c ~ src/allrepo.c ~ src/attach.c ~ src/backlink.c ~ src/backoffice.c ~ src/captcha.c ~ src/cgi.c ~ src/checkin.c ~ src/clone.c ~ src/configure.c ~ src/copybtn.js ~ src/db.c ~ src/default.css ~ src/diff.c ~ src/file.c ~ src/forum.c ~ src/fossil.bootstrap.js ~ src/fossil.confirmer.js ~ src/fossil.copybutton.js ~ src/fossil.dom.js ~ src/fossil.numbered-lines.js ~ src/fossil.page.fileedit.js ~ src/fossil.page.forumpost.js ~ src/fossil.page.wikiedit.js ~ src/fossil.popupwidget.js ~ src/fossil.storage.js ~ src/fossil.tabs.js ~ src/hook.c ~ src/http_ssl.c ~ src/import.c ~ src/info.c ~ src/interwiki.c ~ src/json_config.c ~ src/json_user.c ~ src/json_wiki.c ~ src/login.c ~ src/main.c ~ src/main.mk ~ src/main.mk ~ src/makemake.tcl ~ src/manifest.c ~ src/markdown.c ~ src/markdown.md ~ src/mkbuiltin.c ~ src/mkindex.c ~ src/moderate.c ~ src/printf.c ~ src/rebuild.c ~ src/scroll.js ~ src/security_audit.c ~ src/setup.c ~ src/setupuser.c ~ src/skins.c ~ src/sounds/README.md ~ src/sqlcmd.c ~ src/stat.c ~ src/sync.c ~ src/tkt.c ~ src/translate.c ~ src/url.c ~ src/user.c ~ src/util.c ~ src/wiki.c ~ src/wiki.c ~ src/wiki.wiki ~ src/wikiformat.c ~ src/xfer.c ~ test/reserved-names.test ~ win/Makefile.dmc ~ win/Makefile.mingw ~ win/Makefile.mingw.mistachkin ~ win/Makefile.msc ~ www/changes.wiki ~ www/chroot.md ~ www/customskin.md ~ www/fileformat.wiki ~ www/index.wiki ~ www/interwiki.md ~ www/javascript.md ~ www/mkindex.tcl ~ www/permutedindex.html ~ www/quotes.wiki ~ www/server/any/scgi.md
+1 -1
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1
-2.12.1
1
+2.13
22
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.12.1
2
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.13
2
+5 -2
--- auto.def
+++ auto.def
@@ -176,12 +176,15 @@
176176
}
177177
if {!$ok} {
178178
user-error "unable to compile SQLite compatibility test program"
179179
}
180180
set err [catch {exec-with-stderr ./conftest__} result errinfo]
181
- if {$err} {
182
- user-error $result
181
+ if {[get-define build] eq [get-define host]} {
182
+ set err [catch {exec-with-stderr ./conftest__} result errinfo]
183
+ if {$err} {
184
+ user-error $result
185
+ }
183186
}
184187
file delete ./conftest__
185188
}
186189
test_system_sqlite
187190
188191
--- auto.def
+++ auto.def
@@ -176,12 +176,15 @@
176 }
177 if {!$ok} {
178 user-error "unable to compile SQLite compatibility test program"
179 }
180 set err [catch {exec-with-stderr ./conftest__} result errinfo]
181 if {$err} {
182 user-error $result
 
 
 
183 }
184 file delete ./conftest__
185 }
186 test_system_sqlite
187
188
--- auto.def
+++ auto.def
@@ -176,12 +176,15 @@
176 }
177 if {!$ok} {
178 user-error "unable to compile SQLite compatibility test program"
179 }
180 set err [catch {exec-with-stderr ./conftest__} result errinfo]
181 if {[get-define build] eq [get-define host]} {
182 set err [catch {exec-with-stderr ./conftest__} result errinfo]
183 if {$err} {
184 user-error $result
185 }
186 }
187 file delete ./conftest__
188 }
189 test_system_sqlite
190
191
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -572,10 +572,13 @@
572572
white-space: nowrap;
573573
background: #000;
574574
border: 2px solid #bbb;
575575
border-radius: 5px
576576
}
577
+table.numbered-lines td.file-content > pre {
578
+ margin-top: -2px/*offset CODE tag border*/;
579
+}
577580
pre > code {
578581
padding: 1rem 1.5rem;
579582
white-space: pre
580583
}
581584
td,
582585
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -572,10 +572,13 @@
572 white-space: nowrap;
573 background: #000;
574 border: 2px solid #bbb;
575 border-radius: 5px
576 }
 
 
 
577 pre > code {
578 padding: 1rem 1.5rem;
579 white-space: pre
580 }
581 td,
582
--- skins/ardoise/css.txt
+++ skins/ardoise/css.txt
@@ -572,10 +572,13 @@
572 white-space: nowrap;
573 background: #000;
574 border: 2px solid #bbb;
575 border-radius: 5px
576 }
577 table.numbered-lines td.file-content > pre {
578 margin-top: -2px/*offset CODE tag border*/;
579 }
580 pre > code {
581 padding: 1rem 1.5rem;
582 white-space: pre
583 }
584 td,
585
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -399,11 +399,11 @@
399399
400400
div.filetreeline:hover {
401401
background-color: #7EA2D9;
402402
}
403403
404
-div.selectedText {
404
+table.numbered-lines td.line-numbers span.selected-line {
405405
background-color: #7EA2D9;
406406
}
407407
408408
.statistics-report-graph-line {
409409
background-color: #7EA2D9;
410410
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -399,11 +399,11 @@
399
400 div.filetreeline:hover {
401 background-color: #7EA2D9;
402 }
403
404 div.selectedText {
405 background-color: #7EA2D9;
406 }
407
408 .statistics-report-graph-line {
409 background-color: #7EA2D9;
410
--- skins/eagle/css.txt
+++ skins/eagle/css.txt
@@ -399,11 +399,11 @@
399
400 div.filetreeline:hover {
401 background-color: #7EA2D9;
402 }
403
404 table.numbered-lines td.line-numbers span.selected-line {
405 background-color: #7EA2D9;
406 }
407
408 .statistics-report-graph-line {
409 background-color: #7EA2D9;
410
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1000,15 +1000,15 @@
10001000
/**************************************
10011001
* Did not encounter these
10021002
*/
10031003
10041004
/* selected lines of text within a linenumbered artifact display */
1005
-div.selectedText {
1005
+table.numbered-lines td.line-numbers span.selected-line {
10061006
font-weight: bold;
10071007
color: #00f;
10081008
background-color: #d5d5ff;
1009
- border: 1px #00f solid;
1009
+ border-color: #00f;
10101010
}
10111011
10121012
/* format for missing privileges note on user setup page */
10131013
p.missingPriv {
10141014
color: #00f;
10151015
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1000,15 +1000,15 @@
1000 /**************************************
1001 * Did not encounter these
1002 */
1003
1004 /* selected lines of text within a linenumbered artifact display */
1005 div.selectedText {
1006 font-weight: bold;
1007 color: #00f;
1008 background-color: #d5d5ff;
1009 border: 1px #00f solid;
1010 }
1011
1012 /* format for missing privileges note on user setup page */
1013 p.missingPriv {
1014 color: #00f;
1015
--- skins/xekri/css.txt
+++ skins/xekri/css.txt
@@ -1000,15 +1000,15 @@
1000 /**************************************
1001 * Did not encounter these
1002 */
1003
1004 /* selected lines of text within a linenumbered artifact display */
1005 table.numbered-lines td.line-numbers span.selected-line {
1006 font-weight: bold;
1007 color: #00f;
1008 background-color: #d5d5ff;
1009 border-color: #00f;
1010 }
1011
1012 /* format for missing privileges note on user setup page */
1013 p.missingPriv {
1014 color: #00f;
1015
+2 -1
--- src/ajax.c
+++ src/ajax.c
@@ -130,11 +130,12 @@
130130
wiki_render_by_mimetype(pContent, zMime);
131131
break;
132132
default:{
133133
const char *zContent = blob_str(pContent);
134134
if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135
- output_text_with_line_numbers(zContent, "on");
135
+ output_text_with_line_numbers(zContent, blob_size(pContent),
136
+ zName, "on");
136137
}else{
137138
const char *zExt = strrchr(zName,'.');
138139
if(zExt && zExt[1]){
139140
CX("<pre><code class='language-%s'>%h</code></pre>",
140141
zExt+1, zContent);
141142
--- src/ajax.c
+++ src/ajax.c
@@ -130,11 +130,12 @@
130 wiki_render_by_mimetype(pContent, zMime);
131 break;
132 default:{
133 const char *zContent = blob_str(pContent);
134 if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135 output_text_with_line_numbers(zContent, "on");
 
136 }else{
137 const char *zExt = strrchr(zName,'.');
138 if(zExt && zExt[1]){
139 CX("<pre><code class='language-%s'>%h</code></pre>",
140 zExt+1, zContent);
141
--- src/ajax.c
+++ src/ajax.c
@@ -130,11 +130,12 @@
130 wiki_render_by_mimetype(pContent, zMime);
131 break;
132 default:{
133 const char *zContent = blob_str(pContent);
134 if(AJAX_PREVIEW_LINE_NUMBERS & flags){
135 output_text_with_line_numbers(zContent, blob_size(pContent),
136 zName, "on");
137 }else{
138 const char *zExt = strrchr(zName,'.');
139 if(zExt && zExt[1]){
140 CX("<pre><code class='language-%s'>%h</code></pre>",
141 zExt+1, zContent);
142
+7 -5
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936936
** This is a short name used to identifies the repository in the Subject:
937937
** line of email alerts. Traditionally this name is included in square
938938
** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939939
*/
940940
/*
941
-** SETTING: email-send-method width=5 default=off
941
+** SETTING: email-send-method width=5 default=off sensitive
942942
** Determine the method used to send email. Allowed values are
943943
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944944
** means no email is ever sent. The "relay" value means emails are sent
945945
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946946
** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949949
** by the email-send-dir setting. The "db" value means that emails
950950
** are added to an SQLite database named by the* email-send-db setting.
951951
** The "stdout" value writes email text to standard output, for debugging.
952952
*/
953953
/*
954
-** SETTING: email-send-command width=40
954
+** SETTING: email-send-command width=40 sensitive
955955
** This is a command to which outbound email content is piped when the
956956
** email-send-method is set to "pipe". The command must extract
957957
** recipient, sender, subject, and all other relevant information
958958
** from the email header.
959959
*/
960960
/*
961
-** SETTING: email-send-dir width=40
961
+** SETTING: email-send-dir width=40 sensitive
962962
** This is a directory into which outbound emails are written as individual
963963
** files if the email-send-method is set to "dir".
964964
*/
965965
/*
966
-** SETTING: email-send-db width=40
966
+** SETTING: email-send-db width=40 sensitive
967967
** This is an SQLite database file into which outbound emails are written
968968
** if the email-send-method is set to "db".
969969
*/
970970
/*
971971
** SETTING: email-self width=40
972972
** This is the email address for the repository. Outbound emails add
973973
** this email address as the "From:" field.
974974
*/
975975
/*
976
-** SETTING: email-send-relayhost width=40
976
+** SETTING: email-send-relayhost width=40 sensitive
977977
** This is the hostname and TCP port to which output email messages
978978
** are sent when email-send-method is "relay". There should be an
979979
** SMTP server configured as a Mail Submission Agent listening on the
980980
** designated host and port and all times.
981981
*/
@@ -1769,18 +1769,20 @@
17691769
"UPDATE subscriber SET sverified=1"
17701770
" WHERE subscriberCode=hextoblob(%Q)",
17711771
zName);
17721772
if( db_get_boolean("selfreg-verify",0) ){
17731773
char *zNewCap = db_get("default-perms","u");
1774
+ db_unprotect(PROTECT_USER);
17741775
db_multi_exec(
17751776
"UPDATE user"
17761777
" SET cap=%Q"
17771778
" WHERE cap='7' AND login=("
17781779
" SELECT suname FROM subscriber"
17791780
" WHERE subscriberCode=hextoblob(%Q))",
17801781
zNewCap, zName
17811782
);
1783
+ db_protect_pop();
17821784
login_set_capabilities(zNewCap, 0);
17831785
}
17841786
@ <h1>Your email alert subscription has been verified!</h1>
17851787
@ <p>Use the form below to update your subscription information.</p>
17861788
@ <p>Hint: Bookmark this page so that you can more easily update
17871789
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936 ** This is a short name used to identifies the repository in the Subject:
937 ** line of email alerts. Traditionally this name is included in square
938 ** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939 */
940 /*
941 ** SETTING: email-send-method width=5 default=off
942 ** Determine the method used to send email. Allowed values are
943 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944 ** means no email is ever sent. The "relay" value means emails are sent
945 ** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946 ** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949 ** by the email-send-dir setting. The "db" value means that emails
950 ** are added to an SQLite database named by the* email-send-db setting.
951 ** The "stdout" value writes email text to standard output, for debugging.
952 */
953 /*
954 ** SETTING: email-send-command width=40
955 ** This is a command to which outbound email content is piped when the
956 ** email-send-method is set to "pipe". The command must extract
957 ** recipient, sender, subject, and all other relevant information
958 ** from the email header.
959 */
960 /*
961 ** SETTING: email-send-dir width=40
962 ** This is a directory into which outbound emails are written as individual
963 ** files if the email-send-method is set to "dir".
964 */
965 /*
966 ** SETTING: email-send-db width=40
967 ** This is an SQLite database file into which outbound emails are written
968 ** if the email-send-method is set to "db".
969 */
970 /*
971 ** SETTING: email-self width=40
972 ** This is the email address for the repository. Outbound emails add
973 ** this email address as the "From:" field.
974 */
975 /*
976 ** SETTING: email-send-relayhost width=40
977 ** This is the hostname and TCP port to which output email messages
978 ** are sent when email-send-method is "relay". There should be an
979 ** SMTP server configured as a Mail Submission Agent listening on the
980 ** designated host and port and all times.
981 */
@@ -1769,18 +1769,20 @@
1769 "UPDATE subscriber SET sverified=1"
1770 " WHERE subscriberCode=hextoblob(%Q)",
1771 zName);
1772 if( db_get_boolean("selfreg-verify",0) ){
1773 char *zNewCap = db_get("default-perms","u");
 
1774 db_multi_exec(
1775 "UPDATE user"
1776 " SET cap=%Q"
1777 " WHERE cap='7' AND login=("
1778 " SELECT suname FROM subscriber"
1779 " WHERE subscriberCode=hextoblob(%Q))",
1780 zNewCap, zName
1781 );
 
1782 login_set_capabilities(zNewCap, 0);
1783 }
1784 @ <h1>Your email alert subscription has been verified!</h1>
1785 @ <p>Use the form below to update your subscription information.</p>
1786 @ <p>Hint: Bookmark this page so that you can more easily update
1787
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936 ** This is a short name used to identifies the repository in the Subject:
937 ** line of email alerts. Traditionally this name is included in square
938 ** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939 */
940 /*
941 ** SETTING: email-send-method width=5 default=off sensitive
942 ** Determine the method used to send email. Allowed values are
943 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944 ** means no email is ever sent. The "relay" value means emails are sent
945 ** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946 ** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949 ** by the email-send-dir setting. The "db" value means that emails
950 ** are added to an SQLite database named by the* email-send-db setting.
951 ** The "stdout" value writes email text to standard output, for debugging.
952 */
953 /*
954 ** SETTING: email-send-command width=40 sensitive
955 ** This is a command to which outbound email content is piped when the
956 ** email-send-method is set to "pipe". The command must extract
957 ** recipient, sender, subject, and all other relevant information
958 ** from the email header.
959 */
960 /*
961 ** SETTING: email-send-dir width=40 sensitive
962 ** This is a directory into which outbound emails are written as individual
963 ** files if the email-send-method is set to "dir".
964 */
965 /*
966 ** SETTING: email-send-db width=40 sensitive
967 ** This is an SQLite database file into which outbound emails are written
968 ** if the email-send-method is set to "db".
969 */
970 /*
971 ** SETTING: email-self width=40
972 ** This is the email address for the repository. Outbound emails add
973 ** this email address as the "From:" field.
974 */
975 /*
976 ** SETTING: email-send-relayhost width=40 sensitive
977 ** This is the hostname and TCP port to which output email messages
978 ** are sent when email-send-method is "relay". There should be an
979 ** SMTP server configured as a Mail Submission Agent listening on the
980 ** designated host and port and all times.
981 */
@@ -1769,18 +1769,20 @@
1769 "UPDATE subscriber SET sverified=1"
1770 " WHERE subscriberCode=hextoblob(%Q)",
1771 zName);
1772 if( db_get_boolean("selfreg-verify",0) ){
1773 char *zNewCap = db_get("default-perms","u");
1774 db_unprotect(PROTECT_USER);
1775 db_multi_exec(
1776 "UPDATE user"
1777 " SET cap=%Q"
1778 " WHERE cap='7' AND login=("
1779 " SELECT suname FROM subscriber"
1780 " WHERE subscriberCode=hextoblob(%Q))",
1781 zNewCap, zName
1782 );
1783 db_protect_pop();
1784 login_set_capabilities(zNewCap, 0);
1785 }
1786 @ <h1>Your email alert subscription has been verified!</h1>
1787 @ <p>Use the form below to update your subscription information.</p>
1788 @ <p>Hint: Bookmark this page so that you can more easily update
1789
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299299
useCheckouts?"ckout":"repo", blob_str(&fn)
300300
);
301301
if( dryRunFlag ){
302302
fossil_print("%s\n", blob_sql_text(&sql));
303303
}else{
304
+ db_unprotect(PROTECT_CONFIG);
304305
db_multi_exec("%s", blob_sql_text(&sql));
306
+ db_protect_pop();
305307
}
306308
}
307309
db_end_transaction(0);
308310
blob_reset(&sql);
309311
blob_reset(&fn);
@@ -334,11 +336,13 @@
334336
"VALUES('repo:%q',1)", z
335337
);
336338
if( dryRunFlag ){
337339
fossil_print("%s\n", blob_sql_text(&sql));
338340
}else{
341
+ db_unprotect(PROTECT_CONFIG);
339342
db_multi_exec("%s", blob_sql_text(&sql));
343
+ db_protect_pop();
340344
}
341345
}
342346
db_end_transaction(0);
343347
blob_reset(&sql);
344348
blob_reset(&fn);
@@ -428,9 +432,11 @@
428432
if( nToDel>0 ){
429433
const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
430434
if( dryRunFlag ){
431435
fossil_print("%s\n", zSql);
432436
}else{
437
+ db_unprotect(PROTECT_CONFIG);
433438
db_multi_exec("%s", zSql /*safe-for-%s*/ );
439
+ db_protect_pop();
434440
}
435441
}
436442
}
437443
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299 useCheckouts?"ckout":"repo", blob_str(&fn)
300 );
301 if( dryRunFlag ){
302 fossil_print("%s\n", blob_sql_text(&sql));
303 }else{
 
304 db_multi_exec("%s", blob_sql_text(&sql));
 
305 }
306 }
307 db_end_transaction(0);
308 blob_reset(&sql);
309 blob_reset(&fn);
@@ -334,11 +336,13 @@
334 "VALUES('repo:%q',1)", z
335 );
336 if( dryRunFlag ){
337 fossil_print("%s\n", blob_sql_text(&sql));
338 }else{
 
339 db_multi_exec("%s", blob_sql_text(&sql));
 
340 }
341 }
342 db_end_transaction(0);
343 blob_reset(&sql);
344 blob_reset(&fn);
@@ -428,9 +432,11 @@
428 if( nToDel>0 ){
429 const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
430 if( dryRunFlag ){
431 fossil_print("%s\n", zSql);
432 }else{
 
433 db_multi_exec("%s", zSql /*safe-for-%s*/ );
 
434 }
435 }
436 }
437
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299 useCheckouts?"ckout":"repo", blob_str(&fn)
300 );
301 if( dryRunFlag ){
302 fossil_print("%s\n", blob_sql_text(&sql));
303 }else{
304 db_unprotect(PROTECT_CONFIG);
305 db_multi_exec("%s", blob_sql_text(&sql));
306 db_protect_pop();
307 }
308 }
309 db_end_transaction(0);
310 blob_reset(&sql);
311 blob_reset(&fn);
@@ -334,11 +336,13 @@
336 "VALUES('repo:%q',1)", z
337 );
338 if( dryRunFlag ){
339 fossil_print("%s\n", blob_sql_text(&sql));
340 }else{
341 db_unprotect(PROTECT_CONFIG);
342 db_multi_exec("%s", blob_sql_text(&sql));
343 db_protect_pop();
344 }
345 }
346 db_end_transaction(0);
347 blob_reset(&sql);
348 blob_reset(&fn);
@@ -428,9 +432,11 @@
432 if( nToDel>0 ){
433 const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
434 if( dryRunFlag ){
435 fossil_print("%s\n", zSql);
436 }else{
437 db_unprotect(PROTECT_CONFIG);
438 db_multi_exec("%s", zSql /*safe-for-%s*/ );
439 db_protect_pop();
440 }
441 }
442 }
443
+1 -1
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617617
const char *z;
618618
content_get(ridSrc, &attach);
619619
blob_to_utf8_no_bom(&attach, 0);
620620
z = blob_str(&attach);
621621
if( zLn ){
622
- output_text_with_line_numbers(z, zLn);
622
+ output_text_with_line_numbers(z, blob_size(&attach), zName, zLn);
623623
}else{
624624
@ <pre>
625625
@ %h(z)
626626
@ </pre>
627627
}
628628
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617 const char *z;
618 content_get(ridSrc, &attach);
619 blob_to_utf8_no_bom(&attach, 0);
620 z = blob_str(&attach);
621 if( zLn ){
622 output_text_with_line_numbers(z, zLn);
623 }else{
624 @ <pre>
625 @ %h(z)
626 @ </pre>
627 }
628
--- src/attach.c
+++ src/attach.c
@@ -617,11 +617,11 @@
617 const char *z;
618 content_get(ridSrc, &attach);
619 blob_to_utf8_no_bom(&attach, 0);
620 z = blob_str(&attach);
621 if( zLn ){
622 output_text_with_line_numbers(z, blob_size(&attach), zName, zLn);
623 }else{
624 @ <pre>
625 @ %h(z)
626 @ </pre>
627 }
628
+2 -1
--- src/backlink.c
+++ src/backlink.c
@@ -169,11 +169,12 @@
169169
rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d"
170170
" ORDER BY mtime DESC LIMIT 1", tagid);
171171
if( rid==0 ) return;
172172
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
173173
if( pWiki ){
174
- backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, 2, pWiki->rDate,1);
174
+ backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, BKLNK_WIKI,
175
+ pWiki->rDate, 1);
175176
manifest_destroy(pWiki);
176177
}
177178
}
178179
179180
/*
180181
--- src/backlink.c
+++ src/backlink.c
@@ -169,11 +169,12 @@
169 rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d"
170 " ORDER BY mtime DESC LIMIT 1", tagid);
171 if( rid==0 ) return;
172 pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
173 if( pWiki ){
174 backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, 2, pWiki->rDate,1);
 
175 manifest_destroy(pWiki);
176 }
177 }
178
179 /*
180
--- src/backlink.c
+++ src/backlink.c
@@ -169,11 +169,12 @@
169 rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d"
170 " ORDER BY mtime DESC LIMIT 1", tagid);
171 if( rid==0 ) return;
172 pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
173 if( pWiki ){
174 backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, BKLNK_WIKI,
175 pWiki->rDate, 1);
176 manifest_destroy(pWiki);
177 }
178 }
179
180 /*
181
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241241
** process (1) no longer exists and the current time exceeds (2).
242242
*/
243243
static void backofficeReadLease(Lease *pLease){
244244
Stmt q;
245245
memset(pLease, 0, sizeof(*pLease));
246
+ db_unprotect(PROTECT_CONFIG);
246247
db_prepare(&q, "SELECT value FROM repository.config"
247248
" WHERE name='backoffice'");
248249
if( db_step(&q)==SQLITE_ROW ){
249250
const char *z = db_column_text(&q,0);
250251
z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
251252
z = backofficeParseInt(z, &pLease->tmCurrent);
252253
z = backofficeParseInt(z, &pLease->idNext);
253254
backofficeParseInt(z, &pLease->tmNext);
254255
}
255256
db_finalize(&q);
257
+ db_protect_pop();
256258
}
257259
258260
/*
259261
** Return a string that describes how long it has been since the
260262
** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
277279
278280
/*
279281
** Write a lease to the backoffice property
280282
*/
281283
static void backofficeWriteLease(Lease *pLease){
284
+ db_unprotect(PROTECT_CONFIG);
282285
db_multi_exec(
283286
"REPLACE INTO repository.config(name,value,mtime)"
284287
" VALUES('backoffice','%lld %lld %lld %lld',now())",
285288
pLease->idCurrent, pLease->tmCurrent,
286289
pLease->idNext, pLease->tmNext);
290
+ db_protect_pop();
287291
}
288292
289293
/*
290294
** Check to see if the specified Win32 process is still alive. It
291295
** should be noted that even if this function returns non-zero, the
292296
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241 ** process (1) no longer exists and the current time exceeds (2).
242 */
243 static void backofficeReadLease(Lease *pLease){
244 Stmt q;
245 memset(pLease, 0, sizeof(*pLease));
 
246 db_prepare(&q, "SELECT value FROM repository.config"
247 " WHERE name='backoffice'");
248 if( db_step(&q)==SQLITE_ROW ){
249 const char *z = db_column_text(&q,0);
250 z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
251 z = backofficeParseInt(z, &pLease->tmCurrent);
252 z = backofficeParseInt(z, &pLease->idNext);
253 backofficeParseInt(z, &pLease->tmNext);
254 }
255 db_finalize(&q);
 
256 }
257
258 /*
259 ** Return a string that describes how long it has been since the
260 ** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
277
278 /*
279 ** Write a lease to the backoffice property
280 */
281 static void backofficeWriteLease(Lease *pLease){
 
282 db_multi_exec(
283 "REPLACE INTO repository.config(name,value,mtime)"
284 " VALUES('backoffice','%lld %lld %lld %lld',now())",
285 pLease->idCurrent, pLease->tmCurrent,
286 pLease->idNext, pLease->tmNext);
 
287 }
288
289 /*
290 ** Check to see if the specified Win32 process is still alive. It
291 ** should be noted that even if this function returns non-zero, the
292
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241 ** process (1) no longer exists and the current time exceeds (2).
242 */
243 static void backofficeReadLease(Lease *pLease){
244 Stmt q;
245 memset(pLease, 0, sizeof(*pLease));
246 db_unprotect(PROTECT_CONFIG);
247 db_prepare(&q, "SELECT value FROM repository.config"
248 " WHERE name='backoffice'");
249 if( db_step(&q)==SQLITE_ROW ){
250 const char *z = db_column_text(&q,0);
251 z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
252 z = backofficeParseInt(z, &pLease->tmCurrent);
253 z = backofficeParseInt(z, &pLease->idNext);
254 backofficeParseInt(z, &pLease->tmNext);
255 }
256 db_finalize(&q);
257 db_protect_pop();
258 }
259
260 /*
261 ** Return a string that describes how long it has been since the
262 ** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
279
280 /*
281 ** Write a lease to the backoffice property
282 */
283 static void backofficeWriteLease(Lease *pLease){
284 db_unprotect(PROTECT_CONFIG);
285 db_multi_exec(
286 "REPLACE INTO repository.config(name,value,mtime)"
287 " VALUES('backoffice','%lld %lld %lld %lld',now())",
288 pLease->idCurrent, pLease->tmCurrent,
289 pLease->idNext, pLease->tmNext);
290 db_protect_pop();
291 }
292
293 /*
294 ** Check to see if the specified Win32 process is still alive. It
295 ** should be noted that even if this function returns non-zero, the
296
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458458
Blob b;
459459
static char zRes[20];
460460
461461
zSecret = db_get("captcha-secret", 0);
462462
if( zSecret==0 ){
463
+ db_unprotect(PROTECT_CONFIG);
463464
db_multi_exec(
464465
"REPLACE INTO config(name,value)"
465466
" VALUES('captcha-secret', lower(hex(randomblob(20))));"
466467
);
468
+ db_protect_pop();
467469
zSecret = db_get("captcha-secret", 0);
468470
assert( zSecret!=0 );
469471
}
470472
blob_init(&b, 0, 0);
471473
blob_appendf(&b, "%s-%x", zSecret, seed);
472474
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458 Blob b;
459 static char zRes[20];
460
461 zSecret = db_get("captcha-secret", 0);
462 if( zSecret==0 ){
 
463 db_multi_exec(
464 "REPLACE INTO config(name,value)"
465 " VALUES('captcha-secret', lower(hex(randomblob(20))));"
466 );
 
467 zSecret = db_get("captcha-secret", 0);
468 assert( zSecret!=0 );
469 }
470 blob_init(&b, 0, 0);
471 blob_appendf(&b, "%s-%x", zSecret, seed);
472
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458 Blob b;
459 static char zRes[20];
460
461 zSecret = db_get("captcha-secret", 0);
462 if( zSecret==0 ){
463 db_unprotect(PROTECT_CONFIG);
464 db_multi_exec(
465 "REPLACE INTO config(name,value)"
466 " VALUES('captcha-secret', lower(hex(randomblob(20))));"
467 );
468 db_protect_pop();
469 zSecret = db_get("captcha-secret", 0);
470 assert( zSecret!=0 );
471 }
472 blob_init(&b, 0, 0);
473 blob_appendf(&b, "%s-%x", zSecret, seed);
474
+32 -6
--- src/cgi.c
+++ src/cgi.c
@@ -1053,29 +1053,56 @@
10531053
** assume that PATH_INFO is an empty string and set REQUEST_URI equal
10541054
** to PATH_INFO.
10551055
**
10561056
** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
10571057
** PATH_INFO when it is empty.
1058
+**
1059
+** CGI Parameter quick reference:
1060
+**
1061
+** REQUEST_URI
1062
+** _____________|____________
1063
+** / \
1064
+** https://www.fossil-scm.org/forum/info/12736b30c072551a?t=c
1065
+** \________________/\____/\____________________/ \_/
1066
+** | | | |
1067
+** HTTP_HOST | PATH_INFO QUERY_STRING
1068
+** SCRIPT_NAME
10581069
*/
10591070
void cgi_init(void){
10601071
char *z;
10611072
const char *zType;
10621073
char *zSemi;
10631074
int len;
10641075
const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
1065
- const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
1066
- const char *zPathInfo = cgi_parameter("PATH_INFO",0);
1076
+ const char *zScriptName = cgi_parameter("SCRIPT_NAME","");
1077
+ const char *zPathInfo = cgi_parameter("PATH_INFO","");
10671078
#ifdef _WIN32
10681079
const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
10691080
#endif
10701081
10711082
#ifdef FOSSIL_ENABLE_JSON
10721083
const int noJson = P("no_json")!=0;
10731084
#endif
10741085
g.isHTTP = 1;
10751086
cgi_destination(CGI_BODY);
1076
- if( zScriptName==0 ) malformed_request("missing SCRIPT_NAME");
1087
+
1088
+ /* We must have SCRIPT_NAME. If the web server did not supply it, try
1089
+ ** to compute it from REQUEST_URI and PATH_INFO. */
1090
+ if( zScriptName==0 ){
1091
+ size_t nRU, nPI;
1092
+ if( zRequestUri==0 || zPathInfo==0 ){
1093
+ malformed_request("missing SCRIPT_NAME"); /* Does not return */
1094
+ }
1095
+ nRU = strlen(zRequestUri);
1096
+ nPI = strlen(zPathInfo);
1097
+ if( nRU<nPI ){
1098
+ malformed_request("PATH_INFO is longer than REQUEST_URI");
1099
+ }
1100
+ zScriptName = mprintf("%.*s", (int)(nRU-nPI), zRequestUri);
1101
+ cgi_set_parameter("SCRIPT_NAME", zScriptName);
1102
+ }
1103
+
10771104
#ifdef _WIN32
10781105
/* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
10791106
** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as
10801107
** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
10811108
** beginning. */
@@ -1257,16 +1284,15 @@
12571284
}
12581285
}
12591286
12601287
/* If no match is found and the name begins with an upper-case
12611288
** letter, then check to see if there is an environment variable
1262
- ** with the given name. Handle environment variables with empty values
1263
- ** the same as non-existent environment variables.
1289
+ ** with the given name.
12641290
*/
12651291
if( fossil_isupper(zName[0]) ){
12661292
const char *zValue = fossil_getenv(zName);
1267
- if( zValue && zValue[0] ){
1293
+ if( zValue ){
12681294
cgi_set_parameter_nocopy(zName, zValue, 0);
12691295
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
12701296
return zValue;
12711297
}
12721298
}
12731299
--- src/cgi.c
+++ src/cgi.c
@@ -1053,29 +1053,56 @@
1053 ** assume that PATH_INFO is an empty string and set REQUEST_URI equal
1054 ** to PATH_INFO.
1055 **
1056 ** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
1057 ** PATH_INFO when it is empty.
 
 
 
 
 
 
 
 
 
 
 
1058 */
1059 void cgi_init(void){
1060 char *z;
1061 const char *zType;
1062 char *zSemi;
1063 int len;
1064 const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
1065 const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
1066 const char *zPathInfo = cgi_parameter("PATH_INFO",0);
1067 #ifdef _WIN32
1068 const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
1069 #endif
1070
1071 #ifdef FOSSIL_ENABLE_JSON
1072 const int noJson = P("no_json")!=0;
1073 #endif
1074 g.isHTTP = 1;
1075 cgi_destination(CGI_BODY);
1076 if( zScriptName==0 ) malformed_request("missing SCRIPT_NAME");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077 #ifdef _WIN32
1078 /* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
1079 ** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as
1080 ** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
1081 ** beginning. */
@@ -1257,16 +1284,15 @@
1257 }
1258 }
1259
1260 /* If no match is found and the name begins with an upper-case
1261 ** letter, then check to see if there is an environment variable
1262 ** with the given name. Handle environment variables with empty values
1263 ** the same as non-existent environment variables.
1264 */
1265 if( fossil_isupper(zName[0]) ){
1266 const char *zValue = fossil_getenv(zName);
1267 if( zValue && zValue[0] ){
1268 cgi_set_parameter_nocopy(zName, zValue, 0);
1269 CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1270 return zValue;
1271 }
1272 }
1273
--- src/cgi.c
+++ src/cgi.c
@@ -1053,29 +1053,56 @@
1053 ** assume that PATH_INFO is an empty string and set REQUEST_URI equal
1054 ** to PATH_INFO.
1055 **
1056 ** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
1057 ** PATH_INFO when it is empty.
1058 **
1059 ** CGI Parameter quick reference:
1060 **
1061 ** REQUEST_URI
1062 ** _____________|____________
1063 ** / \
1064 ** https://www.fossil-scm.org/forum/info/12736b30c072551a?t=c
1065 ** \________________/\____/\____________________/ \_/
1066 ** | | | |
1067 ** HTTP_HOST | PATH_INFO QUERY_STRING
1068 ** SCRIPT_NAME
1069 */
1070 void cgi_init(void){
1071 char *z;
1072 const char *zType;
1073 char *zSemi;
1074 int len;
1075 const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
1076 const char *zScriptName = cgi_parameter("SCRIPT_NAME","");
1077 const char *zPathInfo = cgi_parameter("PATH_INFO","");
1078 #ifdef _WIN32
1079 const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
1080 #endif
1081
1082 #ifdef FOSSIL_ENABLE_JSON
1083 const int noJson = P("no_json")!=0;
1084 #endif
1085 g.isHTTP = 1;
1086 cgi_destination(CGI_BODY);
1087
1088 /* We must have SCRIPT_NAME. If the web server did not supply it, try
1089 ** to compute it from REQUEST_URI and PATH_INFO. */
1090 if( zScriptName==0 ){
1091 size_t nRU, nPI;
1092 if( zRequestUri==0 || zPathInfo==0 ){
1093 malformed_request("missing SCRIPT_NAME"); /* Does not return */
1094 }
1095 nRU = strlen(zRequestUri);
1096 nPI = strlen(zPathInfo);
1097 if( nRU<nPI ){
1098 malformed_request("PATH_INFO is longer than REQUEST_URI");
1099 }
1100 zScriptName = mprintf("%.*s", (int)(nRU-nPI), zRequestUri);
1101 cgi_set_parameter("SCRIPT_NAME", zScriptName);
1102 }
1103
1104 #ifdef _WIN32
1105 /* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
1106 ** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as
1107 ** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
1108 ** beginning. */
@@ -1257,16 +1284,15 @@
1284 }
1285 }
1286
1287 /* If no match is found and the name begins with an upper-case
1288 ** letter, then check to see if there is an environment variable
1289 ** with the given name.
 
1290 */
1291 if( fossil_isupper(zName[0]) ){
1292 const char *zValue = fossil_getenv(zName);
1293 if( zValue ){
1294 cgi_set_parameter_nocopy(zName, zValue, 0);
1295 CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1296 return zValue;
1297 }
1298 }
1299
+6 -12
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
6262
** Create a TEMP table named SFILE and add all unmanaged files named on
6363
** the command-line to that table. If directories are named, then add
6464
** all unmanaged files contained underneath those directories. If there
6565
** are no files or directories named on the command-line, then add all
6666
** unmanaged files anywhere in the checkout.
67
+**
68
+** This routine never follows symlinks. It always treats symlinks as
69
+** object unto themselves.
6770
*/
6871
static void locate_unmanaged_files(
6972
int argc, /* Number of command-line arguments to examine */
7073
char **argv, /* values of command-line arguments */
7174
unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
8083
db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
8184
" mtime INTEGER, size INTEGER)", filename_collation());
8285
nRoot = (int)strlen(g.zLocalRoot);
8386
if( argc==0 ){
8487
blob_init(&name, g.zLocalRoot, nRoot - 1);
85
- vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, RepoFILE);
88
+ vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE);
8689
blob_reset(&name);
8790
}else{
8891
for(i=0; i<argc; i++){
8992
file_canonical_name(argv[i], &name, 0);
9093
zName = blob_str(&name);
91
- isDir = file_isdir(zName, RepoFILE);
94
+ isDir = file_isdir(zName, SymFILE);
9295
if( isDir==1 ){
93
- vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, RepoFILE);
96
+ vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE);
9497
}else if( isDir==0 ){
9598
fossil_warning("not found: %s", &zName[nRoot]);
9699
}else if( file_access(zName, R_OK) ){
97100
fossil_fatal("cannot open %s", &zName[nRoot]);
98101
}else{
@@ -856,14 +859,10 @@
856859
857860
if( zIgnoreFlag==0 ){
858861
zIgnoreFlag = db_get("ignore-glob", 0);
859862
}
860863
pIgnore = glob_create(zIgnoreFlag);
861
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
862
- /* Always consider symlinks. */
863
- g.allowSymlinks = db_allow_symlinks_by_default();
864
-#endif
865864
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
866865
glob_free(pIgnore);
867866
868867
blob_zero(&report);
869868
status_report(&report, flags);
@@ -1017,14 +1016,10 @@
10171016
verify_all_options();
10181017
pIgnore = glob_create(zIgnoreFlag);
10191018
pKeep = glob_create(zKeepFlag);
10201019
pClean = glob_create(zCleanFlag);
10211020
nRoot = (int)strlen(g.zLocalRoot);
1022
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
1023
- /* Always consider symlinks. */
1024
- g.allowSymlinks = db_allow_symlinks_by_default();
1025
-#endif
10261021
if( !dirsOnlyFlag ){
10271022
Stmt q;
10281023
Blob repo;
10291024
if( !dryRunFlag && !disableUndo ) undo_begin();
10301025
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2738,11 +2733,10 @@
27382733
/* Commit */
27392734
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
27402735
db_multi_exec("PRAGMA repository.application_id=252006673;");
27412736
db_multi_exec("PRAGMA localdb.application_id=252006674;");
27422737
if( dryRunFlag ){
2743
- leaf_ambiguity_warning(nvid,nvid);
27442738
db_end_transaction(1);
27452739
exit(1);
27462740
}
27472741
db_end_transaction(0);
27482742
27492743
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
62 ** Create a TEMP table named SFILE and add all unmanaged files named on
63 ** the command-line to that table. If directories are named, then add
64 ** all unmanaged files contained underneath those directories. If there
65 ** are no files or directories named on the command-line, then add all
66 ** unmanaged files anywhere in the checkout.
 
 
 
67 */
68 static void locate_unmanaged_files(
69 int argc, /* Number of command-line arguments to examine */
70 char **argv, /* values of command-line arguments */
71 unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
80 db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
81 " mtime INTEGER, size INTEGER)", filename_collation());
82 nRoot = (int)strlen(g.zLocalRoot);
83 if( argc==0 ){
84 blob_init(&name, g.zLocalRoot, nRoot - 1);
85 vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, RepoFILE);
86 blob_reset(&name);
87 }else{
88 for(i=0; i<argc; i++){
89 file_canonical_name(argv[i], &name, 0);
90 zName = blob_str(&name);
91 isDir = file_isdir(zName, RepoFILE);
92 if( isDir==1 ){
93 vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, RepoFILE);
94 }else if( isDir==0 ){
95 fossil_warning("not found: %s", &zName[nRoot]);
96 }else if( file_access(zName, R_OK) ){
97 fossil_fatal("cannot open %s", &zName[nRoot]);
98 }else{
@@ -856,14 +859,10 @@
856
857 if( zIgnoreFlag==0 ){
858 zIgnoreFlag = db_get("ignore-glob", 0);
859 }
860 pIgnore = glob_create(zIgnoreFlag);
861 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
862 /* Always consider symlinks. */
863 g.allowSymlinks = db_allow_symlinks_by_default();
864 #endif
865 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
866 glob_free(pIgnore);
867
868 blob_zero(&report);
869 status_report(&report, flags);
@@ -1017,14 +1016,10 @@
1017 verify_all_options();
1018 pIgnore = glob_create(zIgnoreFlag);
1019 pKeep = glob_create(zKeepFlag);
1020 pClean = glob_create(zCleanFlag);
1021 nRoot = (int)strlen(g.zLocalRoot);
1022 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
1023 /* Always consider symlinks. */
1024 g.allowSymlinks = db_allow_symlinks_by_default();
1025 #endif
1026 if( !dirsOnlyFlag ){
1027 Stmt q;
1028 Blob repo;
1029 if( !dryRunFlag && !disableUndo ) undo_begin();
1030 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2738,11 +2733,10 @@
2738 /* Commit */
2739 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2740 db_multi_exec("PRAGMA repository.application_id=252006673;");
2741 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2742 if( dryRunFlag ){
2743 leaf_ambiguity_warning(nvid,nvid);
2744 db_end_transaction(1);
2745 exit(1);
2746 }
2747 db_end_transaction(0);
2748
2749
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
62 ** Create a TEMP table named SFILE and add all unmanaged files named on
63 ** the command-line to that table. If directories are named, then add
64 ** all unmanaged files contained underneath those directories. If there
65 ** are no files or directories named on the command-line, then add all
66 ** unmanaged files anywhere in the checkout.
67 **
68 ** This routine never follows symlinks. It always treats symlinks as
69 ** object unto themselves.
70 */
71 static void locate_unmanaged_files(
72 int argc, /* Number of command-line arguments to examine */
73 char **argv, /* values of command-line arguments */
74 unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
83 db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
84 " mtime INTEGER, size INTEGER)", filename_collation());
85 nRoot = (int)strlen(g.zLocalRoot);
86 if( argc==0 ){
87 blob_init(&name, g.zLocalRoot, nRoot - 1);
88 vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE);
89 blob_reset(&name);
90 }else{
91 for(i=0; i<argc; i++){
92 file_canonical_name(argv[i], &name, 0);
93 zName = blob_str(&name);
94 isDir = file_isdir(zName, SymFILE);
95 if( isDir==1 ){
96 vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE);
97 }else if( isDir==0 ){
98 fossil_warning("not found: %s", &zName[nRoot]);
99 }else if( file_access(zName, R_OK) ){
100 fossil_fatal("cannot open %s", &zName[nRoot]);
101 }else{
@@ -856,14 +859,10 @@
859
860 if( zIgnoreFlag==0 ){
861 zIgnoreFlag = db_get("ignore-glob", 0);
862 }
863 pIgnore = glob_create(zIgnoreFlag);
 
 
 
 
864 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
865 glob_free(pIgnore);
866
867 blob_zero(&report);
868 status_report(&report, flags);
@@ -1017,14 +1016,10 @@
1016 verify_all_options();
1017 pIgnore = glob_create(zIgnoreFlag);
1018 pKeep = glob_create(zKeepFlag);
1019 pClean = glob_create(zCleanFlag);
1020 nRoot = (int)strlen(g.zLocalRoot);
 
 
 
 
1021 if( !dirsOnlyFlag ){
1022 Stmt q;
1023 Blob repo;
1024 if( !dryRunFlag && !disableUndo ) undo_begin();
1025 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2738,11 +2733,10 @@
2733 /* Commit */
2734 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2735 db_multi_exec("PRAGMA repository.application_id=252006673;");
2736 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2737 if( dryRunFlag ){
 
2738 db_end_transaction(1);
2739 exit(1);
2740 }
2741 db_end_transaction(0);
2742
2743
--- src/clone.c
+++ src/clone.c
@@ -167,10 +167,11 @@
167167
if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
168168
if( g.url.isFile ){
169169
file_copy(g.url.name, g.argv[3]);
170170
db_close(1);
171171
db_open_repository(g.argv[3]);
172
+ db_open_config(1,0);
172173
db_record_repository_filename(g.argv[3]);
173174
url_remember();
174175
if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content();
175176
shun_artifacts();
176177
db_create_default_users(1, zDefaultUser);
@@ -201,15 +202,17 @@
201202
blob_zero(&fn);
202203
file_canonical_name(g.zSSLIdentity, &fn, 0);
203204
db_set("ssl-identity", blob_str(&fn), 0);
204205
blob_reset(&fn);
205206
}
207
+ db_unprotect(PROTECT_CONFIG);
206208
db_multi_exec(
207209
"REPLACE INTO config(name,value,mtime)"
208210
" VALUES('server-code', lower(hex(randomblob(20))), now());"
209211
"DELETE FROM config WHERE name='project-code';"
210212
);
213
+ db_protect_pop();
211214
url_enable_proxy(0);
212215
clone_ssh_db_set_options();
213216
url_get_password_if_needed();
214217
g.xlinkClusterOnly = 1;
215218
nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -235,11 +238,13 @@
235238
fossil_print("Vacuuming the database... "); fflush(stdout);
236239
if( db_int(0, "PRAGMA page_count")>1000
237240
&& db_int(0, "PRAGMA page_size")<8192 ){
238241
db_multi_exec("PRAGMA page_size=8192;");
239242
}
243
+ db_unprotect(PROTECT_ALL);
240244
db_multi_exec("VACUUM");
245
+ db_protect_pop();
241246
fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
242247
fossil_print("server-id: %s\n", db_get("server-code", 0));
243248
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
244249
fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
245250
}
246251
--- src/clone.c
+++ src/clone.c
@@ -167,10 +167,11 @@
167 if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
168 if( g.url.isFile ){
169 file_copy(g.url.name, g.argv[3]);
170 db_close(1);
171 db_open_repository(g.argv[3]);
 
172 db_record_repository_filename(g.argv[3]);
173 url_remember();
174 if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content();
175 shun_artifacts();
176 db_create_default_users(1, zDefaultUser);
@@ -201,15 +202,17 @@
201 blob_zero(&fn);
202 file_canonical_name(g.zSSLIdentity, &fn, 0);
203 db_set("ssl-identity", blob_str(&fn), 0);
204 blob_reset(&fn);
205 }
 
206 db_multi_exec(
207 "REPLACE INTO config(name,value,mtime)"
208 " VALUES('server-code', lower(hex(randomblob(20))), now());"
209 "DELETE FROM config WHERE name='project-code';"
210 );
 
211 url_enable_proxy(0);
212 clone_ssh_db_set_options();
213 url_get_password_if_needed();
214 g.xlinkClusterOnly = 1;
215 nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -235,11 +238,13 @@
235 fossil_print("Vacuuming the database... "); fflush(stdout);
236 if( db_int(0, "PRAGMA page_count")>1000
237 && db_int(0, "PRAGMA page_size")<8192 ){
238 db_multi_exec("PRAGMA page_size=8192;");
239 }
 
240 db_multi_exec("VACUUM");
 
241 fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
242 fossil_print("server-id: %s\n", db_get("server-code", 0));
243 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
244 fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
245 }
246
--- src/clone.c
+++ src/clone.c
@@ -167,10 +167,11 @@
167 if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
168 if( g.url.isFile ){
169 file_copy(g.url.name, g.argv[3]);
170 db_close(1);
171 db_open_repository(g.argv[3]);
172 db_open_config(1,0);
173 db_record_repository_filename(g.argv[3]);
174 url_remember();
175 if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content();
176 shun_artifacts();
177 db_create_default_users(1, zDefaultUser);
@@ -201,15 +202,17 @@
202 blob_zero(&fn);
203 file_canonical_name(g.zSSLIdentity, &fn, 0);
204 db_set("ssl-identity", blob_str(&fn), 0);
205 blob_reset(&fn);
206 }
207 db_unprotect(PROTECT_CONFIG);
208 db_multi_exec(
209 "REPLACE INTO config(name,value,mtime)"
210 " VALUES('server-code', lower(hex(randomblob(20))), now());"
211 "DELETE FROM config WHERE name='project-code';"
212 );
213 db_protect_pop();
214 url_enable_proxy(0);
215 clone_ssh_db_set_options();
216 url_get_password_if_needed();
217 g.xlinkClusterOnly = 1;
218 nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -235,11 +238,13 @@
238 fossil_print("Vacuuming the database... "); fflush(stdout);
239 if( db_int(0, "PRAGMA page_count")>1000
240 && db_int(0, "PRAGMA page_size")<8192 ){
241 db_multi_exec("PRAGMA page_size=8192;");
242 }
243 db_unprotect(PROTECT_ALL);
244 db_multi_exec("VACUUM");
245 db_protect_pop();
246 fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
247 fossil_print("server-id: %s\n", db_get("server-code", 0));
248 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
249 fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
250 }
251
+45 -16
--- src/configure.c
+++ src/configure.c
@@ -37,11 +37,12 @@
3737
#define CONFIGSET_USER 0x000020 /* The USER table */
3838
#define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */
3939
#define CONFIGSET_XFER 0x000080 /* Transfer configuration */
4040
#define CONFIGSET_ALIAS 0x000100 /* URL Aliases */
4141
#define CONFIGSET_SCRIBER 0x000200 /* Email subscribers */
42
-#define CONFIGSET_ALL 0x0003ff /* Everything */
42
+#define CONFIGSET_IWIKI 0x000400 /* Interwiki codes */
43
+#define CONFIGSET_ALL 0x0007ff /* Everything */
4344
4445
#define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */
4546
4647
/*
4748
** This mask is used for the common TH1 configuration settings (i.e. those
@@ -58,22 +59,23 @@
5859
static struct {
5960
const char *zName; /* Name of the configuration set */
6061
int groupMask; /* Mask for that configuration set */
6162
const char *zHelp; /* What it does */
6263
} aGroupName[] = {
63
- { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
64
- { "/project", CONFIGSET_PROJ, "Project name and description" },
64
+ { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
65
+ { "/project", CONFIGSET_PROJ, "Project name and description" },
6566
{ "/skin", CONFIGSET_SKIN | CONFIGSET_CSS,
66
- "Web interface appearance settings" },
67
- { "/css", CONFIGSET_CSS, "Style sheet" },
68
- { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
69
- { "/ticket", CONFIGSET_TKT, "Ticket setup", },
70
- { "/user", CONFIGSET_USER, "Users and privilege settings" },
71
- { "/xfer", CONFIGSET_XFER, "Transfer setup", },
72
- { "/alias", CONFIGSET_ALIAS, "URL Aliases", },
73
- { "/subscriber", CONFIGSET_SCRIBER,"Email notification subscriber list" },
74
- { "/all", CONFIGSET_ALL, "All of the above" },
67
+ "Web interface appearance settings" },
68
+ { "/css", CONFIGSET_CSS, "Style sheet" },
69
+ { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
70
+ { "/ticket", CONFIGSET_TKT, "Ticket setup", },
71
+ { "/user", CONFIGSET_USER, "Users and privilege settings" },
72
+ { "/xfer", CONFIGSET_XFER, "Transfer setup", },
73
+ { "/alias", CONFIGSET_ALIAS, "URL Aliases", },
74
+ { "/subscriber", CONFIGSET_SCRIBER, "Email notification subscriber list" },
75
+ { "/interwiki", CONFIGSET_IWIKI, "Inter-wiki link prefixes" },
76
+ { "/all", CONFIGSET_ALL, "All of the above" },
7577
};
7678
7779
7880
/*
7981
** The following is a list of settings that we are willing to
@@ -143,13 +145,10 @@
143145
{ "keep-glob", CONFIGSET_PROJ },
144146
{ "crlf-glob", CONFIGSET_PROJ },
145147
{ "crnl-glob", CONFIGSET_PROJ },
146148
{ "encoding-glob", CONFIGSET_PROJ },
147149
{ "empty-dirs", CONFIGSET_PROJ },
148
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
149
- { "allow-symlinks", CONFIGSET_PROJ },
150
-#endif
151150
{ "dotfiles", CONFIGSET_PROJ },
152151
{ "parent-project-code", CONFIGSET_PROJ },
153152
{ "parent-project-name", CONFIGSET_PROJ },
154153
{ "hash-policy", CONFIGSET_PROJ },
155154
{ "comment-format", CONFIGSET_PROJ },
@@ -176,10 +175,12 @@
176175
{ "@shun", CONFIGSET_SHUN },
177176
178177
{ "@alias", CONFIGSET_ALIAS },
179178
180179
{ "@subscriber", CONFIGSET_SCRIBER },
180
+
181
+ { "@interwiki", CONFIGSET_IWIKI },
181182
182183
{ "xfer-common-script", CONFIGSET_XFER },
183184
{ "xfer-push-script", CONFIGSET_XFER },
184185
{ "xfer-commit-script", CONFIGSET_XFER },
185186
{ "xfer-ticket-script", CONFIGSET_XFER },
@@ -259,10 +260,13 @@
259260
return m;
260261
}
261262
}
262263
if( strncmp(zName, "walias:/", 8)==0 ){
263264
return CONFIGSET_ALIAS;
265
+ }
266
+ if( strncmp(zName, "interwiki:", 10)==0 ){
267
+ return CONFIGSET_IWIKI;
264268
}
265269
return 0;
266270
}
267271
268272
/*
@@ -441,10 +445,11 @@
441445
blob_append_sql(&sql,") VALUES(%s,%s",
442446
azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
443447
for(jj=2; jj<nToken; jj+=2){
444448
blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
445449
}
450
+ db_protect_only(PROTECT_SENSITIVE);
446451
db_multi_exec("%s)", blob_sql_text(&sql));
447452
if( db_changes()==0 ){
448453
blob_reset(&sql);
449454
blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
450455
&zName[1], azToken[0]/*safe-for-%s*/);
@@ -455,10 +460,11 @@
455460
blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
456461
aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
457462
azToken[0]/*safe-for-%s*/);
458463
db_multi_exec("%s", blob_sql_text(&sql));
459464
}
465
+ db_protect_pop();
460466
blob_reset(&sql);
461467
rebuildMask |= thisMask;
462468
}
463469
}
464470
@@ -596,10 +602,26 @@
596602
blob_size(&rec), blob_str(&rec));
597603
nCard++;
598604
blob_reset(&rec);
599605
}
600606
db_finalize(&q);
607
+ }
608
+ if( groupMask & CONFIGSET_IWIKI ){
609
+ db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config"
610
+ " WHERE name GLOB 'interwiki:*' AND mtime>=%lld", iStart);
611
+ while( db_step(&q)==SQLITE_ROW ){
612
+ blob_appendf(&rec,"%s %s value %s",
613
+ db_column_text(&q, 0),
614
+ db_column_text(&q, 1),
615
+ db_column_text(&q, 2)
616
+ );
617
+ blob_appendf(pOut, "config /config %d\n%s\n",
618
+ blob_size(&rec), blob_str(&rec));
619
+ nCard++;
620
+ blob_reset(&rec);
621
+ }
622
+ db_finalize(&q);
601623
}
602624
if( (groupMask & CONFIGSET_SCRIBER)!=0
603625
&& db_table_exists("repository","subscriber")
604626
){
605627
db_prepare(&q, "SELECT mtime, quote(semail),"
@@ -710,11 +732,12 @@
710732
** > fossil configuration export AREA FILENAME
711733
**
712734
** Write to FILENAME exported configuration information for AREA.
713735
** AREA can be one of:
714736
**
715
-** all email project shun skin ticket user alias subscriber
737
+** all email interwiki project shun skin
738
+** ticket user alias subscriber
716739
**
717740
** > fossil configuration import FILENAME
718741
**
719742
** Read a configuration from FILENAME, overwriting the current
720743
** configuration.
@@ -839,13 +862,17 @@
839862
export_config(mask, g.argv[3], 0, zBackup);
840863
for(i=0; i<count(aConfig); i++){
841864
const char *zName = aConfig[i].zName;
842865
if( (aConfig[i].groupMask & mask)==0 ) continue;
843866
if( zName[0]!='@' ){
867
+ db_unprotect(PROTECT_CONFIG);
844868
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
869
+ db_protect_pop();
845870
}else if( fossil_strcmp(zName,"@user")==0 ){
871
+ db_unprotect(PROTECT_USER);
846872
db_multi_exec("DELETE FROM user");
873
+ db_protect_pop();
847874
db_create_default_users(0, 0);
848875
}else if( fossil_strcmp(zName,"@concealed")==0 ){
849876
db_multi_exec("DELETE FROM concealed");
850877
}else if( fossil_strcmp(zName,"@shun")==0 ){
851878
db_multi_exec("DELETE FROM shun");
@@ -1053,10 +1080,11 @@
10531080
}else if( zBlob ){
10541081
blob_read_from_file(&x, zBlob, ExtFILE);
10551082
}else{
10561083
blob_init(&x,g.argv[3],-1);
10571084
}
1085
+ db_unprotect(PROTECT_CONFIG);
10581086
db_prepare(&ins,
10591087
"REPLACE INTO config(name,value,mtime)"
10601088
"VALUES(%Q,:val,now())", zVar);
10611089
if( zBlob ){
10621090
db_bind_blob(&ins, ":val", &x);
@@ -1063,7 +1091,8 @@
10631091
}else{
10641092
db_bind_text(&ins, ":val", blob_str(&x));
10651093
}
10661094
db_step(&ins);
10671095
db_finalize(&ins);
1096
+ db_protect_pop();
10681097
blob_reset(&x);
10691098
}
10701099
--- src/configure.c
+++ src/configure.c
@@ -37,11 +37,12 @@
37 #define CONFIGSET_USER 0x000020 /* The USER table */
38 #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */
39 #define CONFIGSET_XFER 0x000080 /* Transfer configuration */
40 #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */
41 #define CONFIGSET_SCRIBER 0x000200 /* Email subscribers */
42 #define CONFIGSET_ALL 0x0003ff /* Everything */
 
43
44 #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */
45
46 /*
47 ** This mask is used for the common TH1 configuration settings (i.e. those
@@ -58,22 +59,23 @@
58 static struct {
59 const char *zName; /* Name of the configuration set */
60 int groupMask; /* Mask for that configuration set */
61 const char *zHelp; /* What it does */
62 } aGroupName[] = {
63 { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
64 { "/project", CONFIGSET_PROJ, "Project name and description" },
65 { "/skin", CONFIGSET_SKIN | CONFIGSET_CSS,
66 "Web interface appearance settings" },
67 { "/css", CONFIGSET_CSS, "Style sheet" },
68 { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
69 { "/ticket", CONFIGSET_TKT, "Ticket setup", },
70 { "/user", CONFIGSET_USER, "Users and privilege settings" },
71 { "/xfer", CONFIGSET_XFER, "Transfer setup", },
72 { "/alias", CONFIGSET_ALIAS, "URL Aliases", },
73 { "/subscriber", CONFIGSET_SCRIBER,"Email notification subscriber list" },
74 { "/all", CONFIGSET_ALL, "All of the above" },
 
75 };
76
77
78 /*
79 ** The following is a list of settings that we are willing to
@@ -143,13 +145,10 @@
143 { "keep-glob", CONFIGSET_PROJ },
144 { "crlf-glob", CONFIGSET_PROJ },
145 { "crnl-glob", CONFIGSET_PROJ },
146 { "encoding-glob", CONFIGSET_PROJ },
147 { "empty-dirs", CONFIGSET_PROJ },
148 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
149 { "allow-symlinks", CONFIGSET_PROJ },
150 #endif
151 { "dotfiles", CONFIGSET_PROJ },
152 { "parent-project-code", CONFIGSET_PROJ },
153 { "parent-project-name", CONFIGSET_PROJ },
154 { "hash-policy", CONFIGSET_PROJ },
155 { "comment-format", CONFIGSET_PROJ },
@@ -176,10 +175,12 @@
176 { "@shun", CONFIGSET_SHUN },
177
178 { "@alias", CONFIGSET_ALIAS },
179
180 { "@subscriber", CONFIGSET_SCRIBER },
 
 
181
182 { "xfer-common-script", CONFIGSET_XFER },
183 { "xfer-push-script", CONFIGSET_XFER },
184 { "xfer-commit-script", CONFIGSET_XFER },
185 { "xfer-ticket-script", CONFIGSET_XFER },
@@ -259,10 +260,13 @@
259 return m;
260 }
261 }
262 if( strncmp(zName, "walias:/", 8)==0 ){
263 return CONFIGSET_ALIAS;
 
 
 
264 }
265 return 0;
266 }
267
268 /*
@@ -441,10 +445,11 @@
441 blob_append_sql(&sql,") VALUES(%s,%s",
442 azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
443 for(jj=2; jj<nToken; jj+=2){
444 blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
445 }
 
446 db_multi_exec("%s)", blob_sql_text(&sql));
447 if( db_changes()==0 ){
448 blob_reset(&sql);
449 blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
450 &zName[1], azToken[0]/*safe-for-%s*/);
@@ -455,10 +460,11 @@
455 blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
456 aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
457 azToken[0]/*safe-for-%s*/);
458 db_multi_exec("%s", blob_sql_text(&sql));
459 }
 
460 blob_reset(&sql);
461 rebuildMask |= thisMask;
462 }
463 }
464
@@ -596,10 +602,26 @@
596 blob_size(&rec), blob_str(&rec));
597 nCard++;
598 blob_reset(&rec);
599 }
600 db_finalize(&q);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601 }
602 if( (groupMask & CONFIGSET_SCRIBER)!=0
603 && db_table_exists("repository","subscriber")
604 ){
605 db_prepare(&q, "SELECT mtime, quote(semail),"
@@ -710,11 +732,12 @@
710 ** > fossil configuration export AREA FILENAME
711 **
712 ** Write to FILENAME exported configuration information for AREA.
713 ** AREA can be one of:
714 **
715 ** all email project shun skin ticket user alias subscriber
 
716 **
717 ** > fossil configuration import FILENAME
718 **
719 ** Read a configuration from FILENAME, overwriting the current
720 ** configuration.
@@ -839,13 +862,17 @@
839 export_config(mask, g.argv[3], 0, zBackup);
840 for(i=0; i<count(aConfig); i++){
841 const char *zName = aConfig[i].zName;
842 if( (aConfig[i].groupMask & mask)==0 ) continue;
843 if( zName[0]!='@' ){
 
844 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
845 }else if( fossil_strcmp(zName,"@user")==0 ){
 
846 db_multi_exec("DELETE FROM user");
 
847 db_create_default_users(0, 0);
848 }else if( fossil_strcmp(zName,"@concealed")==0 ){
849 db_multi_exec("DELETE FROM concealed");
850 }else if( fossil_strcmp(zName,"@shun")==0 ){
851 db_multi_exec("DELETE FROM shun");
@@ -1053,10 +1080,11 @@
1053 }else if( zBlob ){
1054 blob_read_from_file(&x, zBlob, ExtFILE);
1055 }else{
1056 blob_init(&x,g.argv[3],-1);
1057 }
 
1058 db_prepare(&ins,
1059 "REPLACE INTO config(name,value,mtime)"
1060 "VALUES(%Q,:val,now())", zVar);
1061 if( zBlob ){
1062 db_bind_blob(&ins, ":val", &x);
@@ -1063,7 +1091,8 @@
1063 }else{
1064 db_bind_text(&ins, ":val", blob_str(&x));
1065 }
1066 db_step(&ins);
1067 db_finalize(&ins);
 
1068 blob_reset(&x);
1069 }
1070
--- src/configure.c
+++ src/configure.c
@@ -37,11 +37,12 @@
37 #define CONFIGSET_USER 0x000020 /* The USER table */
38 #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */
39 #define CONFIGSET_XFER 0x000080 /* Transfer configuration */
40 #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */
41 #define CONFIGSET_SCRIBER 0x000200 /* Email subscribers */
42 #define CONFIGSET_IWIKI 0x000400 /* Interwiki codes */
43 #define CONFIGSET_ALL 0x0007ff /* Everything */
44
45 #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */
46
47 /*
48 ** This mask is used for the common TH1 configuration settings (i.e. those
@@ -58,22 +59,23 @@
59 static struct {
60 const char *zName; /* Name of the configuration set */
61 int groupMask; /* Mask for that configuration set */
62 const char *zHelp; /* What it does */
63 } aGroupName[] = {
64 { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" },
65 { "/project", CONFIGSET_PROJ, "Project name and description" },
66 { "/skin", CONFIGSET_SKIN | CONFIGSET_CSS,
67 "Web interface appearance settings" },
68 { "/css", CONFIGSET_CSS, "Style sheet" },
69 { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" },
70 { "/ticket", CONFIGSET_TKT, "Ticket setup", },
71 { "/user", CONFIGSET_USER, "Users and privilege settings" },
72 { "/xfer", CONFIGSET_XFER, "Transfer setup", },
73 { "/alias", CONFIGSET_ALIAS, "URL Aliases", },
74 { "/subscriber", CONFIGSET_SCRIBER, "Email notification subscriber list" },
75 { "/interwiki", CONFIGSET_IWIKI, "Inter-wiki link prefixes" },
76 { "/all", CONFIGSET_ALL, "All of the above" },
77 };
78
79
80 /*
81 ** The following is a list of settings that we are willing to
@@ -143,13 +145,10 @@
145 { "keep-glob", CONFIGSET_PROJ },
146 { "crlf-glob", CONFIGSET_PROJ },
147 { "crnl-glob", CONFIGSET_PROJ },
148 { "encoding-glob", CONFIGSET_PROJ },
149 { "empty-dirs", CONFIGSET_PROJ },
 
 
 
150 { "dotfiles", CONFIGSET_PROJ },
151 { "parent-project-code", CONFIGSET_PROJ },
152 { "parent-project-name", CONFIGSET_PROJ },
153 { "hash-policy", CONFIGSET_PROJ },
154 { "comment-format", CONFIGSET_PROJ },
@@ -176,10 +175,12 @@
175 { "@shun", CONFIGSET_SHUN },
176
177 { "@alias", CONFIGSET_ALIAS },
178
179 { "@subscriber", CONFIGSET_SCRIBER },
180
181 { "@interwiki", CONFIGSET_IWIKI },
182
183 { "xfer-common-script", CONFIGSET_XFER },
184 { "xfer-push-script", CONFIGSET_XFER },
185 { "xfer-commit-script", CONFIGSET_XFER },
186 { "xfer-ticket-script", CONFIGSET_XFER },
@@ -259,10 +260,13 @@
260 return m;
261 }
262 }
263 if( strncmp(zName, "walias:/", 8)==0 ){
264 return CONFIGSET_ALIAS;
265 }
266 if( strncmp(zName, "interwiki:", 10)==0 ){
267 return CONFIGSET_IWIKI;
268 }
269 return 0;
270 }
271
272 /*
@@ -441,10 +445,11 @@
445 blob_append_sql(&sql,") VALUES(%s,%s",
446 azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
447 for(jj=2; jj<nToken; jj+=2){
448 blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
449 }
450 db_protect_only(PROTECT_SENSITIVE);
451 db_multi_exec("%s)", blob_sql_text(&sql));
452 if( db_changes()==0 ){
453 blob_reset(&sql);
454 blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
455 &zName[1], azToken[0]/*safe-for-%s*/);
@@ -455,10 +460,11 @@
460 blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
461 aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
462 azToken[0]/*safe-for-%s*/);
463 db_multi_exec("%s", blob_sql_text(&sql));
464 }
465 db_protect_pop();
466 blob_reset(&sql);
467 rebuildMask |= thisMask;
468 }
469 }
470
@@ -596,10 +602,26 @@
602 blob_size(&rec), blob_str(&rec));
603 nCard++;
604 blob_reset(&rec);
605 }
606 db_finalize(&q);
607 }
608 if( groupMask & CONFIGSET_IWIKI ){
609 db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config"
610 " WHERE name GLOB 'interwiki:*' AND mtime>=%lld", iStart);
611 while( db_step(&q)==SQLITE_ROW ){
612 blob_appendf(&rec,"%s %s value %s",
613 db_column_text(&q, 0),
614 db_column_text(&q, 1),
615 db_column_text(&q, 2)
616 );
617 blob_appendf(pOut, "config /config %d\n%s\n",
618 blob_size(&rec), blob_str(&rec));
619 nCard++;
620 blob_reset(&rec);
621 }
622 db_finalize(&q);
623 }
624 if( (groupMask & CONFIGSET_SCRIBER)!=0
625 && db_table_exists("repository","subscriber")
626 ){
627 db_prepare(&q, "SELECT mtime, quote(semail),"
@@ -710,11 +732,12 @@
732 ** > fossil configuration export AREA FILENAME
733 **
734 ** Write to FILENAME exported configuration information for AREA.
735 ** AREA can be one of:
736 **
737 ** all email interwiki project shun skin
738 ** ticket user alias subscriber
739 **
740 ** > fossil configuration import FILENAME
741 **
742 ** Read a configuration from FILENAME, overwriting the current
743 ** configuration.
@@ -839,13 +862,17 @@
862 export_config(mask, g.argv[3], 0, zBackup);
863 for(i=0; i<count(aConfig); i++){
864 const char *zName = aConfig[i].zName;
865 if( (aConfig[i].groupMask & mask)==0 ) continue;
866 if( zName[0]!='@' ){
867 db_unprotect(PROTECT_CONFIG);
868 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
869 db_protect_pop();
870 }else if( fossil_strcmp(zName,"@user")==0 ){
871 db_unprotect(PROTECT_USER);
872 db_multi_exec("DELETE FROM user");
873 db_protect_pop();
874 db_create_default_users(0, 0);
875 }else if( fossil_strcmp(zName,"@concealed")==0 ){
876 db_multi_exec("DELETE FROM concealed");
877 }else if( fossil_strcmp(zName,"@shun")==0 ){
878 db_multi_exec("DELETE FROM shun");
@@ -1053,10 +1080,11 @@
1080 }else if( zBlob ){
1081 blob_read_from_file(&x, zBlob, ExtFILE);
1082 }else{
1083 blob_init(&x,g.argv[3],-1);
1084 }
1085 db_unprotect(PROTECT_CONFIG);
1086 db_prepare(&ins,
1087 "REPLACE INTO config(name,value,mtime)"
1088 "VALUES(%Q,:val,now())", zVar);
1089 if( zBlob ){
1090 db_bind_blob(&ins, ":val", &x);
@@ -1063,7 +1091,8 @@
1091 }else{
1092 db_bind_text(&ins, ":val", blob_str(&x));
1093 }
1094 db_step(&ins);
1095 db_finalize(&ins);
1096 db_protect_pop();
1097 blob_reset(&x);
1098 }
1099
+1 -1
--- src/copybtn.js
+++ src/copybtn.js
@@ -80,11 +80,11 @@
8080
}.bind(null,this.id),400);
8181
}
8282
/* Create a temporary <textarea> element and copy the contents to clipboard. */
8383
function copyTextToClipboard(text){
8484
if( window.clipboardData && window.clipboardData.setData ){
85
- clipboardData.setData('Text',text);
85
+ window.clipboardData.setData('Text',text);
8686
}else{
8787
var x = document.createElement("textarea");
8888
x.style.position = 'fixed';
8989
x.value = text;
9090
document.body.appendChild(x);
9191
--- src/copybtn.js
+++ src/copybtn.js
@@ -80,11 +80,11 @@
80 }.bind(null,this.id),400);
81 }
82 /* Create a temporary <textarea> element and copy the contents to clipboard. */
83 function copyTextToClipboard(text){
84 if( window.clipboardData && window.clipboardData.setData ){
85 clipboardData.setData('Text',text);
86 }else{
87 var x = document.createElement("textarea");
88 x.style.position = 'fixed';
89 x.value = text;
90 document.body.appendChild(x);
91
--- src/copybtn.js
+++ src/copybtn.js
@@ -80,11 +80,11 @@
80 }.bind(null,this.id),400);
81 }
82 /* Create a temporary <textarea> element and copy the contents to clipboard. */
83 function copyTextToClipboard(text){
84 if( window.clipboardData && window.clipboardData.setData ){
85 window.clipboardData.setData('Text',text);
86 }else{
87 var x = document.createElement("textarea");
88 x.style.position = 'fixed';
89 x.value = text;
90 document.body.appendChild(x);
91
+327 -120
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
6969
#endif /* INTERFACE */
7070
const struct Stmt empty_Stmt = empty_Stmt_m;
7171
7272
/*
7373
** Call this routine when a database error occurs.
74
+** This routine throws a fatal error. It does not return.
7475
*/
7576
static void db_err(const char *zFormat, ...){
7677
va_list ap;
7778
char *z;
7879
va_start(ap, zFormat);
@@ -113,10 +114,11 @@
113114
/*
114115
** All static variable that a used by only this file are gathered into
115116
** the following structure.
116117
*/
117118
static struct DbLocalData {
119
+ unsigned protectMask; /* Prevent changes to database */
118120
int nBegin; /* Nesting depth of BEGIN */
119121
int doRollback; /* True to force a rollback */
120122
int nCommitHook; /* Number of commit hooks */
121123
int wrTxn; /* Outer-most TNX is a write */
122124
Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -133,11 +135,16 @@
133135
const char *zStartFile; /* File in which transaction was started */
134136
int iStartLine; /* Line of zStartFile where transaction started */
135137
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
136138
void *pAuthArg; /* Argument to the authorizer */
137139
const char *zAuthName; /* Name of the authorizer */
138
-} db = {0, 0, 0, 0, 0, 0, };
140
+ int bProtectTriggers; /* True if protection triggers already exist */
141
+ int nProtect; /* Slots of aProtect used */
142
+ unsigned aProtect[10]; /* Saved values of protectMask */
143
+} db = {
144
+ PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
145
+ 0, 0, 0, 0, 0, 0, };
139146
140147
/*
141148
** Arrange for the given file to be deleted on a failure.
142149
*/
143150
void db_delete_on_failure(const char *zFilename){
@@ -241,17 +248,19 @@
241248
db.nBegin--;
242249
if( db.nBegin==0 ){
243250
int i;
244251
if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
245252
i = 0;
253
+ db_protect_only(PROTECT_SENSITIVE);
246254
while( db.nBeforeCommit ){
247255
db.nBeforeCommit--;
248256
sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
249257
sqlite3_free(db.azBeforeCommit[i]);
250258
i++;
251259
}
252260
leaf_do_pending_checks();
261
+ db_protect_pop();
253262
}
254263
for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
255264
int rc = db.aHook[i].xHook();
256265
if( rc ){
257266
db.doRollback = 1;
@@ -319,10 +328,205 @@
319328
}
320329
db.aHook[db.nCommitHook].sequence = sequence;
321330
db.aHook[db.nCommitHook].xHook = x;
322331
db.nCommitHook++;
323332
}
333
+
334
+#if INTERFACE
335
+/*
336
+** Flag bits for db_protect() and db_unprotect() indicating which parts
337
+** of the databases should be write protected or write enabled, respectively.
338
+*/
339
+#define PROTECT_USER 0x01 /* USER table */
340
+#define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */
341
+#define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */
342
+#define PROTECT_READONLY 0x08 /* everything except TEMP tables */
343
+#define PROTECT_BASELINE 0x10 /* protection system is working */
344
+#define PROTECT_ALL 0x1f /* All of the above */
345
+#define PROTECT_NONE 0x00 /* Nothing. Everything is open */
346
+#endif /* INTERFACE */
347
+
348
+/*
349
+** Enable or disable database write protections.
350
+**
351
+** db_protext(X) Add protects on X
352
+** db_unprotect(X) Remove protections on X
353
+** db_protect_only(X) Remove all prior protections then set
354
+** protections to only X.
355
+**
356
+** Each of these routines pushes the previous protection mask onto
357
+** a finite-size stack. Each should be followed by a call to
358
+** db_protect_pop() to pop the stack and restore the protections that
359
+** existed prior to the call. The protection mask stack has a limited
360
+** depth, so take care not to next calls too deeply.
361
+**
362
+** About Database Write Protection
363
+** -------------------------------
364
+**
365
+** This is *not* a primary means of defending the application from
366
+** attack. Fossil should be secure even if this mechanism is disabled.
367
+** The purpose of database write protection is to provide an additional
368
+** layer of defense in case SQL injection bugs somehow slip into other
369
+** parts of the system. In other words, database write protection is
370
+** not primary defense but rather defense in depth.
371
+**
372
+** This mechanism mostly focuses on the USER table, to prevent an
373
+** attacker from giving themselves Admin privilegs, and on the
374
+** CONFIG table and specially "sensitive" settings such as
375
+** "diff-command" or "editor" that if compromised by an attacker
376
+** could lead to an RCE.
377
+**
378
+** By default, the USER and CONFIG tables are read-only. Various
379
+** subsystems that legitimately need to change those tables can
380
+** temporarily do so using:
381
+**
382
+** db_unprotect(PROTECT_xxx);
383
+** // make the legitmate changes here
384
+** db_protect_pop();
385
+**
386
+** Code that runs inside of reduced protections should be carefully
387
+** reviewed to ensure that it is harmless and not subject to SQL
388
+** injection.
389
+**
390
+** Read-only operations (such as many web pages like /timeline)
391
+** can invoke db_protect(PROTECT_ALL) to effectively make the database
392
+** read-only. TEMP tables (which are often used for these kinds of
393
+** pages) are still writable, however.
394
+**
395
+** The PROTECT_SENSITIVE protection is a subset of PROTECT_CONFIG
396
+** that blocks changes to all of the global_config table, but only
397
+** "sensitive" settings in the config table. PROTECT_SENSITIVE
398
+** relies on triggers and the protected_setting() SQL function to
399
+** prevent changes to sensitive settings.
400
+**
401
+** Additional Notes
402
+** ----------------
403
+**
404
+** Calls to routines like db_set() and db_unset() temporarily disable
405
+** the PROTECT_CONFIG protection. The assumption is that these calls
406
+** cannot be invoked by an SQL injection and are thus safe. Make sure
407
+** this is the case by always using a string literal as the name argument
408
+** to db_set() and db_unset() and friend, not a variable that might
409
+** be compromised by an attack.
410
+*/
411
+void db_protect_only(unsigned flags){
412
+ if( db.nProtect>=count(db.aProtect)-2 ){
413
+ fossil_panic("too many db_protect() calls");
414
+ }
415
+ db.aProtect[db.nProtect++] = db.protectMask;
416
+ if( (flags & PROTECT_SENSITIVE)!=0
417
+ && db.bProtectTriggers==0
418
+ && g.repositoryOpen
419
+ ){
420
+ /* Create the triggers needed to protect sensitive settings from
421
+ ** being created or modified the first time that PROTECT_SENSITIVE
422
+ ** is enabled. Deleting a sensitive setting is harmless, so there
423
+ ** is not trigger to block deletes. After being created once, the
424
+ ** triggers persist for the life of the database connection. */
425
+ db_multi_exec(
426
+ "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
427
+ " WHEN protected_setting(new.name) BEGIN"
428
+ " SELECT raise(abort,'not authorized');"
429
+ "END;\n"
430
+ "CREATE TEMP TRIGGER protect_2 BEFORE UPDATE ON config"
431
+ " WHEN protected_setting(new.name) BEGIN"
432
+ " SELECT raise(abort,'not authorized');"
433
+ "END;\n"
434
+ );
435
+ db.bProtectTriggers = 1;
436
+ }
437
+ db.protectMask = flags;
438
+}
439
+void db_protect(unsigned flags){
440
+ db_protect_only(db.protectMask | flags);
441
+}
442
+void db_unprotect(unsigned flags){
443
+ if( db.nProtect>=count(db.aProtect)-2 ){
444
+ fossil_panic("too many db_unprotect() calls");
445
+ }
446
+ db.aProtect[db.nProtect++] = db.protectMask;
447
+ db.protectMask &= ~flags;
448
+}
449
+void db_protect_pop(void){
450
+ if( db.nProtect<1 ){
451
+ fossil_panic("too many db_protect_pop() calls");
452
+ }
453
+ db.protectMask = db.aProtect[--db.nProtect];
454
+}
455
+
456
+/*
457
+** Verify that the desired database write pertections are in place.
458
+** Throw a fatal error if not.
459
+*/
460
+void db_assert_protected(unsigned flags){
461
+ if( (flags & db.protectMask)!=flags ){
462
+ fossil_panic("missing database write protection bits: %02x",
463
+ flags & ~db.protectMask);
464
+ }
465
+}
466
+
467
+/*
468
+** Assert that either all protections are off (including PROTECT_BASELINE
469
+** which is usually always enabled), or the setting named in the argument
470
+** is no a sensitive setting.
471
+**
472
+** This assert() is used to verify that the db_set() and db_set_int()
473
+** interfaces do not modify a sensitive setting.
474
+*/
475
+void db_assert_protection_off_or_not_sensitive(const char *zName){
476
+ if( db.protectMask!=0 && db_setting_is_protected(zName) ){
477
+ fossil_panic("unauthorized change to protected setting \"%s\"", zName);
478
+ }
479
+}
480
+
481
+/*
482
+** Every Fossil database connection automatically registers the following
483
+** overarching authenticator callback, and leaves it registered for the
484
+** duration of the connection. This authenticator will call any
485
+** sub-authenticators that are registered using db_set_authorizer().
486
+*/
487
+int db_top_authorizer(
488
+ void *pNotUsed,
489
+ int eCode,
490
+ const char *z0,
491
+ const char *z1,
492
+ const char *z2,
493
+ const char *z3
494
+){
495
+ int rc = SQLITE_OK;
496
+ switch( eCode ){
497
+ case SQLITE_INSERT:
498
+ case SQLITE_UPDATE:
499
+ case SQLITE_DELETE: {
500
+ if( (db.protectMask & PROTECT_USER)!=0
501
+ && sqlite3_stricmp(z0,"user")==0 ){
502
+ rc = SQLITE_DENY;
503
+ }else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
504
+ (sqlite3_stricmp(z0,"config")==0 ||
505
+ sqlite3_stricmp(z0,"global_config")==0) ){
506
+ rc = SQLITE_DENY;
507
+ }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 &&
508
+ sqlite3_stricmp(z0,"global_config")==0 ){
509
+ rc = SQLITE_DENY;
510
+ }else if( (db.protectMask & PROTECT_READONLY)!=0
511
+ && sqlite3_stricmp(z2,"temp")!=0 ){
512
+ rc = SQLITE_DENY;
513
+ }
514
+ break;
515
+ }
516
+ case SQLITE_DROP_TEMP_TRIGGER: {
517
+ /* Do not allow the triggers that enforce PROTECT_SENSITIVE
518
+ ** to be dropped */
519
+ rc = SQLITE_DENY;
520
+ break;
521
+ }
522
+ }
523
+ if( db.xAuth && rc==SQLITE_OK ){
524
+ rc = db.xAuth(db.pAuthArg, eCode, z0, z1, z2, z3);
525
+ }
526
+ return rc;
527
+}
324528
325529
/*
326530
** Set or unset the query authorizer callback function
327531
*/
328532
void db_set_authorizer(
@@ -331,23 +535,22 @@
331535
const char *zName /* for tracing */
332536
){
333537
if( db.xAuth ){
334538
fossil_panic("multiple active db_set_authorizer() calls");
335539
}
336
- if( g.db ) sqlite3_set_authorizer(g.db, xAuth, pArg);
337540
db.xAuth = xAuth;
338541
db.pAuthArg = pArg;
339542
db.zAuthName = zName;
340543
if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
341544
}
342545
void db_clear_authorizer(void){
343546
if( db.zAuthName && g.fSqlTrace ){
344547
fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
345548
}
346
- if( g.db ) sqlite3_set_authorizer(g.db, 0, 0);
347549
db.xAuth = 0;
348550
db.pAuthArg = 0;
551
+ db.zAuthName = 0;
349552
}
350553
351554
#if INTERFACE
352555
/*
353556
** Possible flags to db_vprepare
@@ -363,21 +566,24 @@
363566
*/
364567
int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
365568
int rc;
366569
int prepFlags = 0;
367570
char *zSql;
571
+ const char *zExtra = 0;
368572
blob_zero(&pStmt->sql);
369573
blob_vappendf(&pStmt->sql, zFormat, ap);
370574
va_end(ap);
371575
zSql = blob_str(&pStmt->sql);
372576
db.nPrepare++;
373577
if( flags & DB_PREPARE_PERSISTENT ){
374578
prepFlags = SQLITE_PREPARE_PERSISTENT;
375579
}
376
- rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, 0);
580
+ rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
377581
if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
378582
db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
583
+ }else if( zExtra && !fossil_all_whitespace(zExtra) ){
584
+ db_err("surplus text follows SQL: \"%s\"", zExtra);
379585
}
380586
pStmt->pNext = db.pAllStmt;
381587
pStmt->pPrev = 0;
382588
if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
383589
db.pAllStmt = pStmt;
@@ -640,10 +846,11 @@
640846
return rc;
641847
}
642848
643849
/*
644850
** COMMAND: test-db-exec-error
851
+** Usage: %fossil test-db-exec-error
645852
**
646853
** Invoke the db_exec() interface with an erroneous SQL statement
647854
** in order to verify the error handling logic.
648855
*/
649856
void db_test_db_exec_cmd(void){
@@ -650,10 +857,27 @@
650857
Stmt err;
651858
db_find_and_open_repository(0,0);
652859
db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
653860
db_exec(&err);
654861
}
862
+
863
+/*
864
+** COMMAND: test-db-prepare
865
+** Usage: %fossil test-db-prepare ?OPTIONS? SQL
866
+**
867
+** Invoke db_prepare() on the SQL input. Report any errors encountered.
868
+** This command is used to verify error detection logic in the db_prepare()
869
+** utility routine.
870
+*/
871
+void db_test_db_prepare(void){
872
+ Stmt err;
873
+ db_find_and_open_repository(0,0);
874
+ verify_all_options();
875
+ if( g.argc!=3 ) usage("?OPTIONS? SQL");
876
+ db_prepare(&err, "%s", g.argv[2]/*safe-for-%s*/);
877
+ db_finalize(&err);
878
+}
655879
656880
/*
657881
** Print the output of one or more SQL queries on standard output.
658882
** This routine is used for debugging purposes only.
659883
*/
@@ -880,13 +1104,10 @@
8801104
const char *zSql;
8811105
va_list ap;
8821106
8831107
xdb = db_open(zFileName ? zFileName : ":memory:");
8841108
sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
885
- if( db.xAuth ){
886
- sqlite3_set_authorizer(xdb, db.xAuth, db.pAuthArg);
887
- }
8881109
rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
8891110
if( rc!=SQLITE_OK ){
8901111
db_err("%s", sqlite3_errmsg(xdb));
8911112
}
8921113
va_start(ap, zSchema);
@@ -1092,10 +1313,37 @@
10921313
}
10931314
strcpy(zOut, zTemp = obscure((char*)zIn));
10941315
fossil_free(zTemp);
10951316
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
10961317
}
1318
+
1319
+/*
1320
+** Return True if zName is a protected (a.k.a. "sensitive") setting.
1321
+*/
1322
+int db_setting_is_protected(const char *zName){
1323
+ const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
1324
+ return pSetting!=0 && pSetting->sensitive!=0;
1325
+}
1326
+
1327
+/*
1328
+** Implement the protected_setting(X) SQL function. This function returns
1329
+** true if X is the name of a protected (security-sensitive) setting and
1330
+** the db.protectSensitive flag is enabled. It returns false otherwise.
1331
+*/
1332
+LOCAL void db_protected_setting_func(
1333
+ sqlite3_context *context,
1334
+ int argc,
1335
+ sqlite3_value **argv
1336
+){
1337
+ const char *zSetting;
1338
+ if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
1339
+ sqlite3_result_int(context, 0);
1340
+ return;
1341
+ }
1342
+ zSetting = (const char*)sqlite3_value_text(argv[0]);
1343
+ sqlite3_result_int(context, db_setting_is_protected(zSetting));
1344
+}
10971345
10981346
/*
10991347
** Register the SQL functions that are useful both to the internal
11001348
** representation and to the "fossil sql" command.
11011349
*/
@@ -1122,10 +1370,12 @@
11221370
alert_find_emailaddr_func, 0, 0);
11231371
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
11241372
alert_display_name_func, 0, 0);
11251373
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
11261374
db_obscure, 0, 0);
1375
+ sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1376
+ db_protected_setting_func, 0, 0);
11271377
}
11281378
11291379
#if USE_SEE
11301380
/*
11311381
** This is a pointer to the saved database encryption key string.
@@ -1380,10 +1630,11 @@
13801630
if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
13811631
db_add_aux_functions(db);
13821632
re_add_sql_func(db); /* The REGEXP operator */
13831633
foci_register(db); /* The "files_of_checkin" virtual table */
13841634
sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
1635
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
13851636
return db;
13861637
}
13871638
13881639
13891640
/*
@@ -1815,27 +2066,18 @@
18152066
zRepo = db_lget("repository", 0);
18162067
if( zRepo && !file_is_absolute_path(zRepo) ){
18172068
char * zFree = zRepo;
18182069
zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
18192070
fossil_free(zFree);
2071
+ zFree = zRepo;
2072
+ zRepo = file_canonical_name_dup(zFree);
2073
+ fossil_free(zFree);
18202074
}
18212075
}
18222076
return zRepo;
18232077
}
18242078
1825
-/*
1826
-** Returns non-zero if the default value for the "allow-symlinks" setting
1827
-** is "on". When on Windows, this always returns false.
1828
-*/
1829
-int db_allow_symlinks_by_default(void){
1830
-#if defined(_WIN32) || !defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
1831
- return 0;
1832
-#else
1833
- return 1;
1834
-#endif
1835
-}
1836
-
18372079
/*
18382080
** Returns non-zero if support for symlinks is currently enabled.
18392081
*/
18402082
int db_allow_symlinks(void){
18412083
return g.allowSymlinks;
@@ -1877,13 +2119,14 @@
18772119
g.zRepositoryName = mprintf("%s", zDbName);
18782120
db_open_or_attach(g.zRepositoryName, "repository");
18792121
g.repositoryOpen = 1;
18802122
sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
18812123
&g.iRepoDataVers);
2124
+
18822125
/* Cache "allow-symlinks" option, because we'll need it on every stat call */
1883
- g.allowSymlinks = db_get_boolean("allow-symlinks",
1884
- db_allow_symlinks_by_default());
2126
+ g.allowSymlinks = db_get_boolean("allow-symlinks",0);
2127
+
18852128
g.zAuxSchema = db_get("aux-schema","");
18862129
g.eHashPolicy = db_get_int("hash-policy",-1);
18872130
if( g.eHashPolicy<0 ){
18882131
g.eHashPolicy = hname_default_policy();
18892132
db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2169,11 +2412,13 @@
21692412
*/
21702413
if( db_database_slot("localdb")>=0 ){
21712414
int nFree = db_int(0, "PRAGMA localdb.freelist_count");
21722415
int nTotal = db_int(0, "PRAGMA localdb.page_count");
21732416
if( nFree>nTotal/4 ){
2417
+ db_unprotect(PROTECT_ALL);
21742418
db_multi_exec("VACUUM localdb;");
2419
+ db_protect_pop();
21752420
}
21762421
}
21772422
21782423
if( g.db ){
21792424
int rc;
@@ -2187,10 +2432,11 @@
21872432
}
21882433
g.db = 0;
21892434
}
21902435
g.repositoryOpen = 0;
21912436
g.localOpen = 0;
2437
+ db.bProtectTriggers = 0;
21922438
assert( g.dbConfig==0 );
21932439
assert( g.zConfigDbName==0 );
21942440
backoffice_run_if_needed();
21952441
}
21962442
@@ -2249,10 +2495,11 @@
22492495
zUser = fossil_getenv("USERNAME");
22502496
}
22512497
if( zUser==0 ){
22522498
zUser = "root";
22532499
}
2500
+ db_unprotect(PROTECT_USER);
22542501
db_multi_exec(
22552502
"INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
22562503
);
22572504
db_multi_exec(
22582505
"UPDATE user SET cap='s', pw=%Q"
@@ -2268,10 +2515,11 @@
22682515
" VALUES('developer','','ei','Dev');"
22692516
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
22702517
" VALUES('reader','','kptw','Reader');"
22712518
);
22722519
}
2520
+ db_protect_pop();
22732521
}
22742522
22752523
/*
22762524
** Return a pointer to a string that contains the RHS of an IN operator
22772525
** that will select CONFIG table names that are in the list of control
@@ -2319,10 +2567,11 @@
23192567
){
23202568
char *zDate;
23212569
Blob hash;
23222570
Blob manifest;
23232571
2572
+ db_unprotect(PROTECT_ALL);
23242573
db_set("content-schema", CONTENT_SCHEMA, 0);
23252574
db_set("aux-schema", AUX_SCHEMA_MAX, 0);
23262575
db_set("rebuilt", get_version(), 0);
23272576
db_set("admin-log", "1", 0);
23282577
db_set("access-log", "1", 0);
@@ -2377,10 +2626,11 @@
23772626
" photo = (SELECT u2.photo FROM settingSrc.user u2"
23782627
" WHERE u2.login = user.login)"
23792628
" WHERE user.login IN ('anonymous','nobody','developer','reader');"
23802629
);
23812630
}
2631
+ db_protect_pop();
23822632
23832633
if( zInitialDate ){
23842634
int rid;
23852635
blob_zero(&manifest);
23862636
blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2873,10 +3123,12 @@
28733123
z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
28743124
}
28753125
return z;
28763126
}
28773127
void db_set(const char *zName, const char *zValue, int globalFlag){
3128
+ db_assert_protection_off_or_not_sensitive(zName);
3129
+ db_unprotect(PROTECT_CONFIG);
28783130
db_begin_transaction();
28793131
if( globalFlag ){
28803132
db_swap_connections();
28813133
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
28823134
zName, zValue);
@@ -2887,13 +3139,15 @@
28873139
}
28883140
if( globalFlag && g.repositoryOpen ){
28893141
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
28903142
}
28913143
db_end_transaction(0);
3144
+ db_protect_pop();
28923145
}
28933146
void db_unset(const char *zName, int globalFlag){
28943147
db_begin_transaction();
3148
+ db_unprotect(PROTECT_CONFIG);
28953149
if( globalFlag ){
28963150
db_swap_connections();
28973151
db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
28983152
db_swap_connections();
28993153
}else{
@@ -2900,10 +3154,11 @@
29003154
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
29013155
}
29023156
if( globalFlag && g.repositoryOpen ){
29033157
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
29043158
}
3159
+ db_protect_pop();
29053160
db_end_transaction(0);
29063161
}
29073162
int db_is_global(const char *zName){
29083163
int rc = 0;
29093164
if( g.zConfigDbName ){
@@ -2933,10 +3188,12 @@
29333188
db_swap_connections();
29343189
}
29353190
return v;
29363191
}
29373192
void db_set_int(const char *zName, int value, int globalFlag){
3193
+ db_assert_protection_off_or_not_sensitive(zName);
3194
+ db_unprotect(PROTECT_CONFIG);
29383195
if( globalFlag ){
29393196
db_swap_connections();
29403197
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
29413198
zName, value);
29423199
db_swap_connections();
@@ -2945,10 +3202,11 @@
29453202
zName, value);
29463203
}
29473204
if( globalFlag && g.repositoryOpen ){
29483205
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
29493206
}
3207
+ db_protect_pop();
29503208
}
29513209
int db_get_boolean(const char *zName, int dflt){
29523210
char *zVal = db_get(zName, dflt ? "on" : "off");
29533211
if( is_truth(zVal) ){
29543212
dflt = 1;
@@ -2956,19 +3214,17 @@
29563214
dflt = 0;
29573215
}
29583216
fossil_free(zVal);
29593217
return dflt;
29603218
}
2961
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
29623219
int db_get_versioned_boolean(const char *zName, int dflt){
29633220
char *zVal = db_get_versioned(zName, 0);
29643221
if( zVal==0 ) return dflt;
29653222
if( is_truth(zVal) ) return 1;
29663223
if( is_false(zVal) ) return 0;
29673224
return dflt;
29683225
}
2969
-#endif /* FOSSIL_LEGACY_ALLOW_SYMLINKS */
29703226
char *db_lget(const char *zName, const char *zDefault){
29713227
return db_text(zDefault,
29723228
"SELECT value FROM vvar WHERE name=%Q", zName);
29733229
}
29743230
void db_lset(const char *zName, const char *zValue){
@@ -3076,24 +3332,28 @@
30763332
}
30773333
file_canonical_name(zName, &full, 0);
30783334
(void)filename_collation(); /* Initialize before connection swap */
30793335
db_swap_connections();
30803336
zRepoSetting = mprintf("repo:%q", blob_str(&full));
3337
+
3338
+ db_unprotect(PROTECT_CONFIG);
30813339
db_multi_exec(
30823340
"DELETE FROM global_config WHERE name %s = %Q;",
30833341
filename_collation(), zRepoSetting
30843342
);
30853343
db_multi_exec(
30863344
"INSERT OR IGNORE INTO global_config(name,value)"
30873345
"VALUES(%Q,1);",
30883346
zRepoSetting
30893347
);
3348
+ db_protect_pop();
30903349
fossil_free(zRepoSetting);
30913350
if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
30923351
Blob localRoot;
30933352
file_canonical_name(g.zLocalRoot, &localRoot, 1);
30943353
zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
3354
+ db_unprotect(PROTECT_CONFIG);
30953355
db_multi_exec(
30963356
"DELETE FROM global_config WHERE name %s = %Q;",
30973357
filename_collation(), zCkoutSetting
30983358
);
30993359
db_multi_exec(
@@ -3109,10 +3369,11 @@
31093369
db_optional_sql("repository",
31103370
"REPLACE INTO config(name,value,mtime)"
31113371
"VALUES(%Q,1,now());",
31123372
zCkoutSetting
31133373
);
3374
+ db_protect_pop();
31143375
fossil_free(zCkoutSetting);
31153376
blob_reset(&localRoot);
31163377
}else{
31173378
db_swap_connections();
31183379
}
@@ -3147,11 +3408,11 @@
31473408
**
31483409
** Options:
31493410
** --empty Initialize checkout as being empty, but still connected
31503411
** with the local repository. If you commit this checkout,
31513412
** it will become a new "initial" commit in the repository.
3152
-** --force Continue with the open even if the working directory is
3413
+** -f|--force Continue with the open even if the working directory is
31533414
** not empty.
31543415
** --force-missing Force opening a repository with missing content
31553416
** --keep Only modify the manifest and manifest.uuid files
31563417
** --nested Allow opening a repository inside an opened checkout
31573418
** --repodir DIR If REPOSITORY is a URI that will be cloned, store
@@ -3167,13 +3428,10 @@
31673428
void cmd_open(void){
31683429
int emptyFlag;
31693430
int keepFlag;
31703431
int forceMissingFlag;
31713432
int allowNested;
3172
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3173
- int allowSymlinks;
3174
-#endif
31753433
int setmtimeFlag; /* --setmtime. Set mtimes on files */
31763434
int bForce = 0; /* --force. Open even if non-empty dir */
31773435
static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
31783436
const char *zWorkDir; /* --workdir value */
31793437
const char *zRepo = 0; /* Name of the repository file */
@@ -3187,11 +3445,11 @@
31873445
forceMissingFlag = find_option("force-missing",0,0)!=0;
31883446
allowNested = find_option("nested",0,0)!=0;
31893447
setmtimeFlag = find_option("setmtime",0,0)!=0;
31903448
zWorkDir = find_option("workdir",0,1);
31913449
zRepoDir = find_option("repodir",0,1);
3192
- bForce = find_option("force",0,0)!=0;
3450
+ bForce = find_option("force","f",0)!=0;
31933451
zPwd = file_getcwd(0,0);
31943452
31953453
31963454
/* We should be done with options.. */
31973455
verify_all_options();
@@ -3226,11 +3484,11 @@
32263484
fossil_fatal("unable to make %s the working directory", zWorkDir);
32273485
}
32283486
}
32293487
if( keepFlag==0 && bForce==0 && file_directory_size(".", 0, 1)>0 ){
32303488
fossil_fatal("directory %s is not empty\n"
3231
- "use the --force option to override", file_getcwd(0,0));
3489
+ "use the -f or --force option to override", file_getcwd(0,0));
32323490
}
32333491
32343492
if( db_open_local_v2(0, allowNested) ){
32353493
fossil_fatal("there is already an open tree at %s", g.zLocalRoot);
32363494
}
@@ -3280,25 +3538,10 @@
32803538
}else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
32813539
g.zOpenRevision = db_get("main-branch", 0);
32823540
}
32833541
}
32843542
3285
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3286
- if( g.zOpenRevision ){
3287
- /* Since the repository is open and we know the revision now,
3288
- ** refresh the allow-symlinks flag. Since neither the local
3289
- ** checkout nor the configuration database are open at this
3290
- ** point, this should always return the versioned setting,
3291
- ** if any, or the default value, which is negative one. The
3292
- ** value negative one, in this context, means that the code
3293
- ** below should fallback to using the setting value from the
3294
- ** repository or global configuration databases only. */
3295
- allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
3296
- }else{
3297
- allowSymlinks = -1; /* Use non-versioned settings only. */
3298
- }
3299
-#endif
33003543
33013544
#if defined(_WIN32) || defined(__CYGWIN__)
33023545
# define LOCALDB_NAME "./_FOSSIL_"
33033546
#else
33043547
# define LOCALDB_NAME "./.fslckout"
@@ -3308,28 +3551,10 @@
33083551
"COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
33093552
#endif
33103553
(char*)0);
33113554
db_delete_on_failure(LOCALDB_NAME);
33123555
db_open_local(0);
3313
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3314
- if( allowSymlinks>=0 ){
3315
- /* Use the value from the versioned setting, which was read
3316
- ** prior to opening the local checkout (i.e. which is most
3317
- ** likely empty and does not actually contain any versioned
3318
- ** setting files yet). Normally, this value would be given
3319
- ** first priority within db_get_boolean(); however, this is
3320
- ** a special case because we know the on-disk files may not
3321
- ** exist yet. */
3322
- g.allowSymlinks = allowSymlinks;
3323
- }else{
3324
- /* Since the local checkout may not have any files at this
3325
- ** point, this will probably be the setting value from the
3326
- ** repository or global configuration databases. */
3327
- g.allowSymlinks = db_get_boolean("allow-symlinks",
3328
- db_allow_symlinks_by_default());
3329
- }
3330
-#endif /* FOSSIL_LEGACY_ALLOW_SYMLINKS */
33313556
db_lset("repository", zRepo);
33323557
db_record_repository_filename(zRepo);
33333558
db_set_checkout(0);
33343559
azNewArgv[0] = g.argv[0];
33353560
g.argv = azNewArgv;
@@ -3418,12 +3643,13 @@
34183643
const char *name; /* Name of the setting */
34193644
const char *var; /* Internal variable name used by db_set() */
34203645
int width; /* Width of display. 0 for boolean values and
34213646
** negative for values which should not appear
34223647
** on the /setup_settings page. */
3423
- int versionable; /* Is this setting versionable? */
3424
- int forceTextArea; /* Force using a text area for display? */
3648
+ char versionable; /* Is this setting versionable? */
3649
+ char forceTextArea; /* Force using a text area for display? */
3650
+ char sensitive; /* True if this a security-sensitive setting */
34253651
const char *def; /* Default value */
34263652
};
34273653
#endif /* INTERFACE */
34283654
34293655
/*
@@ -3437,50 +3663,29 @@
34373663
** SETTING: admin-log boolean default=off
34383664
**
34393665
** When the admin-log setting is enabled, configuration changes are recorded
34403666
** in the "admin_log" table of the repository.
34413667
*/
3442
-#if !defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3443
-/*
3444
-** SETTING: allow-symlinks boolean default=off
3445
-**
3446
-** When allow-symlinks is OFF (which is the default and recommended setting)
3447
-** symbolic links are treated like text files that contain a single line of
3448
-** content which is the name of their target. If allow-symlinks is ON,
3449
-** the symbolic links are actually followed.
3450
-**
3451
-** The use of symbolic links is dangerous. If you checkout a maliciously
3452
-** crafted checkin that contains symbolic links, it is possible that files
3453
-** outside of the working directory might be overwritten.
3454
-**
3455
-** Keep this setting OFF unless you have a very good reason to turn it
3456
-** on and you implicitly trust the integrity of the repositories you
3457
-** open.
3458
-*/
3459
-#endif
3460
-#if defined(_WIN32) && defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3461
-/*
3462
-** SETTING: allow-symlinks boolean default=off versionable
3463
-**
3464
-** When allow-symlinks is OFF, symbolic links in the repository are followed
3465
-** and treated no differently from real files. When allow-symlinks is ON,
3466
-** the object to which the symbolic link points is ignored, and the content
3467
-** of the symbolic link that is stored in the repository is the name of the
3468
-** object to which the symbolic link points.
3469
-*/
3470
-#endif
3471
-#if !defined(_WIN32) && defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3472
-/*
3473
-** SETTING: allow-symlinks boolean default=on versionable
3474
-**
3475
-** When allow-symlinks is OFF, symbolic links in the repository are followed
3476
-** and treated no differently from real files. When allow-symlinks is ON,
3477
-** the object to which the symbolic link points is ignored, and the content
3478
-** of the symbolic link that is stored in the repository is the name of the
3479
-** object to which the symbolic link points.
3480
-*/
3481
-#endif
3668
+/*
3669
+** SETTING: allow-symlinks boolean default=off sensitive
3670
+**
3671
+** When allow-symlinks is OFF, Fossil does not see symbolic links
3672
+** (a.k.a "symlinks") on disk as a separate class of object. Instead Fossil
3673
+** sees the object that the symlink points to. Fossil will only manage files
3674
+** and directories, not symlinks. When a symlink is added to a repository,
3675
+** the object that the symlink points to is added, not the symlink itself.
3676
+**
3677
+** When allow-symlinks is ON, Fossil sees symlinks on disk as a separate
3678
+** object class that is distinct from files and directories. When a symlink
3679
+** is added to a repository, Fossil stores the target filename. In other
3680
+** words, Fossil stores the symlink itself, not the object that the symlink
3681
+** points to.
3682
+**
3683
+** Symlinks are not cross-platform. They are not available on all
3684
+** operating systems and file systems. Hence the allow-symlinks setting is
3685
+** OFF by default, for portability.
3686
+*/
34823687
/*
34833688
** SETTING: auto-captcha boolean default=on variable=autocaptcha
34843689
** If enabled, the /login page provides a button that will automatically
34853690
** fill in the captcha password. This makes things easier for human users,
34863691
** at the expense of also making logins easier for malicious robots.
@@ -3530,11 +3735,11 @@
35303735
** there is no cron job periodically running "fossil backoffice",
35313736
** email notifications and other work normally done by the
35323737
** backoffice will not occur.
35333738
*/
35343739
/*
3535
-** SETTING: backoffice-logfile width=40
3740
+** SETTING: backoffice-logfile width=40 sensitive
35363741
** If backoffice-logfile is not an empty string and is a valid
35373742
** filename, then a one-line message is appended to that file
35383743
** every time the backoffice runs. This can be used for debugging,
35393744
** to ensure that backoffice is running appropriately.
35403745
*/
@@ -3607,11 +3812,11 @@
36073812
/*
36083813
** SETTING: crnl-glob width=40 versionable block-text
36093814
** This is an alias for the crlf-glob setting.
36103815
*/
36113816
/*
3612
-** SETTING: default-perms width=16 default=u
3817
+** SETTING: default-perms width=16 default=u sensitive
36133818
** Permissions given automatically to new users. For more
36143819
** information on permissions see the Users page in Server
36153820
** Administration of the HTTP UI.
36163821
*/
36173822
/*
@@ -3619,11 +3824,11 @@
36193824
** If enabled, permit files that may be binary
36203825
** or that match the "binary-glob" setting to be used with
36213826
** external diff programs. If disabled, skip these files.
36223827
*/
36233828
/*
3624
-** SETTING: diff-command width=40
3829
+** SETTING: diff-command width=40 sensitive
36253830
** The value is an external command to run when performing a diff.
36263831
** If undefined, the internal text diff will be used.
36273832
*/
36283833
/*
36293834
** SETTING: dont-push boolean default=off
@@ -3634,11 +3839,11 @@
36343839
/*
36353840
** SETTING: dotfiles boolean versionable default=off
36363841
** If enabled, include --dotfiles option for all compatible commands.
36373842
*/
36383843
/*
3639
-** SETTING: editor width=32
3844
+** SETTING: editor width=32 sensitive
36403845
** The value is an external command that will launch the
36413846
** text editor command used for check-in comments.
36423847
*/
36433848
/*
36443849
** SETTING: empty-dirs width=40 versionable block-text
@@ -3677,16 +3882,16 @@
36773882
** An empty list prohibits editing via that page. Note that
36783883
** it cannot edit binary files, so the list should not
36793884
** contain any globs for, e.g., images or PDFs.
36803885
*/
36813886
/*
3682
-** SETTING: gdiff-command width=40 default=gdiff
3887
+** SETTING: gdiff-command width=40 default=gdiff sensitive
36833888
** The value is an external command to run when performing a graphical
36843889
** diff. If undefined, text diff will be used.
36853890
*/
36863891
/*
3687
-** SETTING: gmerge-command width=40
3892
+** SETTING: gmerge-command width=40 sensitive
36883893
** The value is a graphical merge conflict resolver command operating
36893894
** on four files. Examples:
36903895
**
36913896
** kdiff3 "%baseline" "%original" "%merge" -o "%output"
36923897
** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3817,11 +4022,11 @@
38174022
** the associated files within the checkout -AND- the "rm"
38184023
** and "delete" commands will also remove the associated
38194024
** files from within the checkout.
38204025
*/
38214026
/*
3822
-** SETTING: pgp-command width=40
4027
+** SETTING: pgp-command width=40 sensitive
38234028
** Command used to clear-sign manifests at check-in.
38244029
** Default value is "gpg --clearsign -o"
38254030
*/
38264031
/*
38274032
** SETTING: forbid-delta-manifests boolean default=off
@@ -3877,22 +4082,22 @@
38774082
**
38784083
** If repolist-skin has a value of 2, then the repository is omitted from
38794084
** the list in use cases 1 through 4, but not for 5 and 6.
38804085
*/
38814086
/*
3882
-** SETTING: self-register boolean default=off
4087
+** SETTING: self-register boolean default=off sensitive
38834088
** Allow users to register themselves through the HTTP UI.
38844089
** This is useful if you want to see other names than
38854090
** "Anonymous" in e.g. ticketing system. On the other hand
38864091
** users can not be deleted.
38874092
*/
38884093
/*
3889
-** SETTING: ssh-command width=40
4094
+** SETTING: ssh-command width=40 sensitive
38904095
** The command used to talk to a remote machine with the "ssh://" protocol.
38914096
*/
38924097
/*
3893
-** SETTING: ssl-ca-location width=40
4098
+** SETTING: ssl-ca-location width=40 sensitive
38944099
** The full pathname to a file containing PEM encoded
38954100
** CA root certificates, or a directory of certificates
38964101
** with filenames formed from the certificate hashes as
38974102
** required by OpenSSL.
38984103
**
@@ -3902,11 +4107,11 @@
39024107
** Checking your platform behaviour is required if the
39034108
** exact contents of the CA root is critical for your
39044109
** application.
39054110
*/
39064111
/*
3907
-** SETTING: ssl-identity width=40
4112
+** SETTING: ssl-identity width=40 sensitive
39084113
** The full pathname to a file containing a certificate
39094114
** and private key in PEM format. Create by concatenating
39104115
** the certificate and private key files.
39114116
**
39124117
** This identity will be presented to SSL servers to
@@ -3913,33 +4118,33 @@
39134118
** authenticate this client, in addition to the normal
39144119
** password authentication.
39154120
*/
39164121
#ifdef FOSSIL_ENABLE_TCL
39174122
/*
3918
-** SETTING: tcl boolean default=off
4123
+** SETTING: tcl boolean default=off sensitive
39194124
** If enabled Tcl integration commands will be added to the TH1
39204125
** interpreter, allowing arbitrary Tcl expressions and
39214126
** scripts to be evaluated from TH1. Additionally, the Tcl
39224127
** interpreter will be able to evaluate arbitrary TH1
39234128
** expressions and scripts.
39244129
*/
39254130
/*
3926
-** SETTING: tcl-setup width=40 block-text
4131
+** SETTING: tcl-setup width=40 block-text sensitive
39274132
** This is the setup script to be evaluated after creating
39284133
** and initializing the Tcl interpreter. By default, this
39294134
** is empty and no extra setup is performed.
39304135
*/
39314136
#endif /* FOSSIL_ENABLE_TCL */
39324137
/*
3933
-** SETTING: tclsh width=80 default=tclsh
4138
+** SETTING: tclsh width=80 default=tclsh sensitive
39344139
** Name of the external TCL interpreter used for such things
39354140
** as running the GUI diff viewer launched by the --tk option
39364141
** of the various "diff" commands.
39374142
*/
39384143
#ifdef FOSSIL_ENABLE_TH1_DOCS
39394144
/*
3940
-** SETTING: th1-docs boolean default=off
4145
+** SETTING: th1-docs boolean default=off sensitive
39414146
** If enabled, this allows embedded documentation files to contain
39424147
** arbitrary TH1 scripts that are evaluated on the server. If native
39434148
** Tcl integration is also enabled, this setting has the
39444149
** potential to allow anybody with check-in privileges to
39454150
** do almost anything that the associated operating system
@@ -3992,11 +4197,11 @@
39924197
** of a "fossil clone" or "fossil sync" command. The
39934198
** default is false, in which case the -u option is
39944199
** needed to clone or sync unversioned files.
39954200
*/
39964201
/*
3997
-** SETTING: web-browser width=30
4202
+** SETTING: web-browser width=30 sensitive
39984203
** A shell command used to launch your preferred
39994204
** web browser when given a URL as an argument.
40004205
** Defaults to "start" on windows, "open" on Mac,
40014206
** and "firefox" on Unix.
40024207
*/
@@ -4118,11 +4323,13 @@
41184323
fossil_fatal("cannot set 'manifest' globally");
41194324
}
41204325
if( unsetFlag ){
41214326
db_unset(pSetting->name, globalFlag);
41224327
}else{
4328
+ db_protect_only(PROTECT_NONE);
41234329
db_set(pSetting->name, g.argv[3], globalFlag);
4330
+ db_protect_pop();
41244331
}
41254332
if( isManifest && g.localOpen ){
41264333
manifest_to_disk(db_lget_int("checkout", 0));
41274334
}
41284335
}else{
41294336
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
69 #endif /* INTERFACE */
70 const struct Stmt empty_Stmt = empty_Stmt_m;
71
72 /*
73 ** Call this routine when a database error occurs.
 
74 */
75 static void db_err(const char *zFormat, ...){
76 va_list ap;
77 char *z;
78 va_start(ap, zFormat);
@@ -113,10 +114,11 @@
113 /*
114 ** All static variable that a used by only this file are gathered into
115 ** the following structure.
116 */
117 static struct DbLocalData {
 
118 int nBegin; /* Nesting depth of BEGIN */
119 int doRollback; /* True to force a rollback */
120 int nCommitHook; /* Number of commit hooks */
121 int wrTxn; /* Outer-most TNX is a write */
122 Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -133,11 +135,16 @@
133 const char *zStartFile; /* File in which transaction was started */
134 int iStartLine; /* Line of zStartFile where transaction started */
135 int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
136 void *pAuthArg; /* Argument to the authorizer */
137 const char *zAuthName; /* Name of the authorizer */
138 } db = {0, 0, 0, 0, 0, 0, };
 
 
 
 
 
139
140 /*
141 ** Arrange for the given file to be deleted on a failure.
142 */
143 void db_delete_on_failure(const char *zFilename){
@@ -241,17 +248,19 @@
241 db.nBegin--;
242 if( db.nBegin==0 ){
243 int i;
244 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
245 i = 0;
 
246 while( db.nBeforeCommit ){
247 db.nBeforeCommit--;
248 sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
249 sqlite3_free(db.azBeforeCommit[i]);
250 i++;
251 }
252 leaf_do_pending_checks();
 
253 }
254 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
255 int rc = db.aHook[i].xHook();
256 if( rc ){
257 db.doRollback = 1;
@@ -319,10 +328,205 @@
319 }
320 db.aHook[db.nCommitHook].sequence = sequence;
321 db.aHook[db.nCommitHook].xHook = x;
322 db.nCommitHook++;
323 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
325 /*
326 ** Set or unset the query authorizer callback function
327 */
328 void db_set_authorizer(
@@ -331,23 +535,22 @@
331 const char *zName /* for tracing */
332 ){
333 if( db.xAuth ){
334 fossil_panic("multiple active db_set_authorizer() calls");
335 }
336 if( g.db ) sqlite3_set_authorizer(g.db, xAuth, pArg);
337 db.xAuth = xAuth;
338 db.pAuthArg = pArg;
339 db.zAuthName = zName;
340 if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
341 }
342 void db_clear_authorizer(void){
343 if( db.zAuthName && g.fSqlTrace ){
344 fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
345 }
346 if( g.db ) sqlite3_set_authorizer(g.db, 0, 0);
347 db.xAuth = 0;
348 db.pAuthArg = 0;
 
349 }
350
351 #if INTERFACE
352 /*
353 ** Possible flags to db_vprepare
@@ -363,21 +566,24 @@
363 */
364 int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
365 int rc;
366 int prepFlags = 0;
367 char *zSql;
 
368 blob_zero(&pStmt->sql);
369 blob_vappendf(&pStmt->sql, zFormat, ap);
370 va_end(ap);
371 zSql = blob_str(&pStmt->sql);
372 db.nPrepare++;
373 if( flags & DB_PREPARE_PERSISTENT ){
374 prepFlags = SQLITE_PREPARE_PERSISTENT;
375 }
376 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, 0);
377 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
378 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
 
 
379 }
380 pStmt->pNext = db.pAllStmt;
381 pStmt->pPrev = 0;
382 if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
383 db.pAllStmt = pStmt;
@@ -640,10 +846,11 @@
640 return rc;
641 }
642
643 /*
644 ** COMMAND: test-db-exec-error
 
645 **
646 ** Invoke the db_exec() interface with an erroneous SQL statement
647 ** in order to verify the error handling logic.
648 */
649 void db_test_db_exec_cmd(void){
@@ -650,10 +857,27 @@
650 Stmt err;
651 db_find_and_open_repository(0,0);
652 db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
653 db_exec(&err);
654 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
656 /*
657 ** Print the output of one or more SQL queries on standard output.
658 ** This routine is used for debugging purposes only.
659 */
@@ -880,13 +1104,10 @@
880 const char *zSql;
881 va_list ap;
882
883 xdb = db_open(zFileName ? zFileName : ":memory:");
884 sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
885 if( db.xAuth ){
886 sqlite3_set_authorizer(xdb, db.xAuth, db.pAuthArg);
887 }
888 rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
889 if( rc!=SQLITE_OK ){
890 db_err("%s", sqlite3_errmsg(xdb));
891 }
892 va_start(ap, zSchema);
@@ -1092,10 +1313,37 @@
1092 }
1093 strcpy(zOut, zTemp = obscure((char*)zIn));
1094 fossil_free(zTemp);
1095 sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
1096 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
1098 /*
1099 ** Register the SQL functions that are useful both to the internal
1100 ** representation and to the "fossil sql" command.
1101 */
@@ -1122,10 +1370,12 @@
1122 alert_find_emailaddr_func, 0, 0);
1123 sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1124 alert_display_name_func, 0, 0);
1125 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1126 db_obscure, 0, 0);
 
 
1127 }
1128
1129 #if USE_SEE
1130 /*
1131 ** This is a pointer to the saved database encryption key string.
@@ -1380,10 +1630,11 @@
1380 if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
1381 db_add_aux_functions(db);
1382 re_add_sql_func(db); /* The REGEXP operator */
1383 foci_register(db); /* The "files_of_checkin" virtual table */
1384 sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
 
1385 return db;
1386 }
1387
1388
1389 /*
@@ -1815,27 +2066,18 @@
1815 zRepo = db_lget("repository", 0);
1816 if( zRepo && !file_is_absolute_path(zRepo) ){
1817 char * zFree = zRepo;
1818 zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
1819 fossil_free(zFree);
 
 
 
1820 }
1821 }
1822 return zRepo;
1823 }
1824
1825 /*
1826 ** Returns non-zero if the default value for the "allow-symlinks" setting
1827 ** is "on". When on Windows, this always returns false.
1828 */
1829 int db_allow_symlinks_by_default(void){
1830 #if defined(_WIN32) || !defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
1831 return 0;
1832 #else
1833 return 1;
1834 #endif
1835 }
1836
1837 /*
1838 ** Returns non-zero if support for symlinks is currently enabled.
1839 */
1840 int db_allow_symlinks(void){
1841 return g.allowSymlinks;
@@ -1877,13 +2119,14 @@
1877 g.zRepositoryName = mprintf("%s", zDbName);
1878 db_open_or_attach(g.zRepositoryName, "repository");
1879 g.repositoryOpen = 1;
1880 sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
1881 &g.iRepoDataVers);
 
1882 /* Cache "allow-symlinks" option, because we'll need it on every stat call */
1883 g.allowSymlinks = db_get_boolean("allow-symlinks",
1884 db_allow_symlinks_by_default());
1885 g.zAuxSchema = db_get("aux-schema","");
1886 g.eHashPolicy = db_get_int("hash-policy",-1);
1887 if( g.eHashPolicy<0 ){
1888 g.eHashPolicy = hname_default_policy();
1889 db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2169,11 +2412,13 @@
2169 */
2170 if( db_database_slot("localdb")>=0 ){
2171 int nFree = db_int(0, "PRAGMA localdb.freelist_count");
2172 int nTotal = db_int(0, "PRAGMA localdb.page_count");
2173 if( nFree>nTotal/4 ){
 
2174 db_multi_exec("VACUUM localdb;");
 
2175 }
2176 }
2177
2178 if( g.db ){
2179 int rc;
@@ -2187,10 +2432,11 @@
2187 }
2188 g.db = 0;
2189 }
2190 g.repositoryOpen = 0;
2191 g.localOpen = 0;
 
2192 assert( g.dbConfig==0 );
2193 assert( g.zConfigDbName==0 );
2194 backoffice_run_if_needed();
2195 }
2196
@@ -2249,10 +2495,11 @@
2249 zUser = fossil_getenv("USERNAME");
2250 }
2251 if( zUser==0 ){
2252 zUser = "root";
2253 }
 
2254 db_multi_exec(
2255 "INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
2256 );
2257 db_multi_exec(
2258 "UPDATE user SET cap='s', pw=%Q"
@@ -2268,10 +2515,11 @@
2268 " VALUES('developer','','ei','Dev');"
2269 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2270 " VALUES('reader','','kptw','Reader');"
2271 );
2272 }
 
2273 }
2274
2275 /*
2276 ** Return a pointer to a string that contains the RHS of an IN operator
2277 ** that will select CONFIG table names that are in the list of control
@@ -2319,10 +2567,11 @@
2319 ){
2320 char *zDate;
2321 Blob hash;
2322 Blob manifest;
2323
 
2324 db_set("content-schema", CONTENT_SCHEMA, 0);
2325 db_set("aux-schema", AUX_SCHEMA_MAX, 0);
2326 db_set("rebuilt", get_version(), 0);
2327 db_set("admin-log", "1", 0);
2328 db_set("access-log", "1", 0);
@@ -2377,10 +2626,11 @@
2377 " photo = (SELECT u2.photo FROM settingSrc.user u2"
2378 " WHERE u2.login = user.login)"
2379 " WHERE user.login IN ('anonymous','nobody','developer','reader');"
2380 );
2381 }
 
2382
2383 if( zInitialDate ){
2384 int rid;
2385 blob_zero(&manifest);
2386 blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2873,10 +3123,12 @@
2873 z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
2874 }
2875 return z;
2876 }
2877 void db_set(const char *zName, const char *zValue, int globalFlag){
 
 
2878 db_begin_transaction();
2879 if( globalFlag ){
2880 db_swap_connections();
2881 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
2882 zName, zValue);
@@ -2887,13 +3139,15 @@
2887 }
2888 if( globalFlag && g.repositoryOpen ){
2889 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2890 }
2891 db_end_transaction(0);
 
2892 }
2893 void db_unset(const char *zName, int globalFlag){
2894 db_begin_transaction();
 
2895 if( globalFlag ){
2896 db_swap_connections();
2897 db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
2898 db_swap_connections();
2899 }else{
@@ -2900,10 +3154,11 @@
2900 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2901 }
2902 if( globalFlag && g.repositoryOpen ){
2903 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2904 }
 
2905 db_end_transaction(0);
2906 }
2907 int db_is_global(const char *zName){
2908 int rc = 0;
2909 if( g.zConfigDbName ){
@@ -2933,10 +3188,12 @@
2933 db_swap_connections();
2934 }
2935 return v;
2936 }
2937 void db_set_int(const char *zName, int value, int globalFlag){
 
 
2938 if( globalFlag ){
2939 db_swap_connections();
2940 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
2941 zName, value);
2942 db_swap_connections();
@@ -2945,10 +3202,11 @@
2945 zName, value);
2946 }
2947 if( globalFlag && g.repositoryOpen ){
2948 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2949 }
 
2950 }
2951 int db_get_boolean(const char *zName, int dflt){
2952 char *zVal = db_get(zName, dflt ? "on" : "off");
2953 if( is_truth(zVal) ){
2954 dflt = 1;
@@ -2956,19 +3214,17 @@
2956 dflt = 0;
2957 }
2958 fossil_free(zVal);
2959 return dflt;
2960 }
2961 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
2962 int db_get_versioned_boolean(const char *zName, int dflt){
2963 char *zVal = db_get_versioned(zName, 0);
2964 if( zVal==0 ) return dflt;
2965 if( is_truth(zVal) ) return 1;
2966 if( is_false(zVal) ) return 0;
2967 return dflt;
2968 }
2969 #endif /* FOSSIL_LEGACY_ALLOW_SYMLINKS */
2970 char *db_lget(const char *zName, const char *zDefault){
2971 return db_text(zDefault,
2972 "SELECT value FROM vvar WHERE name=%Q", zName);
2973 }
2974 void db_lset(const char *zName, const char *zValue){
@@ -3076,24 +3332,28 @@
3076 }
3077 file_canonical_name(zName, &full, 0);
3078 (void)filename_collation(); /* Initialize before connection swap */
3079 db_swap_connections();
3080 zRepoSetting = mprintf("repo:%q", blob_str(&full));
 
 
3081 db_multi_exec(
3082 "DELETE FROM global_config WHERE name %s = %Q;",
3083 filename_collation(), zRepoSetting
3084 );
3085 db_multi_exec(
3086 "INSERT OR IGNORE INTO global_config(name,value)"
3087 "VALUES(%Q,1);",
3088 zRepoSetting
3089 );
 
3090 fossil_free(zRepoSetting);
3091 if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
3092 Blob localRoot;
3093 file_canonical_name(g.zLocalRoot, &localRoot, 1);
3094 zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
 
3095 db_multi_exec(
3096 "DELETE FROM global_config WHERE name %s = %Q;",
3097 filename_collation(), zCkoutSetting
3098 );
3099 db_multi_exec(
@@ -3109,10 +3369,11 @@
3109 db_optional_sql("repository",
3110 "REPLACE INTO config(name,value,mtime)"
3111 "VALUES(%Q,1,now());",
3112 zCkoutSetting
3113 );
 
3114 fossil_free(zCkoutSetting);
3115 blob_reset(&localRoot);
3116 }else{
3117 db_swap_connections();
3118 }
@@ -3147,11 +3408,11 @@
3147 **
3148 ** Options:
3149 ** --empty Initialize checkout as being empty, but still connected
3150 ** with the local repository. If you commit this checkout,
3151 ** it will become a new "initial" commit in the repository.
3152 ** --force Continue with the open even if the working directory is
3153 ** not empty.
3154 ** --force-missing Force opening a repository with missing content
3155 ** --keep Only modify the manifest and manifest.uuid files
3156 ** --nested Allow opening a repository inside an opened checkout
3157 ** --repodir DIR If REPOSITORY is a URI that will be cloned, store
@@ -3167,13 +3428,10 @@
3167 void cmd_open(void){
3168 int emptyFlag;
3169 int keepFlag;
3170 int forceMissingFlag;
3171 int allowNested;
3172 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3173 int allowSymlinks;
3174 #endif
3175 int setmtimeFlag; /* --setmtime. Set mtimes on files */
3176 int bForce = 0; /* --force. Open even if non-empty dir */
3177 static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
3178 const char *zWorkDir; /* --workdir value */
3179 const char *zRepo = 0; /* Name of the repository file */
@@ -3187,11 +3445,11 @@
3187 forceMissingFlag = find_option("force-missing",0,0)!=0;
3188 allowNested = find_option("nested",0,0)!=0;
3189 setmtimeFlag = find_option("setmtime",0,0)!=0;
3190 zWorkDir = find_option("workdir",0,1);
3191 zRepoDir = find_option("repodir",0,1);
3192 bForce = find_option("force",0,0)!=0;
3193 zPwd = file_getcwd(0,0);
3194
3195
3196 /* We should be done with options.. */
3197 verify_all_options();
@@ -3226,11 +3484,11 @@
3226 fossil_fatal("unable to make %s the working directory", zWorkDir);
3227 }
3228 }
3229 if( keepFlag==0 && bForce==0 && file_directory_size(".", 0, 1)>0 ){
3230 fossil_fatal("directory %s is not empty\n"
3231 "use the --force option to override", file_getcwd(0,0));
3232 }
3233
3234 if( db_open_local_v2(0, allowNested) ){
3235 fossil_fatal("there is already an open tree at %s", g.zLocalRoot);
3236 }
@@ -3280,25 +3538,10 @@
3280 }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
3281 g.zOpenRevision = db_get("main-branch", 0);
3282 }
3283 }
3284
3285 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3286 if( g.zOpenRevision ){
3287 /* Since the repository is open and we know the revision now,
3288 ** refresh the allow-symlinks flag. Since neither the local
3289 ** checkout nor the configuration database are open at this
3290 ** point, this should always return the versioned setting,
3291 ** if any, or the default value, which is negative one. The
3292 ** value negative one, in this context, means that the code
3293 ** below should fallback to using the setting value from the
3294 ** repository or global configuration databases only. */
3295 allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
3296 }else{
3297 allowSymlinks = -1; /* Use non-versioned settings only. */
3298 }
3299 #endif
3300
3301 #if defined(_WIN32) || defined(__CYGWIN__)
3302 # define LOCALDB_NAME "./_FOSSIL_"
3303 #else
3304 # define LOCALDB_NAME "./.fslckout"
@@ -3308,28 +3551,10 @@
3308 "COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
3309 #endif
3310 (char*)0);
3311 db_delete_on_failure(LOCALDB_NAME);
3312 db_open_local(0);
3313 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
3314 if( allowSymlinks>=0 ){
3315 /* Use the value from the versioned setting, which was read
3316 ** prior to opening the local checkout (i.e. which is most
3317 ** likely empty and does not actually contain any versioned
3318 ** setting files yet). Normally, this value would be given
3319 ** first priority within db_get_boolean(); however, this is
3320 ** a special case because we know the on-disk files may not
3321 ** exist yet. */
3322 g.allowSymlinks = allowSymlinks;
3323 }else{
3324 /* Since the local checkout may not have any files at this
3325 ** point, this will probably be the setting value from the
3326 ** repository or global configuration databases. */
3327 g.allowSymlinks = db_get_boolean("allow-symlinks",
3328 db_allow_symlinks_by_default());
3329 }
3330 #endif /* FOSSIL_LEGACY_ALLOW_SYMLINKS */
3331 db_lset("repository", zRepo);
3332 db_record_repository_filename(zRepo);
3333 db_set_checkout(0);
3334 azNewArgv[0] = g.argv[0];
3335 g.argv = azNewArgv;
@@ -3418,12 +3643,13 @@
3418 const char *name; /* Name of the setting */
3419 const char *var; /* Internal variable name used by db_set() */
3420 int width; /* Width of display. 0 for boolean values and
3421 ** negative for values which should not appear
3422 ** on the /setup_settings page. */
3423 int versionable; /* Is this setting versionable? */
3424 int forceTextArea; /* Force using a text area for display? */
 
3425 const char *def; /* Default value */
3426 };
3427 #endif /* INTERFACE */
3428
3429 /*
@@ -3437,50 +3663,29 @@
3437 ** SETTING: admin-log boolean default=off
3438 **
3439 ** When the admin-log setting is enabled, configuration changes are recorded
3440 ** in the "admin_log" table of the repository.
3441 */
3442 #if !defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3443 /*
3444 ** SETTING: allow-symlinks boolean default=off
3445 **
3446 ** When allow-symlinks is OFF (which is the default and recommended setting)
3447 ** symbolic links are treated like text files that contain a single line of
3448 ** content which is the name of their target. If allow-symlinks is ON,
3449 ** the symbolic links are actually followed.
3450 **
3451 ** The use of symbolic links is dangerous. If you checkout a maliciously
3452 ** crafted checkin that contains symbolic links, it is possible that files
3453 ** outside of the working directory might be overwritten.
3454 **
3455 ** Keep this setting OFF unless you have a very good reason to turn it
3456 ** on and you implicitly trust the integrity of the repositories you
3457 ** open.
3458 */
3459 #endif
3460 #if defined(_WIN32) && defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3461 /*
3462 ** SETTING: allow-symlinks boolean default=off versionable
3463 **
3464 ** When allow-symlinks is OFF, symbolic links in the repository are followed
3465 ** and treated no differently from real files. When allow-symlinks is ON,
3466 ** the object to which the symbolic link points is ignored, and the content
3467 ** of the symbolic link that is stored in the repository is the name of the
3468 ** object to which the symbolic link points.
3469 */
3470 #endif
3471 #if !defined(_WIN32) && defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
3472 /*
3473 ** SETTING: allow-symlinks boolean default=on versionable
3474 **
3475 ** When allow-symlinks is OFF, symbolic links in the repository are followed
3476 ** and treated no differently from real files. When allow-symlinks is ON,
3477 ** the object to which the symbolic link points is ignored, and the content
3478 ** of the symbolic link that is stored in the repository is the name of the
3479 ** object to which the symbolic link points.
3480 */
3481 #endif
3482 /*
3483 ** SETTING: auto-captcha boolean default=on variable=autocaptcha
3484 ** If enabled, the /login page provides a button that will automatically
3485 ** fill in the captcha password. This makes things easier for human users,
3486 ** at the expense of also making logins easier for malicious robots.
@@ -3530,11 +3735,11 @@
3530 ** there is no cron job periodically running "fossil backoffice",
3531 ** email notifications and other work normally done by the
3532 ** backoffice will not occur.
3533 */
3534 /*
3535 ** SETTING: backoffice-logfile width=40
3536 ** If backoffice-logfile is not an empty string and is a valid
3537 ** filename, then a one-line message is appended to that file
3538 ** every time the backoffice runs. This can be used for debugging,
3539 ** to ensure that backoffice is running appropriately.
3540 */
@@ -3607,11 +3812,11 @@
3607 /*
3608 ** SETTING: crnl-glob width=40 versionable block-text
3609 ** This is an alias for the crlf-glob setting.
3610 */
3611 /*
3612 ** SETTING: default-perms width=16 default=u
3613 ** Permissions given automatically to new users. For more
3614 ** information on permissions see the Users page in Server
3615 ** Administration of the HTTP UI.
3616 */
3617 /*
@@ -3619,11 +3824,11 @@
3619 ** If enabled, permit files that may be binary
3620 ** or that match the "binary-glob" setting to be used with
3621 ** external diff programs. If disabled, skip these files.
3622 */
3623 /*
3624 ** SETTING: diff-command width=40
3625 ** The value is an external command to run when performing a diff.
3626 ** If undefined, the internal text diff will be used.
3627 */
3628 /*
3629 ** SETTING: dont-push boolean default=off
@@ -3634,11 +3839,11 @@
3634 /*
3635 ** SETTING: dotfiles boolean versionable default=off
3636 ** If enabled, include --dotfiles option for all compatible commands.
3637 */
3638 /*
3639 ** SETTING: editor width=32
3640 ** The value is an external command that will launch the
3641 ** text editor command used for check-in comments.
3642 */
3643 /*
3644 ** SETTING: empty-dirs width=40 versionable block-text
@@ -3677,16 +3882,16 @@
3677 ** An empty list prohibits editing via that page. Note that
3678 ** it cannot edit binary files, so the list should not
3679 ** contain any globs for, e.g., images or PDFs.
3680 */
3681 /*
3682 ** SETTING: gdiff-command width=40 default=gdiff
3683 ** The value is an external command to run when performing a graphical
3684 ** diff. If undefined, text diff will be used.
3685 */
3686 /*
3687 ** SETTING: gmerge-command width=40
3688 ** The value is a graphical merge conflict resolver command operating
3689 ** on four files. Examples:
3690 **
3691 ** kdiff3 "%baseline" "%original" "%merge" -o "%output"
3692 ** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3817,11 +4022,11 @@
3817 ** the associated files within the checkout -AND- the "rm"
3818 ** and "delete" commands will also remove the associated
3819 ** files from within the checkout.
3820 */
3821 /*
3822 ** SETTING: pgp-command width=40
3823 ** Command used to clear-sign manifests at check-in.
3824 ** Default value is "gpg --clearsign -o"
3825 */
3826 /*
3827 ** SETTING: forbid-delta-manifests boolean default=off
@@ -3877,22 +4082,22 @@
3877 **
3878 ** If repolist-skin has a value of 2, then the repository is omitted from
3879 ** the list in use cases 1 through 4, but not for 5 and 6.
3880 */
3881 /*
3882 ** SETTING: self-register boolean default=off
3883 ** Allow users to register themselves through the HTTP UI.
3884 ** This is useful if you want to see other names than
3885 ** "Anonymous" in e.g. ticketing system. On the other hand
3886 ** users can not be deleted.
3887 */
3888 /*
3889 ** SETTING: ssh-command width=40
3890 ** The command used to talk to a remote machine with the "ssh://" protocol.
3891 */
3892 /*
3893 ** SETTING: ssl-ca-location width=40
3894 ** The full pathname to a file containing PEM encoded
3895 ** CA root certificates, or a directory of certificates
3896 ** with filenames formed from the certificate hashes as
3897 ** required by OpenSSL.
3898 **
@@ -3902,11 +4107,11 @@
3902 ** Checking your platform behaviour is required if the
3903 ** exact contents of the CA root is critical for your
3904 ** application.
3905 */
3906 /*
3907 ** SETTING: ssl-identity width=40
3908 ** The full pathname to a file containing a certificate
3909 ** and private key in PEM format. Create by concatenating
3910 ** the certificate and private key files.
3911 **
3912 ** This identity will be presented to SSL servers to
@@ -3913,33 +4118,33 @@
3913 ** authenticate this client, in addition to the normal
3914 ** password authentication.
3915 */
3916 #ifdef FOSSIL_ENABLE_TCL
3917 /*
3918 ** SETTING: tcl boolean default=off
3919 ** If enabled Tcl integration commands will be added to the TH1
3920 ** interpreter, allowing arbitrary Tcl expressions and
3921 ** scripts to be evaluated from TH1. Additionally, the Tcl
3922 ** interpreter will be able to evaluate arbitrary TH1
3923 ** expressions and scripts.
3924 */
3925 /*
3926 ** SETTING: tcl-setup width=40 block-text
3927 ** This is the setup script to be evaluated after creating
3928 ** and initializing the Tcl interpreter. By default, this
3929 ** is empty and no extra setup is performed.
3930 */
3931 #endif /* FOSSIL_ENABLE_TCL */
3932 /*
3933 ** SETTING: tclsh width=80 default=tclsh
3934 ** Name of the external TCL interpreter used for such things
3935 ** as running the GUI diff viewer launched by the --tk option
3936 ** of the various "diff" commands.
3937 */
3938 #ifdef FOSSIL_ENABLE_TH1_DOCS
3939 /*
3940 ** SETTING: th1-docs boolean default=off
3941 ** If enabled, this allows embedded documentation files to contain
3942 ** arbitrary TH1 scripts that are evaluated on the server. If native
3943 ** Tcl integration is also enabled, this setting has the
3944 ** potential to allow anybody with check-in privileges to
3945 ** do almost anything that the associated operating system
@@ -3992,11 +4197,11 @@
3992 ** of a "fossil clone" or "fossil sync" command. The
3993 ** default is false, in which case the -u option is
3994 ** needed to clone or sync unversioned files.
3995 */
3996 /*
3997 ** SETTING: web-browser width=30
3998 ** A shell command used to launch your preferred
3999 ** web browser when given a URL as an argument.
4000 ** Defaults to "start" on windows, "open" on Mac,
4001 ** and "firefox" on Unix.
4002 */
@@ -4118,11 +4323,13 @@
4118 fossil_fatal("cannot set 'manifest' globally");
4119 }
4120 if( unsetFlag ){
4121 db_unset(pSetting->name, globalFlag);
4122 }else{
 
4123 db_set(pSetting->name, g.argv[3], globalFlag);
 
4124 }
4125 if( isManifest && g.localOpen ){
4126 manifest_to_disk(db_lget_int("checkout", 0));
4127 }
4128 }else{
4129
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
69 #endif /* INTERFACE */
70 const struct Stmt empty_Stmt = empty_Stmt_m;
71
72 /*
73 ** Call this routine when a database error occurs.
74 ** This routine throws a fatal error. It does not return.
75 */
76 static void db_err(const char *zFormat, ...){
77 va_list ap;
78 char *z;
79 va_start(ap, zFormat);
@@ -113,10 +114,11 @@
114 /*
115 ** All static variable that a used by only this file are gathered into
116 ** the following structure.
117 */
118 static struct DbLocalData {
119 unsigned protectMask; /* Prevent changes to database */
120 int nBegin; /* Nesting depth of BEGIN */
121 int doRollback; /* True to force a rollback */
122 int nCommitHook; /* Number of commit hooks */
123 int wrTxn; /* Outer-most TNX is a write */
124 Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -133,11 +135,16 @@
135 const char *zStartFile; /* File in which transaction was started */
136 int iStartLine; /* Line of zStartFile where transaction started */
137 int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
138 void *pAuthArg; /* Argument to the authorizer */
139 const char *zAuthName; /* Name of the authorizer */
140 int bProtectTriggers; /* True if protection triggers already exist */
141 int nProtect; /* Slots of aProtect used */
142 unsigned aProtect[10]; /* Saved values of protectMask */
143 } db = {
144 PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
145 0, 0, 0, 0, 0, 0, };
146
147 /*
148 ** Arrange for the given file to be deleted on a failure.
149 */
150 void db_delete_on_failure(const char *zFilename){
@@ -241,17 +248,19 @@
248 db.nBegin--;
249 if( db.nBegin==0 ){
250 int i;
251 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
252 i = 0;
253 db_protect_only(PROTECT_SENSITIVE);
254 while( db.nBeforeCommit ){
255 db.nBeforeCommit--;
256 sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
257 sqlite3_free(db.azBeforeCommit[i]);
258 i++;
259 }
260 leaf_do_pending_checks();
261 db_protect_pop();
262 }
263 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
264 int rc = db.aHook[i].xHook();
265 if( rc ){
266 db.doRollback = 1;
@@ -319,10 +328,205 @@
328 }
329 db.aHook[db.nCommitHook].sequence = sequence;
330 db.aHook[db.nCommitHook].xHook = x;
331 db.nCommitHook++;
332 }
333
334 #if INTERFACE
335 /*
336 ** Flag bits for db_protect() and db_unprotect() indicating which parts
337 ** of the databases should be write protected or write enabled, respectively.
338 */
339 #define PROTECT_USER 0x01 /* USER table */
340 #define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */
341 #define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */
342 #define PROTECT_READONLY 0x08 /* everything except TEMP tables */
343 #define PROTECT_BASELINE 0x10 /* protection system is working */
344 #define PROTECT_ALL 0x1f /* All of the above */
345 #define PROTECT_NONE 0x00 /* Nothing. Everything is open */
346 #endif /* INTERFACE */
347
348 /*
349 ** Enable or disable database write protections.
350 **
351 ** db_protext(X) Add protects on X
352 ** db_unprotect(X) Remove protections on X
353 ** db_protect_only(X) Remove all prior protections then set
354 ** protections to only X.
355 **
356 ** Each of these routines pushes the previous protection mask onto
357 ** a finite-size stack. Each should be followed by a call to
358 ** db_protect_pop() to pop the stack and restore the protections that
359 ** existed prior to the call. The protection mask stack has a limited
360 ** depth, so take care not to next calls too deeply.
361 **
362 ** About Database Write Protection
363 ** -------------------------------
364 **
365 ** This is *not* a primary means of defending the application from
366 ** attack. Fossil should be secure even if this mechanism is disabled.
367 ** The purpose of database write protection is to provide an additional
368 ** layer of defense in case SQL injection bugs somehow slip into other
369 ** parts of the system. In other words, database write protection is
370 ** not primary defense but rather defense in depth.
371 **
372 ** This mechanism mostly focuses on the USER table, to prevent an
373 ** attacker from giving themselves Admin privilegs, and on the
374 ** CONFIG table and specially "sensitive" settings such as
375 ** "diff-command" or "editor" that if compromised by an attacker
376 ** could lead to an RCE.
377 **
378 ** By default, the USER and CONFIG tables are read-only. Various
379 ** subsystems that legitimately need to change those tables can
380 ** temporarily do so using:
381 **
382 ** db_unprotect(PROTECT_xxx);
383 ** // make the legitmate changes here
384 ** db_protect_pop();
385 **
386 ** Code that runs inside of reduced protections should be carefully
387 ** reviewed to ensure that it is harmless and not subject to SQL
388 ** injection.
389 **
390 ** Read-only operations (such as many web pages like /timeline)
391 ** can invoke db_protect(PROTECT_ALL) to effectively make the database
392 ** read-only. TEMP tables (which are often used for these kinds of
393 ** pages) are still writable, however.
394 **
395 ** The PROTECT_SENSITIVE protection is a subset of PROTECT_CONFIG
396 ** that blocks changes to all of the global_config table, but only
397 ** "sensitive" settings in the config table. PROTECT_SENSITIVE
398 ** relies on triggers and the protected_setting() SQL function to
399 ** prevent changes to sensitive settings.
400 **
401 ** Additional Notes
402 ** ----------------
403 **
404 ** Calls to routines like db_set() and db_unset() temporarily disable
405 ** the PROTECT_CONFIG protection. The assumption is that these calls
406 ** cannot be invoked by an SQL injection and are thus safe. Make sure
407 ** this is the case by always using a string literal as the name argument
408 ** to db_set() and db_unset() and friend, not a variable that might
409 ** be compromised by an attack.
410 */
411 void db_protect_only(unsigned flags){
412 if( db.nProtect>=count(db.aProtect)-2 ){
413 fossil_panic("too many db_protect() calls");
414 }
415 db.aProtect[db.nProtect++] = db.protectMask;
416 if( (flags & PROTECT_SENSITIVE)!=0
417 && db.bProtectTriggers==0
418 && g.repositoryOpen
419 ){
420 /* Create the triggers needed to protect sensitive settings from
421 ** being created or modified the first time that PROTECT_SENSITIVE
422 ** is enabled. Deleting a sensitive setting is harmless, so there
423 ** is not trigger to block deletes. After being created once, the
424 ** triggers persist for the life of the database connection. */
425 db_multi_exec(
426 "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
427 " WHEN protected_setting(new.name) BEGIN"
428 " SELECT raise(abort,'not authorized');"
429 "END;\n"
430 "CREATE TEMP TRIGGER protect_2 BEFORE UPDATE ON config"
431 " WHEN protected_setting(new.name) BEGIN"
432 " SELECT raise(abort,'not authorized');"
433 "END;\n"
434 );
435 db.bProtectTriggers = 1;
436 }
437 db.protectMask = flags;
438 }
439 void db_protect(unsigned flags){
440 db_protect_only(db.protectMask | flags);
441 }
442 void db_unprotect(unsigned flags){
443 if( db.nProtect>=count(db.aProtect)-2 ){
444 fossil_panic("too many db_unprotect() calls");
445 }
446 db.aProtect[db.nProtect++] = db.protectMask;
447 db.protectMask &= ~flags;
448 }
449 void db_protect_pop(void){
450 if( db.nProtect<1 ){
451 fossil_panic("too many db_protect_pop() calls");
452 }
453 db.protectMask = db.aProtect[--db.nProtect];
454 }
455
456 /*
457 ** Verify that the desired database write pertections are in place.
458 ** Throw a fatal error if not.
459 */
460 void db_assert_protected(unsigned flags){
461 if( (flags & db.protectMask)!=flags ){
462 fossil_panic("missing database write protection bits: %02x",
463 flags & ~db.protectMask);
464 }
465 }
466
467 /*
468 ** Assert that either all protections are off (including PROTECT_BASELINE
469 ** which is usually always enabled), or the setting named in the argument
470 ** is no a sensitive setting.
471 **
472 ** This assert() is used to verify that the db_set() and db_set_int()
473 ** interfaces do not modify a sensitive setting.
474 */
475 void db_assert_protection_off_or_not_sensitive(const char *zName){
476 if( db.protectMask!=0 && db_setting_is_protected(zName) ){
477 fossil_panic("unauthorized change to protected setting \"%s\"", zName);
478 }
479 }
480
481 /*
482 ** Every Fossil database connection automatically registers the following
483 ** overarching authenticator callback, and leaves it registered for the
484 ** duration of the connection. This authenticator will call any
485 ** sub-authenticators that are registered using db_set_authorizer().
486 */
487 int db_top_authorizer(
488 void *pNotUsed,
489 int eCode,
490 const char *z0,
491 const char *z1,
492 const char *z2,
493 const char *z3
494 ){
495 int rc = SQLITE_OK;
496 switch( eCode ){
497 case SQLITE_INSERT:
498 case SQLITE_UPDATE:
499 case SQLITE_DELETE: {
500 if( (db.protectMask & PROTECT_USER)!=0
501 && sqlite3_stricmp(z0,"user")==0 ){
502 rc = SQLITE_DENY;
503 }else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
504 (sqlite3_stricmp(z0,"config")==0 ||
505 sqlite3_stricmp(z0,"global_config")==0) ){
506 rc = SQLITE_DENY;
507 }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 &&
508 sqlite3_stricmp(z0,"global_config")==0 ){
509 rc = SQLITE_DENY;
510 }else if( (db.protectMask & PROTECT_READONLY)!=0
511 && sqlite3_stricmp(z2,"temp")!=0 ){
512 rc = SQLITE_DENY;
513 }
514 break;
515 }
516 case SQLITE_DROP_TEMP_TRIGGER: {
517 /* Do not allow the triggers that enforce PROTECT_SENSITIVE
518 ** to be dropped */
519 rc = SQLITE_DENY;
520 break;
521 }
522 }
523 if( db.xAuth && rc==SQLITE_OK ){
524 rc = db.xAuth(db.pAuthArg, eCode, z0, z1, z2, z3);
525 }
526 return rc;
527 }
528
529 /*
530 ** Set or unset the query authorizer callback function
531 */
532 void db_set_authorizer(
@@ -331,23 +535,22 @@
535 const char *zName /* for tracing */
536 ){
537 if( db.xAuth ){
538 fossil_panic("multiple active db_set_authorizer() calls");
539 }
 
540 db.xAuth = xAuth;
541 db.pAuthArg = pArg;
542 db.zAuthName = zName;
543 if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
544 }
545 void db_clear_authorizer(void){
546 if( db.zAuthName && g.fSqlTrace ){
547 fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
548 }
 
549 db.xAuth = 0;
550 db.pAuthArg = 0;
551 db.zAuthName = 0;
552 }
553
554 #if INTERFACE
555 /*
556 ** Possible flags to db_vprepare
@@ -363,21 +566,24 @@
566 */
567 int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
568 int rc;
569 int prepFlags = 0;
570 char *zSql;
571 const char *zExtra = 0;
572 blob_zero(&pStmt->sql);
573 blob_vappendf(&pStmt->sql, zFormat, ap);
574 va_end(ap);
575 zSql = blob_str(&pStmt->sql);
576 db.nPrepare++;
577 if( flags & DB_PREPARE_PERSISTENT ){
578 prepFlags = SQLITE_PREPARE_PERSISTENT;
579 }
580 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
581 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
582 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
583 }else if( zExtra && !fossil_all_whitespace(zExtra) ){
584 db_err("surplus text follows SQL: \"%s\"", zExtra);
585 }
586 pStmt->pNext = db.pAllStmt;
587 pStmt->pPrev = 0;
588 if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
589 db.pAllStmt = pStmt;
@@ -640,10 +846,11 @@
846 return rc;
847 }
848
849 /*
850 ** COMMAND: test-db-exec-error
851 ** Usage: %fossil test-db-exec-error
852 **
853 ** Invoke the db_exec() interface with an erroneous SQL statement
854 ** in order to verify the error handling logic.
855 */
856 void db_test_db_exec_cmd(void){
@@ -650,10 +857,27 @@
857 Stmt err;
858 db_find_and_open_repository(0,0);
859 db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
860 db_exec(&err);
861 }
862
863 /*
864 ** COMMAND: test-db-prepare
865 ** Usage: %fossil test-db-prepare ?OPTIONS? SQL
866 **
867 ** Invoke db_prepare() on the SQL input. Report any errors encountered.
868 ** This command is used to verify error detection logic in the db_prepare()
869 ** utility routine.
870 */
871 void db_test_db_prepare(void){
872 Stmt err;
873 db_find_and_open_repository(0,0);
874 verify_all_options();
875 if( g.argc!=3 ) usage("?OPTIONS? SQL");
876 db_prepare(&err, "%s", g.argv[2]/*safe-for-%s*/);
877 db_finalize(&err);
878 }
879
880 /*
881 ** Print the output of one or more SQL queries on standard output.
882 ** This routine is used for debugging purposes only.
883 */
@@ -880,13 +1104,10 @@
1104 const char *zSql;
1105 va_list ap;
1106
1107 xdb = db_open(zFileName ? zFileName : ":memory:");
1108 sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
 
 
 
1109 rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
1110 if( rc!=SQLITE_OK ){
1111 db_err("%s", sqlite3_errmsg(xdb));
1112 }
1113 va_start(ap, zSchema);
@@ -1092,10 +1313,37 @@
1313 }
1314 strcpy(zOut, zTemp = obscure((char*)zIn));
1315 fossil_free(zTemp);
1316 sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
1317 }
1318
1319 /*
1320 ** Return True if zName is a protected (a.k.a. "sensitive") setting.
1321 */
1322 int db_setting_is_protected(const char *zName){
1323 const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
1324 return pSetting!=0 && pSetting->sensitive!=0;
1325 }
1326
1327 /*
1328 ** Implement the protected_setting(X) SQL function. This function returns
1329 ** true if X is the name of a protected (security-sensitive) setting and
1330 ** the db.protectSensitive flag is enabled. It returns false otherwise.
1331 */
1332 LOCAL void db_protected_setting_func(
1333 sqlite3_context *context,
1334 int argc,
1335 sqlite3_value **argv
1336 ){
1337 const char *zSetting;
1338 if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
1339 sqlite3_result_int(context, 0);
1340 return;
1341 }
1342 zSetting = (const char*)sqlite3_value_text(argv[0]);
1343 sqlite3_result_int(context, db_setting_is_protected(zSetting));
1344 }
1345
1346 /*
1347 ** Register the SQL functions that are useful both to the internal
1348 ** representation and to the "fossil sql" command.
1349 */
@@ -1122,10 +1370,12 @@
1370 alert_find_emailaddr_func, 0, 0);
1371 sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1372 alert_display_name_func, 0, 0);
1373 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1374 db_obscure, 0, 0);
1375 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1376 db_protected_setting_func, 0, 0);
1377 }
1378
1379 #if USE_SEE
1380 /*
1381 ** This is a pointer to the saved database encryption key string.
@@ -1380,10 +1630,11 @@
1630 if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
1631 db_add_aux_functions(db);
1632 re_add_sql_func(db); /* The REGEXP operator */
1633 foci_register(db); /* The "files_of_checkin" virtual table */
1634 sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
1635 sqlite3_set_authorizer(db, db_top_authorizer, db);
1636 return db;
1637 }
1638
1639
1640 /*
@@ -1815,27 +2066,18 @@
2066 zRepo = db_lget("repository", 0);
2067 if( zRepo && !file_is_absolute_path(zRepo) ){
2068 char * zFree = zRepo;
2069 zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
2070 fossil_free(zFree);
2071 zFree = zRepo;
2072 zRepo = file_canonical_name_dup(zFree);
2073 fossil_free(zFree);
2074 }
2075 }
2076 return zRepo;
2077 }
2078
 
 
 
 
 
 
 
 
 
 
 
 
2079 /*
2080 ** Returns non-zero if support for symlinks is currently enabled.
2081 */
2082 int db_allow_symlinks(void){
2083 return g.allowSymlinks;
@@ -1877,13 +2119,14 @@
2119 g.zRepositoryName = mprintf("%s", zDbName);
2120 db_open_or_attach(g.zRepositoryName, "repository");
2121 g.repositoryOpen = 1;
2122 sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
2123 &g.iRepoDataVers);
2124
2125 /* Cache "allow-symlinks" option, because we'll need it on every stat call */
2126 g.allowSymlinks = db_get_boolean("allow-symlinks",0);
2127
2128 g.zAuxSchema = db_get("aux-schema","");
2129 g.eHashPolicy = db_get_int("hash-policy",-1);
2130 if( g.eHashPolicy<0 ){
2131 g.eHashPolicy = hname_default_policy();
2132 db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2169,11 +2412,13 @@
2412 */
2413 if( db_database_slot("localdb")>=0 ){
2414 int nFree = db_int(0, "PRAGMA localdb.freelist_count");
2415 int nTotal = db_int(0, "PRAGMA localdb.page_count");
2416 if( nFree>nTotal/4 ){
2417 db_unprotect(PROTECT_ALL);
2418 db_multi_exec("VACUUM localdb;");
2419 db_protect_pop();
2420 }
2421 }
2422
2423 if( g.db ){
2424 int rc;
@@ -2187,10 +2432,11 @@
2432 }
2433 g.db = 0;
2434 }
2435 g.repositoryOpen = 0;
2436 g.localOpen = 0;
2437 db.bProtectTriggers = 0;
2438 assert( g.dbConfig==0 );
2439 assert( g.zConfigDbName==0 );
2440 backoffice_run_if_needed();
2441 }
2442
@@ -2249,10 +2495,11 @@
2495 zUser = fossil_getenv("USERNAME");
2496 }
2497 if( zUser==0 ){
2498 zUser = "root";
2499 }
2500 db_unprotect(PROTECT_USER);
2501 db_multi_exec(
2502 "INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
2503 );
2504 db_multi_exec(
2505 "UPDATE user SET cap='s', pw=%Q"
@@ -2268,10 +2515,11 @@
2515 " VALUES('developer','','ei','Dev');"
2516 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2517 " VALUES('reader','','kptw','Reader');"
2518 );
2519 }
2520 db_protect_pop();
2521 }
2522
2523 /*
2524 ** Return a pointer to a string that contains the RHS of an IN operator
2525 ** that will select CONFIG table names that are in the list of control
@@ -2319,10 +2567,11 @@
2567 ){
2568 char *zDate;
2569 Blob hash;
2570 Blob manifest;
2571
2572 db_unprotect(PROTECT_ALL);
2573 db_set("content-schema", CONTENT_SCHEMA, 0);
2574 db_set("aux-schema", AUX_SCHEMA_MAX, 0);
2575 db_set("rebuilt", get_version(), 0);
2576 db_set("admin-log", "1", 0);
2577 db_set("access-log", "1", 0);
@@ -2377,10 +2626,11 @@
2626 " photo = (SELECT u2.photo FROM settingSrc.user u2"
2627 " WHERE u2.login = user.login)"
2628 " WHERE user.login IN ('anonymous','nobody','developer','reader');"
2629 );
2630 }
2631 db_protect_pop();
2632
2633 if( zInitialDate ){
2634 int rid;
2635 blob_zero(&manifest);
2636 blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2873,10 +3123,12 @@
3123 z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
3124 }
3125 return z;
3126 }
3127 void db_set(const char *zName, const char *zValue, int globalFlag){
3128 db_assert_protection_off_or_not_sensitive(zName);
3129 db_unprotect(PROTECT_CONFIG);
3130 db_begin_transaction();
3131 if( globalFlag ){
3132 db_swap_connections();
3133 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
3134 zName, zValue);
@@ -2887,13 +3139,15 @@
3139 }
3140 if( globalFlag && g.repositoryOpen ){
3141 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3142 }
3143 db_end_transaction(0);
3144 db_protect_pop();
3145 }
3146 void db_unset(const char *zName, int globalFlag){
3147 db_begin_transaction();
3148 db_unprotect(PROTECT_CONFIG);
3149 if( globalFlag ){
3150 db_swap_connections();
3151 db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
3152 db_swap_connections();
3153 }else{
@@ -2900,10 +3154,11 @@
3154 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3155 }
3156 if( globalFlag && g.repositoryOpen ){
3157 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3158 }
3159 db_protect_pop();
3160 db_end_transaction(0);
3161 }
3162 int db_is_global(const char *zName){
3163 int rc = 0;
3164 if( g.zConfigDbName ){
@@ -2933,10 +3188,12 @@
3188 db_swap_connections();
3189 }
3190 return v;
3191 }
3192 void db_set_int(const char *zName, int value, int globalFlag){
3193 db_assert_protection_off_or_not_sensitive(zName);
3194 db_unprotect(PROTECT_CONFIG);
3195 if( globalFlag ){
3196 db_swap_connections();
3197 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
3198 zName, value);
3199 db_swap_connections();
@@ -2945,10 +3202,11 @@
3202 zName, value);
3203 }
3204 if( globalFlag && g.repositoryOpen ){
3205 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3206 }
3207 db_protect_pop();
3208 }
3209 int db_get_boolean(const char *zName, int dflt){
3210 char *zVal = db_get(zName, dflt ? "on" : "off");
3211 if( is_truth(zVal) ){
3212 dflt = 1;
@@ -2956,19 +3214,17 @@
3214 dflt = 0;
3215 }
3216 fossil_free(zVal);
3217 return dflt;
3218 }
 
3219 int db_get_versioned_boolean(const char *zName, int dflt){
3220 char *zVal = db_get_versioned(zName, 0);
3221 if( zVal==0 ) return dflt;
3222 if( is_truth(zVal) ) return 1;
3223 if( is_false(zVal) ) return 0;
3224 return dflt;
3225 }
 
3226 char *db_lget(const char *zName, const char *zDefault){
3227 return db_text(zDefault,
3228 "SELECT value FROM vvar WHERE name=%Q", zName);
3229 }
3230 void db_lset(const char *zName, const char *zValue){
@@ -3076,24 +3332,28 @@
3332 }
3333 file_canonical_name(zName, &full, 0);
3334 (void)filename_collation(); /* Initialize before connection swap */
3335 db_swap_connections();
3336 zRepoSetting = mprintf("repo:%q", blob_str(&full));
3337
3338 db_unprotect(PROTECT_CONFIG);
3339 db_multi_exec(
3340 "DELETE FROM global_config WHERE name %s = %Q;",
3341 filename_collation(), zRepoSetting
3342 );
3343 db_multi_exec(
3344 "INSERT OR IGNORE INTO global_config(name,value)"
3345 "VALUES(%Q,1);",
3346 zRepoSetting
3347 );
3348 db_protect_pop();
3349 fossil_free(zRepoSetting);
3350 if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
3351 Blob localRoot;
3352 file_canonical_name(g.zLocalRoot, &localRoot, 1);
3353 zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
3354 db_unprotect(PROTECT_CONFIG);
3355 db_multi_exec(
3356 "DELETE FROM global_config WHERE name %s = %Q;",
3357 filename_collation(), zCkoutSetting
3358 );
3359 db_multi_exec(
@@ -3109,10 +3369,11 @@
3369 db_optional_sql("repository",
3370 "REPLACE INTO config(name,value,mtime)"
3371 "VALUES(%Q,1,now());",
3372 zCkoutSetting
3373 );
3374 db_protect_pop();
3375 fossil_free(zCkoutSetting);
3376 blob_reset(&localRoot);
3377 }else{
3378 db_swap_connections();
3379 }
@@ -3147,11 +3408,11 @@
3408 **
3409 ** Options:
3410 ** --empty Initialize checkout as being empty, but still connected
3411 ** with the local repository. If you commit this checkout,
3412 ** it will become a new "initial" commit in the repository.
3413 ** -f|--force Continue with the open even if the working directory is
3414 ** not empty.
3415 ** --force-missing Force opening a repository with missing content
3416 ** --keep Only modify the manifest and manifest.uuid files
3417 ** --nested Allow opening a repository inside an opened checkout
3418 ** --repodir DIR If REPOSITORY is a URI that will be cloned, store
@@ -3167,13 +3428,10 @@
3428 void cmd_open(void){
3429 int emptyFlag;
3430 int keepFlag;
3431 int forceMissingFlag;
3432 int allowNested;
 
 
 
3433 int setmtimeFlag; /* --setmtime. Set mtimes on files */
3434 int bForce = 0; /* --force. Open even if non-empty dir */
3435 static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
3436 const char *zWorkDir; /* --workdir value */
3437 const char *zRepo = 0; /* Name of the repository file */
@@ -3187,11 +3445,11 @@
3445 forceMissingFlag = find_option("force-missing",0,0)!=0;
3446 allowNested = find_option("nested",0,0)!=0;
3447 setmtimeFlag = find_option("setmtime",0,0)!=0;
3448 zWorkDir = find_option("workdir",0,1);
3449 zRepoDir = find_option("repodir",0,1);
3450 bForce = find_option("force","f",0)!=0;
3451 zPwd = file_getcwd(0,0);
3452
3453
3454 /* We should be done with options.. */
3455 verify_all_options();
@@ -3226,11 +3484,11 @@
3484 fossil_fatal("unable to make %s the working directory", zWorkDir);
3485 }
3486 }
3487 if( keepFlag==0 && bForce==0 && file_directory_size(".", 0, 1)>0 ){
3488 fossil_fatal("directory %s is not empty\n"
3489 "use the -f or --force option to override", file_getcwd(0,0));
3490 }
3491
3492 if( db_open_local_v2(0, allowNested) ){
3493 fossil_fatal("there is already an open tree at %s", g.zLocalRoot);
3494 }
@@ -3280,25 +3538,10 @@
3538 }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
3539 g.zOpenRevision = db_get("main-branch", 0);
3540 }
3541 }
3542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3543
3544 #if defined(_WIN32) || defined(__CYGWIN__)
3545 # define LOCALDB_NAME "./_FOSSIL_"
3546 #else
3547 # define LOCALDB_NAME "./.fslckout"
@@ -3308,28 +3551,10 @@
3551 "COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
3552 #endif
3553 (char*)0);
3554 db_delete_on_failure(LOCALDB_NAME);
3555 db_open_local(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3556 db_lset("repository", zRepo);
3557 db_record_repository_filename(zRepo);
3558 db_set_checkout(0);
3559 azNewArgv[0] = g.argv[0];
3560 g.argv = azNewArgv;
@@ -3418,12 +3643,13 @@
3643 const char *name; /* Name of the setting */
3644 const char *var; /* Internal variable name used by db_set() */
3645 int width; /* Width of display. 0 for boolean values and
3646 ** negative for values which should not appear
3647 ** on the /setup_settings page. */
3648 char versionable; /* Is this setting versionable? */
3649 char forceTextArea; /* Force using a text area for display? */
3650 char sensitive; /* True if this a security-sensitive setting */
3651 const char *def; /* Default value */
3652 };
3653 #endif /* INTERFACE */
3654
3655 /*
@@ -3437,50 +3663,29 @@
3663 ** SETTING: admin-log boolean default=off
3664 **
3665 ** When the admin-log setting is enabled, configuration changes are recorded
3666 ** in the "admin_log" table of the repository.
3667 */
3668 /*
3669 ** SETTING: allow-symlinks boolean default=off sensitive
3670 **
3671 ** When allow-symlinks is OFF, Fossil does not see symbolic links
3672 ** (a.k.a "symlinks") on disk as a separate class of object. Instead Fossil
3673 ** sees the object that the symlink points to. Fossil will only manage files
3674 ** and directories, not symlinks. When a symlink is added to a repository,
3675 ** the object that the symlink points to is added, not the symlink itself.
3676 **
3677 ** When allow-symlinks is ON, Fossil sees symlinks on disk as a separate
3678 ** object class that is distinct from files and directories. When a symlink
3679 ** is added to a repository, Fossil stores the target filename. In other
3680 ** words, Fossil stores the symlink itself, not the object that the symlink
3681 ** points to.
3682 **
3683 ** Symlinks are not cross-platform. They are not available on all
3684 ** operating systems and file systems. Hence the allow-symlinks setting is
3685 ** OFF by default, for portability.
3686 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3687 /*
3688 ** SETTING: auto-captcha boolean default=on variable=autocaptcha
3689 ** If enabled, the /login page provides a button that will automatically
3690 ** fill in the captcha password. This makes things easier for human users,
3691 ** at the expense of also making logins easier for malicious robots.
@@ -3530,11 +3735,11 @@
3735 ** there is no cron job periodically running "fossil backoffice",
3736 ** email notifications and other work normally done by the
3737 ** backoffice will not occur.
3738 */
3739 /*
3740 ** SETTING: backoffice-logfile width=40 sensitive
3741 ** If backoffice-logfile is not an empty string and is a valid
3742 ** filename, then a one-line message is appended to that file
3743 ** every time the backoffice runs. This can be used for debugging,
3744 ** to ensure that backoffice is running appropriately.
3745 */
@@ -3607,11 +3812,11 @@
3812 /*
3813 ** SETTING: crnl-glob width=40 versionable block-text
3814 ** This is an alias for the crlf-glob setting.
3815 */
3816 /*
3817 ** SETTING: default-perms width=16 default=u sensitive
3818 ** Permissions given automatically to new users. For more
3819 ** information on permissions see the Users page in Server
3820 ** Administration of the HTTP UI.
3821 */
3822 /*
@@ -3619,11 +3824,11 @@
3824 ** If enabled, permit files that may be binary
3825 ** or that match the "binary-glob" setting to be used with
3826 ** external diff programs. If disabled, skip these files.
3827 */
3828 /*
3829 ** SETTING: diff-command width=40 sensitive
3830 ** The value is an external command to run when performing a diff.
3831 ** If undefined, the internal text diff will be used.
3832 */
3833 /*
3834 ** SETTING: dont-push boolean default=off
@@ -3634,11 +3839,11 @@
3839 /*
3840 ** SETTING: dotfiles boolean versionable default=off
3841 ** If enabled, include --dotfiles option for all compatible commands.
3842 */
3843 /*
3844 ** SETTING: editor width=32 sensitive
3845 ** The value is an external command that will launch the
3846 ** text editor command used for check-in comments.
3847 */
3848 /*
3849 ** SETTING: empty-dirs width=40 versionable block-text
@@ -3677,16 +3882,16 @@
3882 ** An empty list prohibits editing via that page. Note that
3883 ** it cannot edit binary files, so the list should not
3884 ** contain any globs for, e.g., images or PDFs.
3885 */
3886 /*
3887 ** SETTING: gdiff-command width=40 default=gdiff sensitive
3888 ** The value is an external command to run when performing a graphical
3889 ** diff. If undefined, text diff will be used.
3890 */
3891 /*
3892 ** SETTING: gmerge-command width=40 sensitive
3893 ** The value is a graphical merge conflict resolver command operating
3894 ** on four files. Examples:
3895 **
3896 ** kdiff3 "%baseline" "%original" "%merge" -o "%output"
3897 ** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3817,11 +4022,11 @@
4022 ** the associated files within the checkout -AND- the "rm"
4023 ** and "delete" commands will also remove the associated
4024 ** files from within the checkout.
4025 */
4026 /*
4027 ** SETTING: pgp-command width=40 sensitive
4028 ** Command used to clear-sign manifests at check-in.
4029 ** Default value is "gpg --clearsign -o"
4030 */
4031 /*
4032 ** SETTING: forbid-delta-manifests boolean default=off
@@ -3877,22 +4082,22 @@
4082 **
4083 ** If repolist-skin has a value of 2, then the repository is omitted from
4084 ** the list in use cases 1 through 4, but not for 5 and 6.
4085 */
4086 /*
4087 ** SETTING: self-register boolean default=off sensitive
4088 ** Allow users to register themselves through the HTTP UI.
4089 ** This is useful if you want to see other names than
4090 ** "Anonymous" in e.g. ticketing system. On the other hand
4091 ** users can not be deleted.
4092 */
4093 /*
4094 ** SETTING: ssh-command width=40 sensitive
4095 ** The command used to talk to a remote machine with the "ssh://" protocol.
4096 */
4097 /*
4098 ** SETTING: ssl-ca-location width=40 sensitive
4099 ** The full pathname to a file containing PEM encoded
4100 ** CA root certificates, or a directory of certificates
4101 ** with filenames formed from the certificate hashes as
4102 ** required by OpenSSL.
4103 **
@@ -3902,11 +4107,11 @@
4107 ** Checking your platform behaviour is required if the
4108 ** exact contents of the CA root is critical for your
4109 ** application.
4110 */
4111 /*
4112 ** SETTING: ssl-identity width=40 sensitive
4113 ** The full pathname to a file containing a certificate
4114 ** and private key in PEM format. Create by concatenating
4115 ** the certificate and private key files.
4116 **
4117 ** This identity will be presented to SSL servers to
@@ -3913,33 +4118,33 @@
4118 ** authenticate this client, in addition to the normal
4119 ** password authentication.
4120 */
4121 #ifdef FOSSIL_ENABLE_TCL
4122 /*
4123 ** SETTING: tcl boolean default=off sensitive
4124 ** If enabled Tcl integration commands will be added to the TH1
4125 ** interpreter, allowing arbitrary Tcl expressions and
4126 ** scripts to be evaluated from TH1. Additionally, the Tcl
4127 ** interpreter will be able to evaluate arbitrary TH1
4128 ** expressions and scripts.
4129 */
4130 /*
4131 ** SETTING: tcl-setup width=40 block-text sensitive
4132 ** This is the setup script to be evaluated after creating
4133 ** and initializing the Tcl interpreter. By default, this
4134 ** is empty and no extra setup is performed.
4135 */
4136 #endif /* FOSSIL_ENABLE_TCL */
4137 /*
4138 ** SETTING: tclsh width=80 default=tclsh sensitive
4139 ** Name of the external TCL interpreter used for such things
4140 ** as running the GUI diff viewer launched by the --tk option
4141 ** of the various "diff" commands.
4142 */
4143 #ifdef FOSSIL_ENABLE_TH1_DOCS
4144 /*
4145 ** SETTING: th1-docs boolean default=off sensitive
4146 ** If enabled, this allows embedded documentation files to contain
4147 ** arbitrary TH1 scripts that are evaluated on the server. If native
4148 ** Tcl integration is also enabled, this setting has the
4149 ** potential to allow anybody with check-in privileges to
4150 ** do almost anything that the associated operating system
@@ -3992,11 +4197,11 @@
4197 ** of a "fossil clone" or "fossil sync" command. The
4198 ** default is false, in which case the -u option is
4199 ** needed to clone or sync unversioned files.
4200 */
4201 /*
4202 ** SETTING: web-browser width=30 sensitive
4203 ** A shell command used to launch your preferred
4204 ** web browser when given a URL as an argument.
4205 ** Defaults to "start" on windows, "open" on Mac,
4206 ** and "firefox" on Unix.
4207 */
@@ -4118,11 +4323,13 @@
4323 fossil_fatal("cannot set 'manifest' globally");
4324 }
4325 if( unsetFlag ){
4326 db_unset(pSetting->name, globalFlag);
4327 }else{
4328 db_protect_only(PROTECT_NONE);
4329 db_set(pSetting->name, g.argv[3], globalFlag);
4330 db_protect_pop();
4331 }
4332 if( isManifest && g.localOpen ){
4333 manifest_to_disk(db_lget_int("checkout", 0));
4334 }
4335 }else{
4336
+158 -23
--- src/default.css
+++ src/default.css
@@ -440,16 +440,10 @@
440440
content:"'";
441441
}
442442
span.usertype:after {
443443
content:"'";
444444
}
445
-div.selectedText {
446
- font-weight: bold;
447
- color: blue;
448
- background-color: #d5d5ff;
449
- border: 1px blue solid;
450
-}
451445
p.missingPriv {
452446
color: blue;
453447
}
454448
span.wikiruleHead {
455449
font-weight: bold;
@@ -948,19 +942,23 @@
948942
.error {
949943
color: darkred;
950944
background: yellow;
951945
}
952946
.warning {
953
- color: darkred;
947
+ color: black;
954948
background: yellow;
955
- opacity: 0.7;
956949
}
957950
.hidden {
958
- position: absolute;
959
- opacity: 0;
960
- pointer-events: none;
961
- display: none;
951
+ /* The framework-wide way of hiding elements is to assign them this
952
+ CSS class. To make them visible again, remove it. The !important
953
+ qualifiers are unfortunate but sometimes necessary when hidden
954
+ element has other classes which specify visibility-related
955
+ options. */
956
+ position: absolute !important;
957
+ opacity: 0 !important;
958
+ pointer-events: none !important;
959
+ display: none !important;
962960
}
963961
input {
964962
max-width: 95%;
965963
}
966964
textarea {
@@ -994,21 +992,11 @@
994992
flex-direction: column;
995993
border-width: 1px;
996994
border-style: outset;
997995
border-color: inherit;
998996
}
999
-.tab-container > .tabs > .tab-panel,
1000
-.tab-container > .tabs > fieldset.tab-wrapper {
1001
- align-self: stretch;
1002
- flex: 10 1 auto;
1003
- display: flex;
1004
- flex-direction: row;
1005
- border: 0;
1006
- padding: 0;
1007
- margin: 0;
1008
-}
1009
-.tab-container > .tabs > fieldset.tab-wrapper > .tab-panel{
997
+.tab-container > .tabs > .tab-panel {
1010998
align-self: stretch;
1011999
flex: 10 1 auto;
10121000
display: block;
10131001
border: 0;
10141002
padding: 0;
@@ -1158,5 +1146,152 @@
11581146
.input-with-label > label {
11591147
font-weight: initial;
11601148
margin: 0 0.25em 0 0.25em;
11611149
vertical-align: middle;
11621150
}
1151
+
1152
+table.numbered-lines {
1153
+ width: 100%;
1154
+ table-layout: fixed /* required to keep ultra-wide code from exceeding
1155
+ window width, and instead force a scrollbar
1156
+ on them. */;
1157
+}
1158
+table.numbered-lines > tbody > tr {
1159
+ font-family: monospace;
1160
+ line-height: 1.35;
1161
+ white-space: pre;
1162
+}
1163
+table.numbered-lines > tbody > tr > td {
1164
+ font-family: inherit;
1165
+ font-size: inherit;
1166
+ line-height: inherit;
1167
+ white-space: inherit;
1168
+ margin: 0;
1169
+ vertical-align: top;
1170
+ padding: 0.25em 0 0 0 /*prevents slight overlap at top */;
1171
+}
1172
+table.numbered-lines td.line-numbers {
1173
+ width: 4.5em;
1174
+}
1175
+table.numbered-lines td.line-numbers > span:first-of-type {
1176
+ margin-top: 0.25em/*must match top PADDING of
1177
+ td.file-content > pre > code*/;
1178
+}
1179
+table.numbered-lines td.line-numbers > span {
1180
+ display: block;
1181
+ margin: 0;
1182
+ padding: 0;
1183
+ line-height: inherit;
1184
+ font-size: inherit;
1185
+ font-family: inherit;
1186
+ cursor: pointer;
1187
+ white-space: pre;
1188
+ margin-right: 2px/*keep selection from nudging the right column */;
1189
+ text-align: right;
1190
+}
1191
+table.numbered-lines td.line-numbers > span:hover {
1192
+ background-color: rgba(112, 112, 112, 0.25);
1193
+}
1194
+table.numbered-lines td.file-content {
1195
+ padding-left: 0.25em;
1196
+}
1197
+table.numbered-lines td.file-content > pre,
1198
+table.numbered-lines td.file-content > pre > code {
1199
+ margin: 0;
1200
+ padding: 0;
1201
+ line-height: inherit;
1202
+ font-size: inherit;
1203
+ font-family: inherit;
1204
+ white-space: pre;
1205
+ display: block/*necessary for certain skins!*/;
1206
+}
1207
+table.numbered-lines td.file-content > pre {
1208
+}
1209
+table.numbered-lines td.file-content > pre > code {
1210
+ overflow: auto;
1211
+ padding-left: 0.5em;
1212
+ padding-right: 0.5em;
1213
+ padding-top: 0.25em/*any top padding here must match the top MARGIN of
1214
+ td.line-numbers's first span child or the
1215
+ lines/code will get misaligned. */;
1216
+ padding-bottom: 0.25em/*prevents a slight overlap at bottom from
1217
+ triggering a scroller*/;
1218
+}
1219
+table.numbered-lines td.file-content > pre > code > * {
1220
+ /* Defense against syntax highlighters indirectly messing up these
1221
+ properties... */
1222
+ line-height: inherit;
1223
+ font-size: inherit;
1224
+ font-family: inherit;
1225
+}
1226
+table.numbered-lines td.line-numbers span.selected-line/*replacement*/ {
1227
+ font-weight: bold;
1228
+ color: blue;
1229
+ background-color: #d5d5ff;
1230
+ border: 1px blue solid;
1231
+ border-top-width: 0;
1232
+ border-bottom-width: 0;
1233
+ padding: 0;
1234
+ margin: 0;
1235
+}
1236
+table.numbered-lines td.line-numbers span.selected-line.start {
1237
+ border-top-width: 1px;
1238
+ margin-top: -1px/*restore alignment*/;
1239
+}
1240
+table.numbered-lines td.line-numbers span.selected-line.end {
1241
+ border-bottom-width: 1px;
1242
+ margin-top: -1px/*restore alignment*/;
1243
+}
1244
+table.numbered-lines td.line-numbers span.selected-line.start.end {
1245
+ margin-top: -2px/*restore alignment*/;
1246
+}
1247
+
1248
+.fossil-tooltip {
1249
+ text-align: center;
1250
+ padding: 0.2em 1em;
1251
+ border: 1px solid black;
1252
+ border-radius: 0.25em;
1253
+ position: absolute;
1254
+ display: inline-block;
1255
+ z-index: 19/*below default skin's hamburger popup*/;
1256
+ box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257
+ background-color: inherit;
1258
+}
1259
+
1260
+.fossil-toast-message {
1261
+ /* "toast"-style popup message.
1262
+ See fossil.popupwidget:toast() */
1263
+ position: absolute;
1264
+ display: block;
1265
+ z-index: 101;
1266
+ text-align: left;
1267
+ padding: 0.15em 0.5em;
1268
+ margin: 0;
1269
+ font-size: 1em;
1270
+ border-width: 1px;
1271
+ border-style: solid;
1272
+ border-color: rgba( 127, 127, 127, 0.75 );
1273
+ border-radius: 0.25em;
1274
+ background-color: rgba(20, 20, 20, 1)
1275
+ /* problem: if we inherit the color it may either be
1276
+ transparent or inherit translucency via the
1277
+ skin, leaving it unreadable. Since we set the bg
1278
+ color we must also set the fg color. */;
1279
+ color: rgba(235, 235, 235, 0.9);
1280
+}
1281
+.fossil-toast-message.error,
1282
+.fossil-toast-message.warning {
1283
+ background: yellow;
1284
+}
1285
+.fossil-toast-message.error {
1286
+ font-weight: bold;
1287
+ color: darkred;
1288
+ border-color: darkred;
1289
+}
1290
+.fossil-toast-message.warning {
1291
+ color: black;
1292
+}
1293
+
1294
+blockquote.file-content {
1295
+ /* file content block in the /file page */
1296
+ margin: 0 1em;
1297
+}
11631298
--- src/default.css
+++ src/default.css
@@ -440,16 +440,10 @@
440 content:"'";
441 }
442 span.usertype:after {
443 content:"'";
444 }
445 div.selectedText {
446 font-weight: bold;
447 color: blue;
448 background-color: #d5d5ff;
449 border: 1px blue solid;
450 }
451 p.missingPriv {
452 color: blue;
453 }
454 span.wikiruleHead {
455 font-weight: bold;
@@ -948,19 +942,23 @@
948 .error {
949 color: darkred;
950 background: yellow;
951 }
952 .warning {
953 color: darkred;
954 background: yellow;
955 opacity: 0.7;
956 }
957 .hidden {
958 position: absolute;
959 opacity: 0;
960 pointer-events: none;
961 display: none;
 
 
 
 
 
962 }
963 input {
964 max-width: 95%;
965 }
966 textarea {
@@ -994,21 +992,11 @@
994 flex-direction: column;
995 border-width: 1px;
996 border-style: outset;
997 border-color: inherit;
998 }
999 .tab-container > .tabs > .tab-panel,
1000 .tab-container > .tabs > fieldset.tab-wrapper {
1001 align-self: stretch;
1002 flex: 10 1 auto;
1003 display: flex;
1004 flex-direction: row;
1005 border: 0;
1006 padding: 0;
1007 margin: 0;
1008 }
1009 .tab-container > .tabs > fieldset.tab-wrapper > .tab-panel{
1010 align-self: stretch;
1011 flex: 10 1 auto;
1012 display: block;
1013 border: 0;
1014 padding: 0;
@@ -1158,5 +1146,152 @@
1158 .input-with-label > label {
1159 font-weight: initial;
1160 margin: 0 0.25em 0 0.25em;
1161 vertical-align: middle;
1162 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1163
--- src/default.css
+++ src/default.css
@@ -440,16 +440,10 @@
440 content:"'";
441 }
442 span.usertype:after {
443 content:"'";
444 }
 
 
 
 
 
 
445 p.missingPriv {
446 color: blue;
447 }
448 span.wikiruleHead {
449 font-weight: bold;
@@ -948,19 +942,23 @@
942 .error {
943 color: darkred;
944 background: yellow;
945 }
946 .warning {
947 color: black;
948 background: yellow;
 
949 }
950 .hidden {
951 /* The framework-wide way of hiding elements is to assign them this
952 CSS class. To make them visible again, remove it. The !important
953 qualifiers are unfortunate but sometimes necessary when hidden
954 element has other classes which specify visibility-related
955 options. */
956 position: absolute !important;
957 opacity: 0 !important;
958 pointer-events: none !important;
959 display: none !important;
960 }
961 input {
962 max-width: 95%;
963 }
964 textarea {
@@ -994,21 +992,11 @@
992 flex-direction: column;
993 border-width: 1px;
994 border-style: outset;
995 border-color: inherit;
996 }
997 .tab-container > .tabs > .tab-panel {
 
 
 
 
 
 
 
 
 
 
998 align-self: stretch;
999 flex: 10 1 auto;
1000 display: block;
1001 border: 0;
1002 padding: 0;
@@ -1158,5 +1146,152 @@
1146 .input-with-label > label {
1147 font-weight: initial;
1148 margin: 0 0.25em 0 0.25em;
1149 vertical-align: middle;
1150 }
1151
1152 table.numbered-lines {
1153 width: 100%;
1154 table-layout: fixed /* required to keep ultra-wide code from exceeding
1155 window width, and instead force a scrollbar
1156 on them. */;
1157 }
1158 table.numbered-lines > tbody > tr {
1159 font-family: monospace;
1160 line-height: 1.35;
1161 white-space: pre;
1162 }
1163 table.numbered-lines > tbody > tr > td {
1164 font-family: inherit;
1165 font-size: inherit;
1166 line-height: inherit;
1167 white-space: inherit;
1168 margin: 0;
1169 vertical-align: top;
1170 padding: 0.25em 0 0 0 /*prevents slight overlap at top */;
1171 }
1172 table.numbered-lines td.line-numbers {
1173 width: 4.5em;
1174 }
1175 table.numbered-lines td.line-numbers > span:first-of-type {
1176 margin-top: 0.25em/*must match top PADDING of
1177 td.file-content > pre > code*/;
1178 }
1179 table.numbered-lines td.line-numbers > span {
1180 display: block;
1181 margin: 0;
1182 padding: 0;
1183 line-height: inherit;
1184 font-size: inherit;
1185 font-family: inherit;
1186 cursor: pointer;
1187 white-space: pre;
1188 margin-right: 2px/*keep selection from nudging the right column */;
1189 text-align: right;
1190 }
1191 table.numbered-lines td.line-numbers > span:hover {
1192 background-color: rgba(112, 112, 112, 0.25);
1193 }
1194 table.numbered-lines td.file-content {
1195 padding-left: 0.25em;
1196 }
1197 table.numbered-lines td.file-content > pre,
1198 table.numbered-lines td.file-content > pre > code {
1199 margin: 0;
1200 padding: 0;
1201 line-height: inherit;
1202 font-size: inherit;
1203 font-family: inherit;
1204 white-space: pre;
1205 display: block/*necessary for certain skins!*/;
1206 }
1207 table.numbered-lines td.file-content > pre {
1208 }
1209 table.numbered-lines td.file-content > pre > code {
1210 overflow: auto;
1211 padding-left: 0.5em;
1212 padding-right: 0.5em;
1213 padding-top: 0.25em/*any top padding here must match the top MARGIN of
1214 td.line-numbers's first span child or the
1215 lines/code will get misaligned. */;
1216 padding-bottom: 0.25em/*prevents a slight overlap at bottom from
1217 triggering a scroller*/;
1218 }
1219 table.numbered-lines td.file-content > pre > code > * {
1220 /* Defense against syntax highlighters indirectly messing up these
1221 properties... */
1222 line-height: inherit;
1223 font-size: inherit;
1224 font-family: inherit;
1225 }
1226 table.numbered-lines td.line-numbers span.selected-line/*replacement*/ {
1227 font-weight: bold;
1228 color: blue;
1229 background-color: #d5d5ff;
1230 border: 1px blue solid;
1231 border-top-width: 0;
1232 border-bottom-width: 0;
1233 padding: 0;
1234 margin: 0;
1235 }
1236 table.numbered-lines td.line-numbers span.selected-line.start {
1237 border-top-width: 1px;
1238 margin-top: -1px/*restore alignment*/;
1239 }
1240 table.numbered-lines td.line-numbers span.selected-line.end {
1241 border-bottom-width: 1px;
1242 margin-top: -1px/*restore alignment*/;
1243 }
1244 table.numbered-lines td.line-numbers span.selected-line.start.end {
1245 margin-top: -2px/*restore alignment*/;
1246 }
1247
1248 .fossil-tooltip {
1249 text-align: center;
1250 padding: 0.2em 1em;
1251 border: 1px solid black;
1252 border-radius: 0.25em;
1253 position: absolute;
1254 display: inline-block;
1255 z-index: 19/*below default skin's hamburger popup*/;
1256 box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.75);
1257 background-color: inherit;
1258 }
1259
1260 .fossil-toast-message {
1261 /* "toast"-style popup message.
1262 See fossil.popupwidget:toast() */
1263 position: absolute;
1264 display: block;
1265 z-index: 101;
1266 text-align: left;
1267 padding: 0.15em 0.5em;
1268 margin: 0;
1269 font-size: 1em;
1270 border-width: 1px;
1271 border-style: solid;
1272 border-color: rgba( 127, 127, 127, 0.75 );
1273 border-radius: 0.25em;
1274 background-color: rgba(20, 20, 20, 1)
1275 /* problem: if we inherit the color it may either be
1276 transparent or inherit translucency via the
1277 skin, leaving it unreadable. Since we set the bg
1278 color we must also set the fg color. */;
1279 color: rgba(235, 235, 235, 0.9);
1280 }
1281 .fossil-toast-message.error,
1282 .fossil-toast-message.warning {
1283 background: yellow;
1284 }
1285 .fossil-toast-message.error {
1286 font-weight: bold;
1287 color: darkred;
1288 border-color: darkred;
1289 }
1290 .fossil-toast-message.warning {
1291 color: black;
1292 }
1293
1294 blockquote.file-content {
1295 /* file content block in the /file page */
1296 margin: 0 1em;
1297 }
1298
+2 -2
--- src/diff.c
+++ src/diff.c
@@ -125,11 +125,11 @@
125125
** in the count even if it lacks the \n terminator. If an empty string
126126
** is specified, the number of lines is zero. For the purposes of this
127127
** function, a string is considered empty if it contains no characters
128128
** -OR- it contains only NUL characters.
129129
*/
130
-static int count_lines(
130
+int count_lines(
131131
const char *z,
132132
int n,
133133
int *pnLine
134134
){
135135
int nLine;
@@ -1566,11 +1566,11 @@
15661566
iSYp = iSY;
15671567
iEXp = iEX;
15681568
iEYp = iEY;
15691569
}
15701570
}
1571
- if( iSXb==iEXb && (iE1-iS1)*(iE2-iS2)<400 ){
1571
+ if( iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<400 ){
15721572
/* If no common sequence is found using the hashing heuristic and
15731573
** the input is not too big, use the expensive exact solution */
15741574
optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY);
15751575
}else{
15761576
*piSX = iSXb;
15771577
--- src/diff.c
+++ src/diff.c
@@ -125,11 +125,11 @@
125 ** in the count even if it lacks the \n terminator. If an empty string
126 ** is specified, the number of lines is zero. For the purposes of this
127 ** function, a string is considered empty if it contains no characters
128 ** -OR- it contains only NUL characters.
129 */
130 static int count_lines(
131 const char *z,
132 int n,
133 int *pnLine
134 ){
135 int nLine;
@@ -1566,11 +1566,11 @@
1566 iSYp = iSY;
1567 iEXp = iEX;
1568 iEYp = iEY;
1569 }
1570 }
1571 if( iSXb==iEXb && (iE1-iS1)*(iE2-iS2)<400 ){
1572 /* If no common sequence is found using the hashing heuristic and
1573 ** the input is not too big, use the expensive exact solution */
1574 optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY);
1575 }else{
1576 *piSX = iSXb;
1577
--- src/diff.c
+++ src/diff.c
@@ -125,11 +125,11 @@
125 ** in the count even if it lacks the \n terminator. If an empty string
126 ** is specified, the number of lines is zero. For the purposes of this
127 ** function, a string is considered empty if it contains no characters
128 ** -OR- it contains only NUL characters.
129 */
130 int count_lines(
131 const char *z,
132 int n,
133 int *pnLine
134 ){
135 int nLine;
@@ -1566,11 +1566,11 @@
1566 iSYp = iSY;
1567 iEXp = iEX;
1568 iEYp = iEY;
1569 }
1570 }
1571 if( iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<400 ){
1572 /* If no common sequence is found using the hashing heuristic and
1573 ** the input is not too big, use the expensive exact solution */
1574 optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY);
1575 }else{
1576 *piSX = iSXb;
1577
+49 -40
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
4747
** used for files that are under management by a Fossil repository. ExtFILE
4848
** should be used for files that are not under management. SymFILE is for
4949
** a few special cases such as the "fossil test-tarball" command when we never
5050
** want to follow symlinks.
5151
**
52
-** If RepoFILE is used and if the allow-symlinks setting is true and if
53
-** the object is a symbolic link, then the object is treated like an ordinary
54
-** file whose content is name of the object to which the symbolic link
55
-** points.
56
-**
57
-** If ExtFILE is used or allow-symlinks is false, then operations on a
58
-** symbolic link are the same as operations on the object to which the
59
-** symbolic link points.
60
-**
61
-** SymFILE is like RepoFILE except that it always uses the target filename of
62
-** a symbolic link as the content, instead of the content of the object
63
-** that the symlink points to. SymFILE acts as if allow-symlinks is always ON.
52
+** ExtFILE Symbolic links always refer to the object to which the
53
+** link points. Symlinks are never recognized as symlinks but
54
+** instead always appear to the the target object.
55
+**
56
+** SymFILE Symbolic links always appear to be files whose name is
57
+** the target pathname of the symbolic link.
58
+**
59
+** RepoFILE Like symfile is allow-symlinks is true, or like
60
+** ExtFile if allow-symlinks is false. In other words,
61
+** symbolic links are only recognized as something different
62
+** from files or directories if allow-symlinks is true.
6463
*/
6564
#define ExtFILE 0 /* Always follow symlinks */
6665
#define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
6766
#define SymFILE 2 /* Never follow symlinks */
6867
@@ -134,13 +133,16 @@
134133
int eFType /* Look at symlink itself if RepoFILE and enabled. */
135134
){
136135
int rc;
137136
void *zMbcs = fossil_utf8_to_path(zFilename, 0);
138137
#if !defined(_WIN32)
139
- if( eFType>=RepoFILE && (eFType==SymFILE || db_allow_symlinks()) ){
138
+ if( (eFType=RepoFILE && db_allow_symlinks())
139
+ || eFType==SymFILE ){
140
+ /* Symlinks look like files whose content is the name of the target */
140141
rc = lstat(zMbcs, buf);
141142
}else{
143
+ /* Symlinks look like the object to which they point */
142144
rc = stat(zMbcs, buf);
143145
}
144146
#else
145147
rc = win32_stat(zMbcs, buf, eFType);
146148
#endif
@@ -316,11 +318,12 @@
316318
317319
/*
318320
** Return TRUE if the named file is a symlink and symlinks are allowed.
319321
** Return false for all other cases.
320322
**
321
-** This routines RepoFILE - that zFilename is always a file under management.
323
+** This routines assumes RepoFILE - that zFilename is always a file
324
+** under management.
322325
**
323326
** On Windows, always return False.
324327
*/
325328
int file_islink(const char *zFilename){
326329
return file_perm(zFilename, RepoFILE)==PERM_LNK;
@@ -1311,12 +1314,12 @@
13111314
sqlite3_int64 iMtime;
13121315
struct fossilStat testFileStat;
13131316
memset(zBuf, 0, sizeof(zBuf));
13141317
blob_zero(&x);
13151318
file_canonical_name(zPath, &x, slash);
1316
- fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
1317
- blob_reset(&x);
1319
+ char *zFull = blob_str(&x);
1320
+ fossil_print("[%s] -> [%s]\n", zPath, zFull);
13181321
memset(&testFileStat, 0, sizeof(struct fossilStat));
13191322
rc = fossil_stat(zPath, &testFileStat, 0);
13201323
fossil_print(" stat_rc = %d\n", rc);
13211324
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
13221325
fossil_print(" stat_size = %s\n", zBuf);
@@ -1360,10 +1363,13 @@
13601363
fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
13611364
fossil_print(" file_islink = %d\n", file_islink(zPath));
13621365
fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
13631366
fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
13641367
fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
1368
+ fossil_print(" file_is_reserved_name = %d\n",
1369
+ file_is_reserved_name(zFull,-1));
1370
+ blob_reset(&x);
13651371
if( reset ) resetStat();
13661372
}
13671373
13681374
/*
13691375
** COMMAND: test-file-environment
@@ -1375,32 +1381,45 @@
13751381
**
13761382
** Options:
13771383
**
13781384
** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
13791385
** --open-config Open the configuration database first.
1380
-** --slash Trailing slashes, if any, are retained.
13811386
** --reset Reset cached stat() info for each file.
1387
+** --root ROOT Use ROOT as the root of the checkout
1388
+** --slash Trailing slashes, if any, are retained.
13821389
*/
13831390
void cmd_test_file_environment(void){
13841391
int i;
13851392
int slashFlag = find_option("slash",0,0)!=0;
13861393
int resetFlag = find_option("reset",0,0)!=0;
1394
+ const char *zRoot = find_option("root",0,1);
13871395
const char *zAllow = find_option("allow-symlinks",0,1);
13881396
if( find_option("open-config", 0, 0)!=0 ){
13891397
Th_OpenConfig(1);
13901398
}
13911399
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
13921400
fossil_print("filenames_are_case_sensitive() = %d\n",
13931401
filenames_are_case_sensitive());
1394
- fossil_print("db_allow_symlinks_by_default() = %d\n",
1395
- db_allow_symlinks_by_default());
13961402
if( zAllow ){
13971403
g.allowSymlinks = !is_false(zAllow);
13981404
}
1405
+ if( zRoot==0 ) zRoot = g.zLocalRoot;
13991406
fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1407
+ fossil_print("local-root = [%s]\n", zRoot);
14001408
for(i=2; i<g.argc; i++){
1409
+ char *z;
14011410
emitFileStat(g.argv[i], slashFlag, resetFlag);
1411
+ z = file_canonical_name_dup(g.argv[i]);
1412
+ fossil_print(" file_canonical_name = %s\n", z);
1413
+ fossil_print(" file_nondir_path = ");
1414
+ if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){
1415
+ fossil_print("(--root is not a prefix of this file)\n");
1416
+ }else{
1417
+ int n = file_nondir_objects_on_path(zRoot, z);
1418
+ fossil_print("%.*s\n", n, z);
1419
+ }
1420
+ fossil_free(z);
14021421
}
14031422
}
14041423
14051424
/*
14061425
** COMMAND: test-canonical-name
@@ -2472,10 +2491,21 @@
24722491
changeCount);
24732492
}else{
24742493
fossil_print("Touched %d file(s)\n", changeCount);
24752494
}
24762495
}
2496
+
2497
+/*
2498
+** If zFileName is not NULL and contains a '.', this returns a pointer
2499
+** to the position after the final '.', else it returns NULL. As a
2500
+** special case, if it ends with a period then a pointer to the
2501
+** terminating NUL byte is returned.
2502
+*/
2503
+const char * file_extension(const char *zFileName){
2504
+ const char * zExt = zFileName ? strrchr(zFileName, '.') : 0;
2505
+ return zExt ? &zExt[1] : 0;
2506
+}
24772507
24782508
/*
24792509
** Returns non-zero if the specified file name ends with any reserved name,
24802510
** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match
24812511
** or 2 for a tail match on a longer file name.
@@ -2534,26 +2564,5 @@
25342564
default:{
25352565
return 0;
25362566
}
25372567
}
25382568
}
2539
-
2540
-/*
2541
-** COMMAND: test-is-reserved-name
2542
-**
2543
-** Usage: %fossil test-is-ckout-db FILENAMES...
2544
-**
2545
-** Passes each given name to file_is_reserved_name() and outputs one
2546
-** line per file: the result value of that function followed by the
2547
-** name.
2548
-*/
2549
-void test_is_reserved_name_cmd(void){
2550
- int i;
2551
-
2552
- if(g.argc<3){
2553
- usage("FILENAME_1 [...FILENAME_N]");
2554
- }
2555
- for( i = 2; i < g.argc; ++i ){
2556
- const int check = file_is_reserved_name(g.argv[i], -1);
2557
- fossil_print("%d %s\n", check, g.argv[i]);
2558
- }
2559
-}
25602569
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
47 ** used for files that are under management by a Fossil repository. ExtFILE
48 ** should be used for files that are not under management. SymFILE is for
49 ** a few special cases such as the "fossil test-tarball" command when we never
50 ** want to follow symlinks.
51 **
52 ** If RepoFILE is used and if the allow-symlinks setting is true and if
53 ** the object is a symbolic link, then the object is treated like an ordinary
54 ** file whose content is name of the object to which the symbolic link
55 ** points.
56 **
57 ** If ExtFILE is used or allow-symlinks is false, then operations on a
58 ** symbolic link are the same as operations on the object to which the
59 ** symbolic link points.
60 **
61 ** SymFILE is like RepoFILE except that it always uses the target filename of
62 ** a symbolic link as the content, instead of the content of the object
63 ** that the symlink points to. SymFILE acts as if allow-symlinks is always ON.
64 */
65 #define ExtFILE 0 /* Always follow symlinks */
66 #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
67 #define SymFILE 2 /* Never follow symlinks */
68
@@ -134,13 +133,16 @@
134 int eFType /* Look at symlink itself if RepoFILE and enabled. */
135 ){
136 int rc;
137 void *zMbcs = fossil_utf8_to_path(zFilename, 0);
138 #if !defined(_WIN32)
139 if( eFType>=RepoFILE && (eFType==SymFILE || db_allow_symlinks()) ){
 
 
140 rc = lstat(zMbcs, buf);
141 }else{
 
142 rc = stat(zMbcs, buf);
143 }
144 #else
145 rc = win32_stat(zMbcs, buf, eFType);
146 #endif
@@ -316,11 +318,12 @@
316
317 /*
318 ** Return TRUE if the named file is a symlink and symlinks are allowed.
319 ** Return false for all other cases.
320 **
321 ** This routines RepoFILE - that zFilename is always a file under management.
 
322 **
323 ** On Windows, always return False.
324 */
325 int file_islink(const char *zFilename){
326 return file_perm(zFilename, RepoFILE)==PERM_LNK;
@@ -1311,12 +1314,12 @@
1311 sqlite3_int64 iMtime;
1312 struct fossilStat testFileStat;
1313 memset(zBuf, 0, sizeof(zBuf));
1314 blob_zero(&x);
1315 file_canonical_name(zPath, &x, slash);
1316 fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
1317 blob_reset(&x);
1318 memset(&testFileStat, 0, sizeof(struct fossilStat));
1319 rc = fossil_stat(zPath, &testFileStat, 0);
1320 fossil_print(" stat_rc = %d\n", rc);
1321 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
1322 fossil_print(" stat_size = %s\n", zBuf);
@@ -1360,10 +1363,13 @@
1360 fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
1361 fossil_print(" file_islink = %d\n", file_islink(zPath));
1362 fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
1363 fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
1364 fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
 
 
 
1365 if( reset ) resetStat();
1366 }
1367
1368 /*
1369 ** COMMAND: test-file-environment
@@ -1375,32 +1381,45 @@
1375 **
1376 ** Options:
1377 **
1378 ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
1379 ** --open-config Open the configuration database first.
1380 ** --slash Trailing slashes, if any, are retained.
1381 ** --reset Reset cached stat() info for each file.
 
 
1382 */
1383 void cmd_test_file_environment(void){
1384 int i;
1385 int slashFlag = find_option("slash",0,0)!=0;
1386 int resetFlag = find_option("reset",0,0)!=0;
 
1387 const char *zAllow = find_option("allow-symlinks",0,1);
1388 if( find_option("open-config", 0, 0)!=0 ){
1389 Th_OpenConfig(1);
1390 }
1391 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
1392 fossil_print("filenames_are_case_sensitive() = %d\n",
1393 filenames_are_case_sensitive());
1394 fossil_print("db_allow_symlinks_by_default() = %d\n",
1395 db_allow_symlinks_by_default());
1396 if( zAllow ){
1397 g.allowSymlinks = !is_false(zAllow);
1398 }
 
1399 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
 
1400 for(i=2; i<g.argc; i++){
 
1401 emitFileStat(g.argv[i], slashFlag, resetFlag);
 
 
 
 
 
 
 
 
 
 
1402 }
1403 }
1404
1405 /*
1406 ** COMMAND: test-canonical-name
@@ -2472,10 +2491,21 @@
2472 changeCount);
2473 }else{
2474 fossil_print("Touched %d file(s)\n", changeCount);
2475 }
2476 }
 
 
 
 
 
 
 
 
 
 
 
2477
2478 /*
2479 ** Returns non-zero if the specified file name ends with any reserved name,
2480 ** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match
2481 ** or 2 for a tail match on a longer file name.
@@ -2534,26 +2564,5 @@
2534 default:{
2535 return 0;
2536 }
2537 }
2538 }
2539
2540 /*
2541 ** COMMAND: test-is-reserved-name
2542 **
2543 ** Usage: %fossil test-is-ckout-db FILENAMES...
2544 **
2545 ** Passes each given name to file_is_reserved_name() and outputs one
2546 ** line per file: the result value of that function followed by the
2547 ** name.
2548 */
2549 void test_is_reserved_name_cmd(void){
2550 int i;
2551
2552 if(g.argc<3){
2553 usage("FILENAME_1 [...FILENAME_N]");
2554 }
2555 for( i = 2; i < g.argc; ++i ){
2556 const int check = file_is_reserved_name(g.argv[i], -1);
2557 fossil_print("%d %s\n", check, g.argv[i]);
2558 }
2559 }
2560
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
47 ** used for files that are under management by a Fossil repository. ExtFILE
48 ** should be used for files that are not under management. SymFILE is for
49 ** a few special cases such as the "fossil test-tarball" command when we never
50 ** want to follow symlinks.
51 **
52 ** ExtFILE Symbolic links always refer to the object to which the
53 ** link points. Symlinks are never recognized as symlinks but
54 ** instead always appear to the the target object.
55 **
56 ** SymFILE Symbolic links always appear to be files whose name is
57 ** the target pathname of the symbolic link.
58 **
59 ** RepoFILE Like symfile is allow-symlinks is true, or like
60 ** ExtFile if allow-symlinks is false. In other words,
61 ** symbolic links are only recognized as something different
62 ** from files or directories if allow-symlinks is true.
 
63 */
64 #define ExtFILE 0 /* Always follow symlinks */
65 #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
66 #define SymFILE 2 /* Never follow symlinks */
67
@@ -134,13 +133,16 @@
133 int eFType /* Look at symlink itself if RepoFILE and enabled. */
134 ){
135 int rc;
136 void *zMbcs = fossil_utf8_to_path(zFilename, 0);
137 #if !defined(_WIN32)
138 if( (eFType=RepoFILE && db_allow_symlinks())
139 || eFType==SymFILE ){
140 /* Symlinks look like files whose content is the name of the target */
141 rc = lstat(zMbcs, buf);
142 }else{
143 /* Symlinks look like the object to which they point */
144 rc = stat(zMbcs, buf);
145 }
146 #else
147 rc = win32_stat(zMbcs, buf, eFType);
148 #endif
@@ -316,11 +318,12 @@
318
319 /*
320 ** Return TRUE if the named file is a symlink and symlinks are allowed.
321 ** Return false for all other cases.
322 **
323 ** This routines assumes RepoFILE - that zFilename is always a file
324 ** under management.
325 **
326 ** On Windows, always return False.
327 */
328 int file_islink(const char *zFilename){
329 return file_perm(zFilename, RepoFILE)==PERM_LNK;
@@ -1311,12 +1314,12 @@
1314 sqlite3_int64 iMtime;
1315 struct fossilStat testFileStat;
1316 memset(zBuf, 0, sizeof(zBuf));
1317 blob_zero(&x);
1318 file_canonical_name(zPath, &x, slash);
1319 char *zFull = blob_str(&x);
1320 fossil_print("[%s] -> [%s]\n", zPath, zFull);
1321 memset(&testFileStat, 0, sizeof(struct fossilStat));
1322 rc = fossil_stat(zPath, &testFileStat, 0);
1323 fossil_print(" stat_rc = %d\n", rc);
1324 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
1325 fossil_print(" stat_size = %s\n", zBuf);
@@ -1360,10 +1363,13 @@
1363 fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
1364 fossil_print(" file_islink = %d\n", file_islink(zPath));
1365 fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
1366 fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
1367 fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
1368 fossil_print(" file_is_reserved_name = %d\n",
1369 file_is_reserved_name(zFull,-1));
1370 blob_reset(&x);
1371 if( reset ) resetStat();
1372 }
1373
1374 /*
1375 ** COMMAND: test-file-environment
@@ -1375,32 +1381,45 @@
1381 **
1382 ** Options:
1383 **
1384 ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
1385 ** --open-config Open the configuration database first.
 
1386 ** --reset Reset cached stat() info for each file.
1387 ** --root ROOT Use ROOT as the root of the checkout
1388 ** --slash Trailing slashes, if any, are retained.
1389 */
1390 void cmd_test_file_environment(void){
1391 int i;
1392 int slashFlag = find_option("slash",0,0)!=0;
1393 int resetFlag = find_option("reset",0,0)!=0;
1394 const char *zRoot = find_option("root",0,1);
1395 const char *zAllow = find_option("allow-symlinks",0,1);
1396 if( find_option("open-config", 0, 0)!=0 ){
1397 Th_OpenConfig(1);
1398 }
1399 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
1400 fossil_print("filenames_are_case_sensitive() = %d\n",
1401 filenames_are_case_sensitive());
 
 
1402 if( zAllow ){
1403 g.allowSymlinks = !is_false(zAllow);
1404 }
1405 if( zRoot==0 ) zRoot = g.zLocalRoot;
1406 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1407 fossil_print("local-root = [%s]\n", zRoot);
1408 for(i=2; i<g.argc; i++){
1409 char *z;
1410 emitFileStat(g.argv[i], slashFlag, resetFlag);
1411 z = file_canonical_name_dup(g.argv[i]);
1412 fossil_print(" file_canonical_name = %s\n", z);
1413 fossil_print(" file_nondir_path = ");
1414 if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){
1415 fossil_print("(--root is not a prefix of this file)\n");
1416 }else{
1417 int n = file_nondir_objects_on_path(zRoot, z);
1418 fossil_print("%.*s\n", n, z);
1419 }
1420 fossil_free(z);
1421 }
1422 }
1423
1424 /*
1425 ** COMMAND: test-canonical-name
@@ -2472,10 +2491,21 @@
2491 changeCount);
2492 }else{
2493 fossil_print("Touched %d file(s)\n", changeCount);
2494 }
2495 }
2496
2497 /*
2498 ** If zFileName is not NULL and contains a '.', this returns a pointer
2499 ** to the position after the final '.', else it returns NULL. As a
2500 ** special case, if it ends with a period then a pointer to the
2501 ** terminating NUL byte is returned.
2502 */
2503 const char * file_extension(const char *zFileName){
2504 const char * zExt = zFileName ? strrchr(zFileName, '.') : 0;
2505 return zExt ? &zExt[1] : 0;
2506 }
2507
2508 /*
2509 ** Returns non-zero if the specified file name ends with any reserved name,
2510 ** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match
2511 ** or 2 for a tail match on a longer file name.
@@ -2534,26 +2564,5 @@
2564 default:{
2565 return 0;
2566 }
2567 }
2568 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2569
+451 -530
--- src/forum.c
+++ src/forum.c
@@ -26,44 +26,45 @@
2626
*/
2727
#define DEFAULT_FORUM_MIMETYPE "text/x-markdown"
2828
2929
#if INTERFACE
3030
/*
31
-** Each instance of the following object represents a single message -
31
+** Each instance of the following object represents a single message -
3232
** either the initial post, an edit to a post, a reply, or an edit to
3333
** a reply.
3434
*/
35
-struct ForumEntry {
36
- int fpid; /* rid for this entry */
37
- int fprev; /* zero if initial entry. non-zero if an edit */
38
- int firt; /* This entry replies to firt */
39
- int mfirt; /* Root in-reply-to */
40
- int nReply; /* Number of replies to this entry */
35
+struct ForumPost {
36
+ int fpid; /* rid for this post */
4137
int sid; /* Serial ID number */
38
+ int rev; /* Revision number */
4239
char *zUuid; /* Artifact hash */
43
- ForumEntry *pLeaf; /* Most recent edit for this entry */
44
- ForumEntry *pEdit; /* This entry is an edit of pEdit */
45
- ForumEntry *pNext; /* Next in chronological order */
46
- ForumEntry *pPrev; /* Previous in chronological order */
47
- ForumEntry *pDisplay; /* Next in display order */
48
- int nIndent; /* Number of levels of indentation for this entry */
40
+ ForumPost *pIrt; /* This post replies to pIrt */
41
+ ForumPost *pEditHead; /* Original, unedited post */
42
+ ForumPost *pEditTail; /* Most recent edit for this post */
43
+ ForumPost *pEditNext; /* This post is edited by pEditNext */
44
+ ForumPost *pEditPrev; /* This post is an edit of pEditPrev */
45
+ ForumPost *pNext; /* Next in chronological order */
46
+ ForumPost *pPrev; /* Previous in chronological order */
47
+ ForumPost *pDisplay; /* Next in display order */
48
+ int nEdit; /* Number of edits to this post */
49
+ int nIndent; /* Number of levels of indentation for this post */
4950
};
5051
5152
/*
5253
** A single instance of the following tracks all entries for a thread.
5354
*/
5455
struct ForumThread {
55
- ForumEntry *pFirst; /* First entry in chronological order */
56
- ForumEntry *pLast; /* Last entry in chronological order */
57
- ForumEntry *pDisplay; /* Entries in display order */
58
- ForumEntry *pTail; /* Last on the display list */
56
+ ForumPost *pFirst; /* First post in chronological order */
57
+ ForumPost *pLast; /* Last post in chronological order */
58
+ ForumPost *pDisplay; /* Entries in display order */
59
+ ForumPost *pTail; /* Last on the display list */
5960
int mxIndent; /* Maximum indentation level */
6061
};
6162
#endif /* INTERFACE */
6263
6364
/*
64
-** Return true if the forum entry with the given rid has been
65
+** Return true if the forum post with the given rid has been
6566
** subsequently edited.
6667
*/
6768
int forum_rid_has_been_edited(int rid){
6869
static Stmt q;
6970
int res;
@@ -79,41 +80,39 @@
7980
8081
/*
8182
** Delete a complete ForumThread and all its entries.
8283
*/
8384
static void forumthread_delete(ForumThread *pThread){
84
- ForumEntry *pEntry, *pNext;
85
- for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
86
- pNext = pEntry->pNext;
87
- fossil_free(pEntry->zUuid);
88
- fossil_free(pEntry);
85
+ ForumPost *pPost, *pNext;
86
+ for(pPost=pThread->pFirst; pPost; pPost = pNext){
87
+ pNext = pPost->pNext;
88
+ fossil_free(pPost->zUuid);
89
+ fossil_free(pPost);
8990
}
9091
fossil_free(pThread);
9192
}
9293
93
-#if 0 /* not used */
9494
/*
95
-** Search a ForumEntry list forwards looking for the entry with fpid
95
+** Search a ForumPost list forwards looking for the post with fpid
9696
*/
97
-static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){
97
+static ForumPost *forumpost_forward(ForumPost *p, int fpid){
9898
while( p && p->fpid!=fpid ) p = p->pNext;
9999
return p;
100100
}
101
-#endif
102101
103102
/*
104
-** Search backwards for a ForumEntry
103
+** Search backwards for a ForumPost
105104
*/
106
-static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){
105
+static ForumPost *forumpost_backward(ForumPost *p, int fpid){
107106
while( p && p->fpid!=fpid ) p = p->pPrev;
108107
return p;
109108
}
110109
111110
/*
112
-** Add an entry to the display list
111
+** Add a post to the display list
113112
*/
114
-static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){
113
+static void forumpost_add_to_display(ForumThread *pThread, ForumPost *p){
115114
if( pThread->pDisplay==0 ){
116115
pThread->pDisplay = p;
117116
}else{
118117
pThread->pTail->pDisplay = p;
119118
}
@@ -120,108 +119,112 @@
120119
pThread->pTail = p;
121120
}
122121
123122
/*
124123
** Extend the display list for pThread by adding all entries that
125
-** reference fpid. The first such entry will be no earlier then
126
-** entry "p".
124
+** reference fpid. The first such post will be no earlier then
125
+** post "p".
127126
*/
128127
static void forumthread_display_order(
129128
ForumThread *pThread, /* The complete thread */
130
- ForumEntry *pBase /* Add replies to this entry */
129
+ ForumPost *pBase /* Add replies to this post */
131130
){
132
- ForumEntry *p;
133
- ForumEntry *pPrev = 0;
131
+ ForumPost *p;
132
+ ForumPost *pPrev = 0;
133
+ ForumPost *pBaseIrt;
134134
for(p=pBase->pNext; p; p=p->pNext){
135
- if( p->fprev==0 && p->mfirt==pBase->fpid ){
136
- if( pPrev ){
137
- pPrev->nIndent = pBase->nIndent + 1;
138
- forumentry_add_to_display(pThread, pPrev);
139
- forumthread_display_order(pThread, pPrev);
140
- }
141
- pBase->nReply++;
142
- pPrev = p;
135
+ if( !p->pEditPrev && p->pIrt ){
136
+ pBaseIrt = p->pIrt->pEditHead ? p->pIrt->pEditHead : p->pIrt;
137
+ if( pBaseIrt==pBase ){
138
+ if( pPrev ){
139
+ pPrev->nIndent = pBase->nIndent + 1;
140
+ forumpost_add_to_display(pThread, pPrev);
141
+ forumthread_display_order(pThread, pPrev);
142
+ }
143
+ pPrev = p;
144
+ }
143145
}
144146
}
145147
if( pPrev ){
146148
pPrev->nIndent = pBase->nIndent + 1;
147149
if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent;
148
- forumentry_add_to_display(pThread, pPrev);
150
+ forumpost_add_to_display(pThread, pPrev);
149151
forumthread_display_order(pThread, pPrev);
150152
}
151153
}
152154
153155
/*
154156
** Construct a ForumThread object given the root record id.
155157
*/
156158
static ForumThread *forumthread_create(int froot, int computeHierarchy){
157159
ForumThread *pThread;
158
- ForumEntry *pEntry;
160
+ ForumPost *pPost;
161
+ ForumPost *p;
159162
Stmt q;
160163
int sid = 1;
161
- Bag seen = Bag_INIT;
164
+ int firt, fprev;
162165
pThread = fossil_malloc( sizeof(*pThread) );
163166
memset(pThread, 0, sizeof(*pThread));
164167
db_prepare(&q,
165168
"SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
166169
" FROM forumpost"
167170
" WHERE froot=%d ORDER BY fmtime",
168171
froot
169172
);
170173
while( db_step(&q)==SQLITE_ROW ){
171
- pEntry = fossil_malloc( sizeof(*pEntry) );
172
- memset(pEntry, 0, sizeof(*pEntry));
173
- pEntry->fpid = db_column_int(&q, 0);
174
- pEntry->firt = db_column_int(&q, 1);
175
- pEntry->fprev = db_column_int(&q, 2);
176
- pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
177
- pEntry->mfirt = pEntry->firt;
178
- pEntry->sid = sid++;
179
- pEntry->pPrev = pThread->pLast;
180
- pEntry->pNext = 0;
181
- bag_insert(&seen, pEntry->fpid);
174
+ pPost = fossil_malloc( sizeof(*pPost) );
175
+ memset(pPost, 0, sizeof(*pPost));
176
+ pPost->fpid = db_column_int(&q, 0);
177
+ firt = db_column_int(&q, 1);
178
+ fprev = db_column_int(&q, 2);
179
+ pPost->zUuid = fossil_strdup(db_column_text(&q,3));
180
+ if( !fprev ) pPost->sid = sid++;
181
+ pPost->pPrev = pThread->pLast;
182
+ pPost->pNext = 0;
182183
if( pThread->pLast==0 ){
183
- pThread->pFirst = pEntry;
184
- }else{
185
- pThread->pLast->pNext = pEntry;
186
- }
187
- if( pEntry->firt && !bag_find(&seen,pEntry->firt) ){
188
- pEntry->firt = froot;
189
- pEntry->mfirt = froot;
190
- }
191
- pThread->pLast = pEntry;
192
- }
193
- db_finalize(&q);
194
- bag_clear(&seen);
195
-
196
- /* Establish which entries are the latest edit. After this loop
197
- ** completes, entries that have non-NULL pLeaf should not be
198
- ** displayed.
199
- */
200
- for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
201
- if( pEntry->fprev ){
202
- ForumEntry *pBase = 0, *p;
203
- p = forumentry_backward(pEntry->pPrev, pEntry->fprev);
204
- pEntry->pEdit = p;
205
- while( p ){
206
- pBase = p;
207
- p->pLeaf = pEntry;
208
- p = pBase->pEdit;
209
- }
210
- for(p=pEntry->pNext; p; p=p->pNext){
211
- if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->fpid;
212
- }
213
- }
214
- }
184
+ pThread->pFirst = pPost;
185
+ }else{
186
+ pThread->pLast->pNext = pPost;
187
+ }
188
+ pThread->pLast = pPost;
189
+
190
+ /* Find the in-reply-to post. Default to the topic post if the replied-to
191
+ ** post cannot be found. */
192
+ if( firt ){
193
+ pPost->pIrt = pThread->pFirst;
194
+ for(p=pThread->pFirst; p; p=p->pNext){
195
+ if( p->fpid==firt ){
196
+ pPost->pIrt = p;
197
+ break;
198
+ }
199
+ }
200
+ }
201
+
202
+ /* Maintain the linked list of post edits. */
203
+ if( fprev ){
204
+ p = forumpost_backward(pPost->pPrev, fprev);
205
+ p->pEditNext = pPost;
206
+ pPost->sid = p->sid;
207
+ pPost->rev = p->rev+1;
208
+ pPost->nEdit = p->nEdit+1;
209
+ pPost->pEditPrev = p;
210
+ pPost->pEditHead = p->pEditHead ? p->pEditHead : p;
211
+ for(; p; p=p->pEditPrev ){
212
+ p->nEdit = pPost->nEdit;
213
+ p->pEditTail = pPost;
214
+ }
215
+ }
216
+ }
217
+ db_finalize(&q);
215218
216219
if( computeHierarchy ){
217220
/* Compute the hierarchical display order */
218
- pEntry = pThread->pFirst;
219
- pEntry->nIndent = 1;
221
+ pPost = pThread->pFirst;
222
+ pPost->nIndent = 1;
220223
pThread->mxIndent = 1;
221
- forumentry_add_to_display(pThread, pEntry);
222
- forumthread_display_order(pThread, pEntry);
224
+ forumpost_add_to_display(pThread, pPost);
225
+ forumthread_display_order(pThread, pPost);
223226
}
224227
225228
/* Return the result */
226229
return pThread;
227230
}
@@ -265,11 +268,11 @@
265268
void forumthread_cmd(void){
266269
int fpid;
267270
int froot;
268271
const char *zName;
269272
ForumThread *pThread;
270
- ForumEntry *p;
273
+ ForumPost *p;
271274
272275
db_find_and_open_repository(0,0);
273276
verify_all_options();
274277
if( g.argc==2 ){
275278
forum_thread_list();
@@ -293,21 +296,22 @@
293296
pThread = forumthread_create(froot, 1);
294297
fossil_print("Chronological:\n");
295298
fossil_print(
296299
/* 0 1 2 3 4 5 6 7 */
297300
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
298
- " sid fpid firt fprev mfirt pLeaf nReply hash\n");
301
+ " sid rev fpid pIrt pEditPrev pEditTail hash\n");
299302
for(p=pThread->pFirst; p; p=p->pNext){
300
- fossil_print("%4d %9d %9d %9d %9d %9d %6d %8.8s\n", p->sid,
301
- p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0,
302
- p->nReply, p->zUuid);
303
+ fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev,
304
+ p->fpid, p->pIrt ? p->pIrt->fpid : 0,
305
+ p->pEditPrev ? p->pEditPrev->fpid : 0,
306
+ p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
303307
}
304308
fossil_print("\nDisplay\n");
305309
for(p=pThread->pDisplay; p; p=p->pDisplay){
306310
fossil_print("%*s", (p->nIndent-1)*3, "");
307
- if( p->pLeaf ){
308
- fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid);
311
+ if( p->pEditTail ){
312
+ fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
309313
}else{
310314
fossil_print("%d\n", p->fpid);
311315
}
312316
}
313317
forumthread_delete(pThread);
@@ -352,28 +356,10 @@
352356
if( zClass ){
353357
@ </div>
354358
}
355359
}
356360
357
-/*
358
-** Generate the buttons in the display that allow a forum supervisor to
359
-** mark a user as trusted. Only do this if:
360
-**
361
-** (1) The poster is an individual, not a special user like "anonymous"
362
-** (2) The current user has Forum Supervisor privilege
363
-*/
364
-static void generateTrustControls(Manifest *pPost){
365
- if( !g.perm.AdminForum ) return;
366
- if( login_is_special(pPost->zUser) ) return;
367
- @ <br>
368
- @ <label><input type="checkbox" name="trust">
369
- @ Trust user "%h(pPost->zUser)"
370
- @ so that future posts by "%h(pPost->zUser)" do not require moderation.
371
- @ </label>
372
- @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
373
-}
374
-
375361
/*
376362
** Compute a display name from a login name.
377363
**
378364
** If the input login is found in the USER table, then check the USER.INFO
379365
** field to see if it has display-name followed by an email address.
@@ -403,413 +389,326 @@
403389
db_reset(&q);
404390
return zResult;
405391
}
406392
407393
/*
408
-** Display all posts in a forum thread in chronological order
394
+** Display a single post in a forum thread.
409395
*/
410
-static void forum_display_chronological(int froot, int target, int bRawMode){
411
- ForumThread *pThread = forumthread_create(froot, 0);
412
- ForumEntry *p;
413
- int notAnon = login_is_individual();
414
- char cMode = bRawMode ? 'r' : 'c';
415
- for(p=pThread->pFirst; p; p=p->pNext){
416
- char *zDate;
417
- Manifest *pPost;
418
- int isPrivate; /* True for posts awaiting moderation */
419
- int sameUser; /* True if author is also the reader */
420
- const char *zUuid;
421
- char *zDisplayName; /* The display name */
422
- int sid;
423
-
424
- pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
425
- if( pPost==0 ) continue;
426
- if( p->fpid==target ){
427
- @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
428
- }else if( p->pLeaf!=0 ){
429
- @ <div id="forum%d(p->fpid)" class="forumTime forumObs">
430
- }else{
431
- @ <div id="forum%d(p->fpid)" class="forumTime">
432
- }
433
- if( pPost->zThreadTitle ){
434
- @ <h1>%h(pPost->zThreadTitle)</h1>
435
- }
436
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
437
- zDisplayName = display_name_from_login(pPost->zUser);
438
- sid = p->pEdit ? p->pEdit->sid : p->sid;
439
- @ <h3 class='forumPostHdr'>(%d(sid)) By %h(zDisplayName) on %h(zDate)
396
+static void forum_display_post(
397
+ ForumPost *p, /* Forum post to display */
398
+ int iIndentScale, /* Indent scale factor */
399
+ int bRaw, /* True to omit the border */
400
+ int bUnf, /* True to leave the post unformatted */
401
+ int bHist, /* True if showing edit history */
402
+ int bSelect, /* True if this is the selected post */
403
+ char *zQuery /* Common query string */
404
+){
405
+ char *zDisplayName; /* The display name */
406
+ char *zDate; /* The time/date string */
407
+ char *zHist; /* History query string */
408
+ Manifest *pManifest; /* Manifest comprising the current post */
409
+ int bPrivate; /* True for posts awaiting moderation */
410
+ int bSameUser; /* True if author is also the reader */
411
+ int iIndent; /* Indent level */
412
+ const char *zMimetype;/* Formatting MIME type */
413
+
414
+ /* Get the manifest for the post. Abort if not found (e.g. shunned). */
415
+ pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
416
+ if( !pManifest ) return;
417
+
418
+ /* When not in raw mode, create the border around the post. */
419
+ if( !bRaw ){
420
+ /* Open the <div> enclosing the post. Set the class string to mark the post
421
+ ** as selected and/or obsolete. */
422
+ iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
423
+ @ <div id='forum%d(p->fpid)' class='forumTime\
424
+ @ %s(bSelect ? " forumSel" : "")\
425
+ @ %s(p->pEditTail ? " forumObs" : "")'\
426
+ if( iIndent && iIndentScale ){
427
+ @ style='margin-left: %d(iIndent*iIndentScale)ex'
428
+ }
429
+ @ >
430
+
431
+ /* If this is the first post (or an edit thereof), emit the thread title. */
432
+ if( pManifest->zThreadTitle ){
433
+ @ <h1>%h(pManifest->zThreadTitle)</h1>
434
+ }
435
+
436
+ /* Emit the serial number, revision number, author, and date. */
437
+ zDisplayName = display_name_from_login(pManifest->zUser);
438
+ zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate);
439
+ @ <h3 class='forumPostHdr'>(%d(p->sid)\
440
+ if( p->nEdit ){
441
+ @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
442
+ }
443
+ @ ) By %h(zDisplayName) on %h(zDate)
440444
fossil_free(zDisplayName);
441445
fossil_free(zDate);
442
- if( p->pEdit ){
443
- @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEdit->zUuid,cMode))\
444
- @ %d(p->pEdit->sid)</a>
446
+
447
+ /* If this is an edit, refer back to the old version. Be sure "hist" is in
448
+ ** the query string so the old version will actually be shown. */
449
+ if( p->pEditPrev ){
450
+ zHist = bHist ? "" : "&hist";
451
+ @ edit of \
452
+ @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
453
+ @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
445454
}
455
+
456
+ /* If debugging is enabled, link to the artifact page. */
446457
if( g.perm.Debug ){
447458
@ <span class="debug">\
448459
@ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
449460
}
450
- if( p->firt ){
451
- ForumEntry *pIrt = p->pPrev;
452
- while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
453
- if( pIrt ){
454
- @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
455
- @ %d(pIrt->sid)</a>
456
- }
457
- }
458
- zUuid = p->zUuid;
459
- if( p->pLeaf ){
460
- @ updated by %z(href("%R/forumpost/%S?t=%c",p->pLeaf->zUuid,cMode))\
461
- @ %d(p->pLeaf->sid)</a>
462
- zUuid = p->pLeaf->zUuid;
463
- }
464
- if( p->fpid!=target ){
465
- @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
466
- }
467
- if( !bRawMode ){
468
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
469
- }
470
- isPrivate = content_is_private(p->fpid);
471
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
472
- @ </h3>
473
- if( isPrivate && !g.perm.ModForum && !sameUser ){
474
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
475
- }else{
476
- const char *zMimetype;
477
- if( bRawMode ){
478
- zMimetype = "text/plain";
479
- }else if( p->pLeaf!=0 ){
480
- zMimetype = "text/plain";
481
- }else{
482
- zMimetype = pPost->zMimetype;
483
- }
484
- forum_render(0, zMimetype, pPost->zWiki, 0, 1);
485
- }
486
- if( g.perm.WrForum && p->pLeaf==0 ){
487
- int sameUser = login_is_individual()
488
- && fossil_strcmp(pPost->zUser, g.zLogin)==0;
461
+
462
+ /* If this is a reply, refer back to the parent post. */
463
+ if( p->pIrt ){
464
+ @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\
465
+ @ %d(p->pIrt->sid)\
466
+ if( p->pIrt->nEdit ){
467
+ @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
468
+ }
469
+ @ </a>
470
+ }
471
+
472
+ /* If this post was later edited, refer forward to the next edit. */
473
+ if( p->pEditNext ){
474
+ @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\
475
+ @ %d(p->pEditNext->sid)\
476
+ @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a>
477
+ }
478
+
479
+ /* Provide a link to select the individual post. */
480
+ if( !bSelect ){
481
+ @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a>
482
+ }
483
+
484
+ /* Provide a link to the raw source code. */
485
+ if( !bUnf ){
486
+ @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a>
487
+ }
488
+ @ </h3>
489
+ }
490
+
491
+ /* Check if this post is approved, also if it's by the current user. */
492
+ bPrivate = content_is_private(p->fpid);
493
+ bSameUser = login_is_individual()
494
+ && fossil_strcmp(pManifest->zUser, g.zLogin)==0;
495
+
496
+ /* Render the post if the user is able to see it. */
497
+ if( bPrivate && !g.perm.ModForum && !bSameUser ){
498
+ @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
499
+ }else{
500
+ if( bRaw || bUnf || p->pEditTail ){
501
+ zMimetype = "text/plain";
502
+ }else{
503
+ zMimetype = pManifest->zMimetype;
504
+ }
505
+ forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw);
506
+ }
507
+
508
+ /* When not in raw mode, finish creating the border around the post. */
509
+ if( !bRaw ){
510
+ /* If the user is able to write to the forum and if this post has not been
511
+ ** edited, create a form with various interaction buttons. */
512
+ if( g.perm.WrForum && !p->pEditTail ){
489513
@ <div><form action="%R/forumedit" method="POST">
490514
@ <input type="hidden" name="fpid" value="%s(p->zUuid)">
491
- if( !isPrivate ){
492
- /* Reply and Edit are only available if the post has already
493
- ** been approved */
515
+ if( !bPrivate ){
516
+ /* Reply and Edit are only available if the post has been approved. */
494517
@ <input type="submit" name="reply" value="Reply">
495
- if( g.perm.Admin || sameUser ){
518
+ if( g.perm.Admin || bSameUser ){
496519
@ <input type="submit" name="edit" value="Edit">
497520
@ <input type="submit" name="nullout" value="Delete">
498521
}
499522
}else if( g.perm.ModForum ){
500
- /* Provide moderators with moderation buttons for posts that
501
- ** are pending moderation */
523
+ /* Allow moderators to approve or reject pending posts. Also allow
524
+ ** forum supervisors to mark non-special users as trusted and therefore
525
+ ** able to post unmoderated. */
502526
@ <input type="submit" name="approve" value="Approve">
503527
@ <input type="submit" name="reject" value="Reject">
504
- generateTrustControls(pPost);
505
- }else if( sameUser ){
506
- /* A post that is pending moderation can be deleted by the
507
- ** person who originally submitted the post */
528
+ if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){
529
+ @ <br><label><input type="checkbox" name="trust">
530
+ @ Trust user "%h(pManifest->zUser)" so that future posts by \
531
+ @ "%h(pManifest->zUser)" do not require moderation.
532
+ @ </label>
533
+ @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)">
534
+ }
535
+ }else if( bSameUser ){
536
+ /* Allow users to delete (reject) their own pending posts. */
508537
@ <input type="submit" name="reject" value="Delete">
509538
}
510539
@ </form></div>
511540
}
512
- manifest_destroy(pPost);
513541
@ </div>
514542
}
515543
516
- /* Undocumented "threadtable" query parameter causes thread table
517
- ** to be displayed for debugging purposes.
518
- */
544
+ /* Clean up. */
545
+ manifest_destroy(pManifest);
546
+}
547
+
548
+/*
549
+** Possible display modes for forum_display_thread().
550
+*/
551
+enum {
552
+ FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force
553
+ ** unformatted mode, and inhibit history mode */
554
+ FD_SINGLE, /* Render a single post and (optionally) its edit history */
555
+ FD_CHRONO, /* Render all posts in chronological order */
556
+ FD_HIER, /* Render all posts in an indented hierarchy */
557
+};
558
+
559
+/*
560
+** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a
561
+** single post from the thread and (optionally) its edit history.
562
+*/
563
+static void forum_display_thread(
564
+ int froot, /* Forum thread root post ID */
565
+ int fpid, /* Selected forum post ID, or 0 if none selected */
566
+ int mode, /* Forum display mode, one of the FD_* enumerations */
567
+ int bUnf, /* True if rendering unformatted */
568
+ int bHist /* True if showing edit history, ignored for FD_RAW */
569
+){
570
+ ForumThread *pThread; /* Thread structure */
571
+ ForumPost *pSelect; /* Currently selected post, or NULL if none */
572
+ ForumPost *p; /* Post iterator pointer */
573
+ char *zQuery; /* Common query string */
574
+ int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */
575
+ int sid; /* Comparison serial ID */
576
+
577
+ /* In raw mode, force unformatted display and disable history. */
578
+ if( mode == FD_RAW ){
579
+ bUnf = 1;
580
+ bHist = 0;
581
+ }
582
+
583
+ /* Thread together the posts and (optionally) compute the hierarchy. */
584
+ pThread = forumthread_create(froot, mode==FD_HIER);
585
+
586
+ /* Compute the appropriate indent scaling. */
587
+ if( mode==FD_HIER ){
588
+ iIndentScale = 4;
589
+ while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
590
+ iIndentScale--;
591
+ }
592
+ }else{
593
+ iIndentScale = 0;
594
+ }
595
+
596
+ /* Find the selected post, or (depending on parameters) its latest edit. */
597
+ pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0;
598
+ if( !bHist && mode!=FD_RAW && pSelect && pSelect->pEditTail ){
599
+ pSelect = pSelect->pEditTail;
600
+ }
601
+
602
+ /* When displaying only a single post, abort if no post was selected or the
603
+ ** selected forum post does not exist in the thread. Otherwise proceed to
604
+ ** display the entire thread without marking any posts as selected. */
605
+ if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){
606
+ return;
607
+ }
608
+
609
+ /* Create the common query string to append to nearly all post links. */
610
+ zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s",
611
+ mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h',
612
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
613
+
614
+ /* Identify which post to display first. If history is shown, start with the
615
+ ** original, unedited post. Otherwise advance to the post's latest edit. */
616
+ if( mode==FD_RAW || mode==FD_SINGLE ){
617
+ p = pSelect;
618
+ if( bHist && p->pEditHead ) p = p->pEditHead;
619
+ }else{
620
+ p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
621
+ if( !bHist && p->pEditTail ) p = p->pEditTail;
622
+ }
623
+
624
+ /* Display the appropriate subset of posts in sequence. */
625
+ while( p ){
626
+ /* Display the post. */
627
+ forum_display_post(p, iIndentScale, mode==FD_RAW,
628
+ bUnf, bHist, p==pSelect, zQuery);
629
+
630
+ /* Advance to the next post in the thread. */
631
+ if( mode==FD_CHRONO ){
632
+ /* Chronological mode: display posts (optionally including edits) in their
633
+ ** original commit order. */
634
+ if( bHist ){
635
+ p = p->pNext;
636
+ }else{
637
+ sid = p->sid;
638
+ if( p->pEditHead ) p = p->pEditHead;
639
+ do p = p->pNext; while( p && p->sid<=sid );
640
+ if( p && p->pEditTail ) p = p->pEditTail;
641
+ }
642
+ }else if( bHist && p->pEditNext ){
643
+ /* Hierarchical and single mode: display each post's edits in sequence. */
644
+ p = p->pEditNext;
645
+ }else if( mode==FD_HIER ){
646
+ /* Hierarchical mode: after displaying with each post (optionally
647
+ ** including edits), go to the next post in computed display order. */
648
+ p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay;
649
+ if( !bHist && p && p->pEditTail ) p = p->pEditTail;
650
+ }else{
651
+ /* Single and raw mode: terminate after displaying the selected post and
652
+ ** (optionally) its edits. */
653
+ break;
654
+ }
655
+ }
656
+
657
+ /* Undocumented "threadtable" query parameter causes thread table to be
658
+ ** displayed for debugging purposes. */
519659
if( PB("threadtable") ){
520660
@ <hr>
521661
@ <table border="1" cellpadding="3" cellspacing="0">
522
- @ <tr><th>sid<th>fpid<th>firt<th>fprev<th>mfirt<th>pLeaf<th>nReply<th>hash
662
+ @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
663
+ @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash
523664
for(p=pThread->pFirst; p; p=p->pNext){
524
- @ <tr><td>%d(p->sid)<td>%d(p->fpid)<td>%d(p->firt)\
525
- @ <td>%d(p->fprev)<td>%d(p->mfirt)\
526
- @ <td>%d(p->pLeaf?p->pLeaf->fpid:0)<td>%d(p->nReply)\
665
+ @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
666
+ @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\
667
+ @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\
668
+ @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\
669
+ @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\
670
+ @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\
671
+ @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\
527672
@ <td>%S(p->zUuid)</tr>
528673
}
529674
@ </table>
530675
}
531676
532
- forumthread_delete(pThread);
533
-}
534
-/*
535
-** Display all the edit history of post "target".
536
-*/
537
-static void forum_display_history(int froot, int target, int bRawMode){
538
- ForumThread *pThread = forumthread_create(froot, 0);
539
- ForumEntry *p;
540
- int notAnon = login_is_individual();
541
- char cMode = bRawMode ? 'r' : 'c';
542
- ForumEntry *pLeaf = 0;
543
- int cnt = 0;
544
- for(p=pThread->pFirst; p; p=p->pNext){
545
- if( p->fpid==target ){
546
- pLeaf = p->pLeaf ? p->pLeaf : p;
547
- break;
548
- }
549
- }
550
- for(p=pThread->pFirst; p; p=p->pNext){
551
- char *zDate;
552
- Manifest *pPost;
553
- int isPrivate; /* True for posts awaiting moderation */
554
- int sameUser; /* True if author is also the reader */
555
- const char *zUuid;
556
- char *zDisplayName; /* The display name */
557
-
558
- if( p->fpid!=pLeaf->fpid && p->pLeaf!=pLeaf ) continue;
559
- cnt++;
560
- pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
561
- if( pPost==0 ) continue;
562
- @ <div id="forum%d(p->fpid)" class="forumTime">
563
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
564
- zDisplayName = display_name_from_login(pPost->zUser);
565
- @ <h3 class='forumPostHdr'>(%d(p->sid)) By %h(zDisplayName) on %h(zDate)
566
- fossil_free(zDisplayName);
567
- fossil_free(zDate);
568
- if( g.perm.Debug ){
569
- @ <span class="debug">\
570
- @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
571
- }
572
- if( p->firt && cnt==1 ){
573
- ForumEntry *pIrt = p->pPrev;
574
- while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
575
- if( pIrt ){
576
- @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
577
- @ %d(pIrt->sid)</a>
578
- }
579
- }
580
- zUuid = p->zUuid;
581
- @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a>
582
- if( !bRawMode ){
583
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
584
- }
585
- isPrivate = content_is_private(p->fpid);
586
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
587
- @ </h3>
588
- if( isPrivate && !g.perm.ModForum && !sameUser ){
589
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
590
- }else{
591
- forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki,
592
- 0, 1);
593
- }
594
- if( g.perm.WrForum && p->pLeaf==0 ){
595
- int sameUser = login_is_individual()
596
- && fossil_strcmp(pPost->zUser, g.zLogin)==0;
597
- @ <div><form action="%R/forumedit" method="POST">
598
- @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
599
- if( !isPrivate ){
600
- /* Reply and Edit are only available if the post has already
601
- ** been approved */
602
- @ <input type="submit" name="reply" value="Reply">
603
- if( g.perm.Admin || sameUser ){
604
- @ <input type="submit" name="edit" value="Edit">
605
- @ <input type="submit" name="nullout" value="Delete">
606
- }
607
- }else if( g.perm.ModForum ){
608
- /* Provide moderators with moderation buttons for posts that
609
- ** are pending moderation */
610
- @ <input type="submit" name="approve" value="Approve">
611
- @ <input type="submit" name="reject" value="Reject">
612
- generateTrustControls(pPost);
613
- }else if( sameUser ){
614
- /* A post that is pending moderation can be deleted by the
615
- ** person who originally submitted the post */
616
- @ <input type="submit" name="reject" value="Delete">
617
- }
618
- @ </form></div>
619
- }
620
- manifest_destroy(pPost);
621
- @ </div>
622
- }
623
- forumthread_delete(pThread);
624
-}
625
-
626
-/*
627
-** Display all messages in a forumthread with indentation.
628
-*/
629
-static int forum_display_hierarchical(int froot, int target){
630
- ForumThread *pThread;
631
- ForumEntry *p;
632
- Manifest *pPost, *pOPost;
633
- int fpid;
634
- const char *zUuid;
635
- char *zDate;
636
- const char *zSel;
637
- int notAnon = login_is_individual();
638
- int iIndentScale = 4;
639
-
640
- pThread = forumthread_create(froot, 1);
641
- for(p=pThread->pFirst; p; p=p->pNext){
642
- if( p->fpid==target ){
643
- while( p->pEdit ) p = p->pEdit;
644
- target = p->fpid;
645
- break;
646
- }
647
- }
648
- while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
649
- iIndentScale--;
650
- }
651
- for(p=pThread->pDisplay; p; p=p->pDisplay){
652
- int isPrivate; /* True for posts awaiting moderation */
653
- int sameUser; /* True if reader is also the poster */
654
- char *zDisplayName; /* User name to be displayed */
655
- pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
656
- if( p->pLeaf ){
657
- fpid = p->pLeaf->fpid;
658
- zUuid = p->pLeaf->zUuid;
659
- pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
660
- }else{
661
- fpid = p->fpid;
662
- zUuid = p->zUuid;
663
- pPost = pOPost;
664
- }
665
- zSel = p->fpid==target ? " forumSel" : "";
666
- if( p->nIndent==1 ){
667
- @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
668
- }else{
669
- @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
670
- @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
671
- }
672
- if( pPost==0 ) continue;
673
- if( pPost->zThreadTitle ){
674
- @ <h1>%h(pPost->zThreadTitle)</h1>
675
- }
676
- zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
677
- zDisplayName = display_name_from_login(pOPost->zUser);
678
- @ <h3 class='forumPostHdr'>\
679
- @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate)
680
- fossil_free(zDisplayName);
681
- fossil_free(zDate);
682
- if( g.perm.Debug ){
683
- @ <span class="debug">\
684
- @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
685
- }
686
- if( p->pLeaf ){
687
- zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
688
- if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
689
- @ and edited on %h(zDate)
690
- }else{
691
- @ as edited by %h(pPost->zUser) on %h(zDate)
692
- }
693
- fossil_free(zDate);
694
- if( g.perm.Debug ){
695
- @ <span class="debug">\
696
- @ <a href="%R/artifact/%h(p->pLeaf->zUuid)">\
697
- @ (artifact-%d(p->pLeaf->fpid))</a></span>
698
- }
699
- @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a>
700
- manifest_destroy(pOPost);
701
- }
702
- if( fpid!=target ){
703
- @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
704
- }
705
- @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
706
- if( p->firt ){
707
- ForumEntry *pIrt = p->pPrev;
708
- while( pIrt && pIrt->fpid!=p->mfirt ) pIrt = pIrt->pPrev;
709
- if( pIrt ){
710
- @ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
711
- @ %d(pIrt->sid)</a>
712
- }
713
- }
714
- @ </h3>
715
- isPrivate = content_is_private(fpid);
716
- sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
717
- if( isPrivate && !g.perm.ModForum && !sameUser ){
718
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
719
- }else{
720
- forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1);
721
- }
722
- if( g.perm.WrForum ){
723
- @ <div><form action="%R/forumedit" method="POST">
724
- @ <input type="hidden" name="fpid" value="%s(zUuid)">
725
- if( !isPrivate ){
726
- /* Reply and Edit are only available if the post has already
727
- ** been approved */
728
- @ <input type="submit" name="reply" value="Reply">
729
- if( g.perm.Admin || sameUser ){
730
- @ <input type="submit" name="edit" value="Edit">
731
- @ <input type="submit" name="nullout" value="Delete">
732
- }
733
- }else if( g.perm.ModForum ){
734
- /* Provide moderators with moderation buttons for posts that
735
- ** are pending moderation */
736
- @ <input type="submit" name="approve" value="Approve">
737
- @ <input type="submit" name="reject" value="Reject">
738
- generateTrustControls(pPost);
739
- }else if( sameUser ){
740
- /* A post that is pending moderation can be deleted by the
741
- ** person who originally submitted the post */
742
- @ <input type="submit" name="reject" value="Delete">
743
- }
744
- @ </form></div>
745
- }
746
- manifest_destroy(pPost);
747
- @ </div>
748
- }
749
- forumthread_delete(pThread);
750
- return target;
751
-}
752
-
753
-/*
754
-** Emits all JS code required by /forumpost.
755
-*/
756
-static void forumpost_emit_page_js(){
757
- static int once = 0;
758
- if(0==once){
759
- once = 1;
760
- style_emit_script_fossil_bootstrap(1);
761
- builtin_request_js("forum.js");
762
- builtin_request_js("fossil.dom.js");
763
- builtin_request_js("fossil.page.forumpost.js");
764
- }
677
+ /* Clean up. */
678
+ forumthread_delete(pThread);
679
+ fossil_free(zQuery);
765680
}
766681
767682
/*
768683
** WEBPAGE: forumpost
769684
**
770685
** Show a single forum posting. The posting is shown in context with
771
-** it's entire thread. The selected posting is enclosed within
686
+** its entire thread. The selected posting is enclosed within
772687
** <div class='forumSel'>...</div>. Javascript is used to move the
773688
** selected posting into view after the page loads.
774689
**
775690
** Query parameters:
776691
**
777
-** name=X REQUIRED. The hash of the post to display
778
-** t=MODE Display mode.
779
-** 'c' for chronological
780
-** 'h' for hierarchical
781
-** 'a' for automatic
782
-** 'r' for raw
783
-** 'y' for history of post X only
784
-** raw If present, show only the post specified and
785
-** show its original unformatted source text.
692
+** name=X REQUIRED. The hash of the post to display.
693
+** t=a Automatic display mode, i.e. hierarchical for
694
+** desktop and chronological for mobile. This is the
695
+** default if the "t" query parameter is omitted.
696
+** t=c Show posts in the order they were written.
697
+** t=h Show posts usin hierarchical indenting.
698
+** t=s Show only the post specified by "name=X".
699
+** t=r Alias for "t=c&unf&hist".
700
+** t=y Alias for "t=s&unf&hist".
701
+** raw Alias for "t=s&unf". Additionally, omit the border
702
+** around the post, and ignore "t" and "hist".
703
+** unf Show the original, unformatted source text.
704
+** hist Show edit history in addition to current posts.
786705
*/
787706
void forumpost_page(void){
788707
forumthread_page();
789708
}
790709
791
-/*
792
-** Add an appropriate style_header() to include title of the
793
-** given forum post.
794
-*/
795
-static int forumthread_page_header(int froot, int fpid){
796
- char *zThreadTitle = 0;
797
-
798
- zThreadTitle = db_text("",
799
- "SELECT"
800
- " substr(event.comment,instr(event.comment,':')+2)"
801
- " FROM forumpost, event"
802
- " WHERE event.objid=forumpost.fpid"
803
- " AND forumpost.fpid=%d;",
804
- fpid
805
- );
806
- style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
807
- fossil_free(zThreadTitle);
808
- return 0;
809
-}
810
-
811710
/*
812711
** WEBPAGE: forumthread
813712
**
814713
** Show all forum messages associated with a particular message thread.
815714
** The result is basically the same as /forumpost except that none of
@@ -816,24 +715,28 @@
816715
** the postings in the thread are selected.
817716
**
818717
** Query parameters:
819718
**
820719
** name=X REQUIRED. The hash of any post of the thread.
821
-** t=MODE Display mode. MODE is...
822
-** 'c' for chronological, or
823
-** 'h' for hierarchical, or
824
-** 'a' for automatic, or
825
-** 'r' for raw.
826
-** raw Show only the post given by name= and show it unformatted
827
-** hist Show only the edit history for the name= post
720
+** t=a Automatic display mode, i.e. hierarchical for
721
+** desktop and chronological for mobile. This is the
722
+** default if the "t" query parameter is omitted.
723
+** t=c Show posts in the order they were written.
724
+** t=h Show posts using hierarchical indenting.
725
+** unf Show the original, unformatted source text.
726
+** hist Show edit history in addition to current posts.
828727
*/
829728
void forumthread_page(void){
830729
int fpid;
831730
int froot;
731
+ char *zThreadTitle;
832732
const char *zName = P("name");
833733
const char *zMode = PD("t","a");
834734
int bRaw = PB("raw");
735
+ int bUnf = PB("unf");
736
+ int bHist = PB("hist");
737
+ int mode = 0;
835738
login_check_credentials();
836739
if( !g.perm.RdForum ){
837740
login_needed(g.anon.RdForum);
838741
return;
839742
}
@@ -847,54 +750,70 @@
847750
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
848751
if( froot==0 ){
849752
webpage_error("Not a forum post: \"%s\"", zName);
850753
}
851754
if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
852
- if( zMode[0]=='a' ){
853
- if( cgi_from_mobile() ){
854
- zMode = "c"; /* Default to chronological on mobile */
855
- }else{
856
- zMode = "h";
857
- }
858
- }
859
- if( zMode[0]!='y' ){
860
- forumthread_page_header(froot, fpid);
861
- }
862
- if( bRaw && fpid ){
863
- Manifest *pPost;
864
- pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
865
- if( pPost==0 ){
866
- @ <p>No such forum post: %h(zName)
867
- }else{
868
- int isPrivate = content_is_private(fpid);
869
- int notAnon = login_is_individual();
870
- int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
871
- if( isPrivate && !g.perm.ModForum && !sameUser ){
872
- @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
873
- }else{
874
- forum_render(0, "text/plain", pPost->zWiki, 0, 0);
875
- }
876
- manifest_destroy(pPost);
877
- }
878
- }else if( zMode[0]=='c' ){
879
- style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
880
- style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
881
- forum_display_chronological(froot, fpid, 0);
882
- }else if( zMode[0]=='r' ){
883
- style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
884
- style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
885
- forum_display_chronological(froot, fpid, 1);
886
- }else if( zMode[0]=='y' ){
887
- style_header("Edit History Of A Forum Post");
888
- style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName);
889
- forum_display_history(froot, fpid, 1);
890
- }else{
891
- style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
892
- style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
893
- forum_display_hierarchical(froot, fpid);
894
- }
895
- forumpost_emit_page_js();
755
+
756
+ /* Decode the mode parameters. */
757
+ if( bRaw ){
758
+ mode = FD_RAW;
759
+ bUnf = 1;
760
+ bHist = 0;
761
+ cgi_replace_query_parameter("unf", "on");
762
+ cgi_delete_query_parameter("hist");
763
+ cgi_delete_query_parameter("raw");
764
+ }else{
765
+ switch( *zMode ){
766
+ case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break;
767
+ case 'c': mode = FD_CHRONO; break;
768
+ case 'h': mode = FD_HIER; break;
769
+ case 's': mode = FD_SINGLE; break;
770
+ case 'r': mode = FD_CHRONO; break;
771
+ case 'y': mode = FD_SINGLE; break;
772
+ default: webpage_error("Invalid thread mode: \"%s\"", zMode);
773
+ }
774
+ if( *zMode=='r' || *zMode=='y') {
775
+ bUnf = 1;
776
+ bHist = 1;
777
+ cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s");
778
+ cgi_replace_query_parameter("unf", "on");
779
+ cgi_replace_query_parameter("hist", "on");
780
+ }
781
+ }
782
+
783
+ /* Define the page header. */
784
+ zThreadTitle = db_text("",
785
+ "SELECT"
786
+ " substr(event.comment,instr(event.comment,':')+2)"
787
+ " FROM forumpost, event"
788
+ " WHERE event.objid=forumpost.fpid"
789
+ " AND forumpost.fpid=%d;",
790
+ fpid
791
+ );
792
+ style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum");
793
+ fossil_free(zThreadTitle);
794
+ if( mode!=FD_CHRONO ){
795
+ style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName,
796
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
797
+ }
798
+ if( mode!=FD_HIER ){
799
+ style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName,
800
+ bUnf ? "&unf" : "", bHist ? "&hist" : "");
801
+ }
802
+ style_submenu_checkbox("unf", "Unformatted", 0, 0);
803
+ style_submenu_checkbox("hist", "History", 0, 0);
804
+
805
+ /* Display the thread. */
806
+ forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
+
808
+ /* Emit Forum Javascript. */
809
+ style_emit_script_fossil_bootstrap(1);
810
+ builtin_request_js("forum.js");
811
+ builtin_request_js("fossil.dom.js");
812
+ builtin_request_js("fossil.page.forumpost.js");
813
+
814
+ /* Emit the page style. */
896815
style_footer();
897816
}
898817
899818
/*
900819
** Return true if a forum post should be moderated.
@@ -949,11 +868,11 @@
949868
webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 );
950869
blob_init(&x, 0, 0);
951870
zDate = date_in_standard_format("now");
952871
blob_appendf(&x, "D %s\n", zDate);
953872
fossil_free(zDate);
954
- zG = db_text(0,
873
+ zG = db_text(0,
955874
"SELECT uuid FROM blob, forumpost"
956875
" WHERE blob.rid==forumpost.froot"
957876
" AND forumpost.fpid=%d", iBasis);
958877
if( zG ){
959878
blob_appendf(&x, "G %s\n", zG);
@@ -1017,11 +936,11 @@
1017936
}
1018937
1019938
/*
1020939
** Paint the form elements for entering a Forum post
1021940
*/
1022
-static void forum_entry_widget(
941
+static void forum_post_widget(
1023942
const char *zTitle,
1024943
const char *zMimetype,
1025944
const char *zContent
1026945
){
1027946
if( zTitle ){
@@ -1126,11 +1045,11 @@
11261045
}
11271046
style_header("New Forum Thread");
11281047
@ <form action="%R/forume1" method="POST">
11291048
@ <h1>New Thread:</h1>
11301049
forum_from_line();
1131
- forum_entry_widget(zTitle, zMimetype, zContent);
1050
+ forum_post_widget(zTitle, zMimetype, zContent);
11321051
@ <input type="submit" name="preview" value="Preview">
11331052
if( P("preview") && !whitespace_only(zContent) ){
11341053
@ <input type="submit" name="submit" value="Submit">
11351054
}else{
11361055
@ <input type="submit" name="submit" value="Submit" disabled>
@@ -1195,19 +1114,21 @@
11951114
moderation_approve('f', fpid);
11961115
if( g.perm.AdminForum
11971116
&& PB("trust")
11981117
&& (zUserToTrust = P("trustuser"))!=0
11991118
){
1119
+ db_unprotect(PROTECT_USER);
12001120
db_multi_exec("UPDATE user SET cap=cap||'4' "
12011121
"WHERE login=%Q AND cap NOT GLOB '*4*'",
12021122
zUserToTrust);
1123
+ db_protect_pop();
12031124
}
12041125
cgi_redirectf("%R/forumpost/%S",P("fpid"));
12051126
return;
12061127
}
12071128
if( P("reject") ){
1208
- char *zParent =
1129
+ char *zParent =
12091130
db_text(0,
12101131
"SELECT uuid FROM forumpost, blob"
12111132
" WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt",
12121133
fpid
12131134
);
@@ -1276,11 +1197,11 @@
12761197
@ <h2>Revised Message:</h2>
12771198
@ <form action="%R/forume2" method="POST">
12781199
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
12791200
@ <input type="hidden" name="edit" value="1">
12801201
forum_from_line();
1281
- forum_entry_widget(zTitle, zMimetype, zContent);
1202
+ forum_post_widget(zTitle, zMimetype, zContent);
12821203
}else{
12831204
/* Reply */
12841205
char *zDisplayName;
12851206
zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
12861207
zContent = PDT("content","");
@@ -1302,11 +1223,11 @@
13021223
@ <h2>Enter Reply:</h2>
13031224
@ <form action="%R/forume2" method="POST">
13041225
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
13051226
@ <input type="hidden" name="reply" value="1">
13061227
forum_from_line();
1307
- forum_entry_widget(0, zMimetype, zContent);
1228
+ forum_post_widget(0, zMimetype, zContent);
13081229
}
13091230
if( !isDelete ){
13101231
@ <input type="submit" name="preview" value="Preview">
13111232
}
13121233
@ <input type="submit" name="cancel" value="Cancel">
13131234
--- src/forum.c
+++ src/forum.c
@@ -26,44 +26,45 @@
26 */
27 #define DEFAULT_FORUM_MIMETYPE "text/x-markdown"
28
29 #if INTERFACE
30 /*
31 ** Each instance of the following object represents a single message -
32 ** either the initial post, an edit to a post, a reply, or an edit to
33 ** a reply.
34 */
35 struct ForumEntry {
36 int fpid; /* rid for this entry */
37 int fprev; /* zero if initial entry. non-zero if an edit */
38 int firt; /* This entry replies to firt */
39 int mfirt; /* Root in-reply-to */
40 int nReply; /* Number of replies to this entry */
41 int sid; /* Serial ID number */
 
42 char *zUuid; /* Artifact hash */
43 ForumEntry *pLeaf; /* Most recent edit for this entry */
44 ForumEntry *pEdit; /* This entry is an edit of pEdit */
45 ForumEntry *pNext; /* Next in chronological order */
46 ForumEntry *pPrev; /* Previous in chronological order */
47 ForumEntry *pDisplay; /* Next in display order */
48 int nIndent; /* Number of levels of indentation for this entry */
 
 
 
 
49 };
50
51 /*
52 ** A single instance of the following tracks all entries for a thread.
53 */
54 struct ForumThread {
55 ForumEntry *pFirst; /* First entry in chronological order */
56 ForumEntry *pLast; /* Last entry in chronological order */
57 ForumEntry *pDisplay; /* Entries in display order */
58 ForumEntry *pTail; /* Last on the display list */
59 int mxIndent; /* Maximum indentation level */
60 };
61 #endif /* INTERFACE */
62
63 /*
64 ** Return true if the forum entry with the given rid has been
65 ** subsequently edited.
66 */
67 int forum_rid_has_been_edited(int rid){
68 static Stmt q;
69 int res;
@@ -79,41 +80,39 @@
79
80 /*
81 ** Delete a complete ForumThread and all its entries.
82 */
83 static void forumthread_delete(ForumThread *pThread){
84 ForumEntry *pEntry, *pNext;
85 for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
86 pNext = pEntry->pNext;
87 fossil_free(pEntry->zUuid);
88 fossil_free(pEntry);
89 }
90 fossil_free(pThread);
91 }
92
93 #if 0 /* not used */
94 /*
95 ** Search a ForumEntry list forwards looking for the entry with fpid
96 */
97 static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){
98 while( p && p->fpid!=fpid ) p = p->pNext;
99 return p;
100 }
101 #endif
102
103 /*
104 ** Search backwards for a ForumEntry
105 */
106 static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){
107 while( p && p->fpid!=fpid ) p = p->pPrev;
108 return p;
109 }
110
111 /*
112 ** Add an entry to the display list
113 */
114 static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){
115 if( pThread->pDisplay==0 ){
116 pThread->pDisplay = p;
117 }else{
118 pThread->pTail->pDisplay = p;
119 }
@@ -120,108 +119,112 @@
120 pThread->pTail = p;
121 }
122
123 /*
124 ** Extend the display list for pThread by adding all entries that
125 ** reference fpid. The first such entry will be no earlier then
126 ** entry "p".
127 */
128 static void forumthread_display_order(
129 ForumThread *pThread, /* The complete thread */
130 ForumEntry *pBase /* Add replies to this entry */
131 ){
132 ForumEntry *p;
133 ForumEntry *pPrev = 0;
 
134 for(p=pBase->pNext; p; p=p->pNext){
135 if( p->fprev==0 && p->mfirt==pBase->fpid ){
136 if( pPrev ){
137 pPrev->nIndent = pBase->nIndent + 1;
138 forumentry_add_to_display(pThread, pPrev);
139 forumthread_display_order(pThread, pPrev);
140 }
141 pBase->nReply++;
142 pPrev = p;
 
 
143 }
144 }
145 if( pPrev ){
146 pPrev->nIndent = pBase->nIndent + 1;
147 if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent;
148 forumentry_add_to_display(pThread, pPrev);
149 forumthread_display_order(pThread, pPrev);
150 }
151 }
152
153 /*
154 ** Construct a ForumThread object given the root record id.
155 */
156 static ForumThread *forumthread_create(int froot, int computeHierarchy){
157 ForumThread *pThread;
158 ForumEntry *pEntry;
 
159 Stmt q;
160 int sid = 1;
161 Bag seen = Bag_INIT;
162 pThread = fossil_malloc( sizeof(*pThread) );
163 memset(pThread, 0, sizeof(*pThread));
164 db_prepare(&q,
165 "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
166 " FROM forumpost"
167 " WHERE froot=%d ORDER BY fmtime",
168 froot
169 );
170 while( db_step(&q)==SQLITE_ROW ){
171 pEntry = fossil_malloc( sizeof(*pEntry) );
172 memset(pEntry, 0, sizeof(*pEntry));
173 pEntry->fpid = db_column_int(&q, 0);
174 pEntry->firt = db_column_int(&q, 1);
175 pEntry->fprev = db_column_int(&q, 2);
176 pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
177 pEntry->mfirt = pEntry->firt;
178 pEntry->sid = sid++;
179 pEntry->pPrev = pThread->pLast;
180 pEntry->pNext = 0;
181 bag_insert(&seen, pEntry->fpid);
182 if( pThread->pLast==0 ){
183 pThread->pFirst = pEntry;
184 }else{
185 pThread->pLast->pNext = pEntry;
186 }
187 if( pEntry->firt && !bag_find(&seen,pEntry->firt) ){
188 pEntry->firt = froot;
189 pEntry->mfirt = froot;
190 }
191 pThread->pLast = pEntry;
192 }
193 db_finalize(&q);
194 bag_clear(&seen);
195
196 /* Establish which entries are the latest edit. After this loop
197 ** completes, entries that have non-NULL pLeaf should not be
198 ** displayed.
199 */
200 for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
201 if( pEntry->fprev ){
202 ForumEntry *pBase = 0, *p;
203 p = forumentry_backward(pEntry->pPrev, pEntry->fprev);
204 pEntry->pEdit = p;
205 while( p ){
206 pBase = p;
207 p->pLeaf = pEntry;
208 p = pBase->pEdit;
209 }
210 for(p=pEntry->pNext; p; p=p->pNext){
211 if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->fpid;
212 }
213 }
214 }
 
 
215
216 if( computeHierarchy ){
217 /* Compute the hierarchical display order */
218 pEntry = pThread->pFirst;
219 pEntry->nIndent = 1;
220 pThread->mxIndent = 1;
221 forumentry_add_to_display(pThread, pEntry);
222 forumthread_display_order(pThread, pEntry);
223 }
224
225 /* Return the result */
226 return pThread;
227 }
@@ -265,11 +268,11 @@
265 void forumthread_cmd(void){
266 int fpid;
267 int froot;
268 const char *zName;
269 ForumThread *pThread;
270 ForumEntry *p;
271
272 db_find_and_open_repository(0,0);
273 verify_all_options();
274 if( g.argc==2 ){
275 forum_thread_list();
@@ -293,21 +296,22 @@
293 pThread = forumthread_create(froot, 1);
294 fossil_print("Chronological:\n");
295 fossil_print(
296 /* 0 1 2 3 4 5 6 7 */
297 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
298 " sid fpid firt fprev mfirt pLeaf nReply hash\n");
299 for(p=pThread->pFirst; p; p=p->pNext){
300 fossil_print("%4d %9d %9d %9d %9d %9d %6d %8.8s\n", p->sid,
301 p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0,
302 p->nReply, p->zUuid);
 
303 }
304 fossil_print("\nDisplay\n");
305 for(p=pThread->pDisplay; p; p=p->pDisplay){
306 fossil_print("%*s", (p->nIndent-1)*3, "");
307 if( p->pLeaf ){
308 fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid);
309 }else{
310 fossil_print("%d\n", p->fpid);
311 }
312 }
313 forumthread_delete(pThread);
@@ -352,28 +356,10 @@
352 if( zClass ){
353 @ </div>
354 }
355 }
356
357 /*
358 ** Generate the buttons in the display that allow a forum supervisor to
359 ** mark a user as trusted. Only do this if:
360 **
361 ** (1) The poster is an individual, not a special user like "anonymous"
362 ** (2) The current user has Forum Supervisor privilege
363 */
364 static void generateTrustControls(Manifest *pPost){
365 if( !g.perm.AdminForum ) return;
366 if( login_is_special(pPost->zUser) ) return;
367 @ <br>
368 @ <label><input type="checkbox" name="trust">
369 @ Trust user "%h(pPost->zUser)"
370 @ so that future posts by "%h(pPost->zUser)" do not require moderation.
371 @ </label>
372 @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
373 }
374
375 /*
376 ** Compute a display name from a login name.
377 **
378 ** If the input login is found in the USER table, then check the USER.INFO
379 ** field to see if it has display-name followed by an email address.
@@ -403,413 +389,326 @@
403 db_reset(&q);
404 return zResult;
405 }
406
407 /*
408 ** Display all posts in a forum thread in chronological order
409 */
410 static void forum_display_chronological(int froot, int target, int bRawMode){
411 ForumThread *pThread = forumthread_create(froot, 0);
412 ForumEntry *p;
413 int notAnon = login_is_individual();
414 char cMode = bRawMode ? 'r' : 'c';
415 for(p=pThread->pFirst; p; p=p->pNext){
416 char *zDate;
417 Manifest *pPost;
418 int isPrivate; /* True for posts awaiting moderation */
419 int sameUser; /* True if author is also the reader */
420 const char *zUuid;
421 char *zDisplayName; /* The display name */
422 int sid;
423
424 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
425 if( pPost==0 ) continue;
426 if( p->fpid==target ){
427 @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
428 }else if( p->pLeaf!=0 ){
429 @ <div id="forum%d(p->fpid)" class="forumTime forumObs">
430 }else{
431 @ <div id="forum%d(p->fpid)" class="forumTime">
432 }
433 if( pPost->zThreadTitle ){
434 @ <h1>%h(pPost->zThreadTitle)</h1>
435 }
436 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
437 zDisplayName = display_name_from_login(pPost->zUser);
438 sid = p->pEdit ? p->pEdit->sid : p->sid;
439 @ <h3 class='forumPostHdr'>(%d(sid)) By %h(zDisplayName) on %h(zDate)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440 fossil_free(zDisplayName);
441 fossil_free(zDate);
442 if( p->pEdit ){
443 @ edit of %z(href("%R/forumpost/%S?t=%c",p->pEdit->zUuid,cMode))\
444 @ %d(p->pEdit->sid)</a>
 
 
 
 
 
445 }
 
 
446 if( g.perm.Debug ){
447 @ <span class="debug">\
448 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
449 }
450 if( p->firt ){
451 ForumEntry *pIrt = p->pPrev;
452 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
453 if( pIrt ){
454 @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
455 @ %d(pIrt->sid)</a>
456 }
457 }
458 zUuid = p->zUuid;
459 if( p->pLeaf ){
460 @ updated by %z(href("%R/forumpost/%S?t=%c",p->pLeaf->zUuid,cMode))\
461 @ %d(p->pLeaf->sid)</a>
462 zUuid = p->pLeaf->zUuid;
463 }
464 if( p->fpid!=target ){
465 @ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
466 }
467 if( !bRawMode ){
468 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
469 }
470 isPrivate = content_is_private(p->fpid);
471 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
472 @ </h3>
473 if( isPrivate && !g.perm.ModForum && !sameUser ){
474 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
475 }else{
476 const char *zMimetype;
477 if( bRawMode ){
478 zMimetype = "text/plain";
479 }else if( p->pLeaf!=0 ){
480 zMimetype = "text/plain";
481 }else{
482 zMimetype = pPost->zMimetype;
483 }
484 forum_render(0, zMimetype, pPost->zWiki, 0, 1);
485 }
486 if( g.perm.WrForum && p->pLeaf==0 ){
487 int sameUser = login_is_individual()
488 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
 
 
 
 
 
 
 
 
 
 
 
 
 
489 @ <div><form action="%R/forumedit" method="POST">
490 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
491 if( !isPrivate ){
492 /* Reply and Edit are only available if the post has already
493 ** been approved */
494 @ <input type="submit" name="reply" value="Reply">
495 if( g.perm.Admin || sameUser ){
496 @ <input type="submit" name="edit" value="Edit">
497 @ <input type="submit" name="nullout" value="Delete">
498 }
499 }else if( g.perm.ModForum ){
500 /* Provide moderators with moderation buttons for posts that
501 ** are pending moderation */
 
502 @ <input type="submit" name="approve" value="Approve">
503 @ <input type="submit" name="reject" value="Reject">
504 generateTrustControls(pPost);
505 }else if( sameUser ){
506 /* A post that is pending moderation can be deleted by the
507 ** person who originally submitted the post */
 
 
 
 
 
508 @ <input type="submit" name="reject" value="Delete">
509 }
510 @ </form></div>
511 }
512 manifest_destroy(pPost);
513 @ </div>
514 }
515
516 /* Undocumented "threadtable" query parameter causes thread table
517 ** to be displayed for debugging purposes.
518 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519 if( PB("threadtable") ){
520 @ <hr>
521 @ <table border="1" cellpadding="3" cellspacing="0">
522 @ <tr><th>sid<th>fpid<th>firt<th>fprev<th>mfirt<th>pLeaf<th>nReply<th>hash
 
523 for(p=pThread->pFirst; p; p=p->pNext){
524 @ <tr><td>%d(p->sid)<td>%d(p->fpid)<td>%d(p->firt)\
525 @ <td>%d(p->fprev)<td>%d(p->mfirt)\
526 @ <td>%d(p->pLeaf?p->pLeaf->fpid:0)<td>%d(p->nReply)\
 
 
 
 
527 @ <td>%S(p->zUuid)</tr>
528 }
529 @ </table>
530 }
531
532 forumthread_delete(pThread);
533 }
534 /*
535 ** Display all the edit history of post "target".
536 */
537 static void forum_display_history(int froot, int target, int bRawMode){
538 ForumThread *pThread = forumthread_create(froot, 0);
539 ForumEntry *p;
540 int notAnon = login_is_individual();
541 char cMode = bRawMode ? 'r' : 'c';
542 ForumEntry *pLeaf = 0;
543 int cnt = 0;
544 for(p=pThread->pFirst; p; p=p->pNext){
545 if( p->fpid==target ){
546 pLeaf = p->pLeaf ? p->pLeaf : p;
547 break;
548 }
549 }
550 for(p=pThread->pFirst; p; p=p->pNext){
551 char *zDate;
552 Manifest *pPost;
553 int isPrivate; /* True for posts awaiting moderation */
554 int sameUser; /* True if author is also the reader */
555 const char *zUuid;
556 char *zDisplayName; /* The display name */
557
558 if( p->fpid!=pLeaf->fpid && p->pLeaf!=pLeaf ) continue;
559 cnt++;
560 pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
561 if( pPost==0 ) continue;
562 @ <div id="forum%d(p->fpid)" class="forumTime">
563 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
564 zDisplayName = display_name_from_login(pPost->zUser);
565 @ <h3 class='forumPostHdr'>(%d(p->sid)) By %h(zDisplayName) on %h(zDate)
566 fossil_free(zDisplayName);
567 fossil_free(zDate);
568 if( g.perm.Debug ){
569 @ <span class="debug">\
570 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
571 }
572 if( p->firt && cnt==1 ){
573 ForumEntry *pIrt = p->pPrev;
574 while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
575 if( pIrt ){
576 @ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
577 @ %d(pIrt->sid)</a>
578 }
579 }
580 zUuid = p->zUuid;
581 @ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a>
582 if( !bRawMode ){
583 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
584 }
585 isPrivate = content_is_private(p->fpid);
586 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
587 @ </h3>
588 if( isPrivate && !g.perm.ModForum && !sameUser ){
589 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
590 }else{
591 forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki,
592 0, 1);
593 }
594 if( g.perm.WrForum && p->pLeaf==0 ){
595 int sameUser = login_is_individual()
596 && fossil_strcmp(pPost->zUser, g.zLogin)==0;
597 @ <div><form action="%R/forumedit" method="POST">
598 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
599 if( !isPrivate ){
600 /* Reply and Edit are only available if the post has already
601 ** been approved */
602 @ <input type="submit" name="reply" value="Reply">
603 if( g.perm.Admin || sameUser ){
604 @ <input type="submit" name="edit" value="Edit">
605 @ <input type="submit" name="nullout" value="Delete">
606 }
607 }else if( g.perm.ModForum ){
608 /* Provide moderators with moderation buttons for posts that
609 ** are pending moderation */
610 @ <input type="submit" name="approve" value="Approve">
611 @ <input type="submit" name="reject" value="Reject">
612 generateTrustControls(pPost);
613 }else if( sameUser ){
614 /* A post that is pending moderation can be deleted by the
615 ** person who originally submitted the post */
616 @ <input type="submit" name="reject" value="Delete">
617 }
618 @ </form></div>
619 }
620 manifest_destroy(pPost);
621 @ </div>
622 }
623 forumthread_delete(pThread);
624 }
625
626 /*
627 ** Display all messages in a forumthread with indentation.
628 */
629 static int forum_display_hierarchical(int froot, int target){
630 ForumThread *pThread;
631 ForumEntry *p;
632 Manifest *pPost, *pOPost;
633 int fpid;
634 const char *zUuid;
635 char *zDate;
636 const char *zSel;
637 int notAnon = login_is_individual();
638 int iIndentScale = 4;
639
640 pThread = forumthread_create(froot, 1);
641 for(p=pThread->pFirst; p; p=p->pNext){
642 if( p->fpid==target ){
643 while( p->pEdit ) p = p->pEdit;
644 target = p->fpid;
645 break;
646 }
647 }
648 while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
649 iIndentScale--;
650 }
651 for(p=pThread->pDisplay; p; p=p->pDisplay){
652 int isPrivate; /* True for posts awaiting moderation */
653 int sameUser; /* True if reader is also the poster */
654 char *zDisplayName; /* User name to be displayed */
655 pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
656 if( p->pLeaf ){
657 fpid = p->pLeaf->fpid;
658 zUuid = p->pLeaf->zUuid;
659 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
660 }else{
661 fpid = p->fpid;
662 zUuid = p->zUuid;
663 pPost = pOPost;
664 }
665 zSel = p->fpid==target ? " forumSel" : "";
666 if( p->nIndent==1 ){
667 @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
668 }else{
669 @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
670 @ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
671 }
672 if( pPost==0 ) continue;
673 if( pPost->zThreadTitle ){
674 @ <h1>%h(pPost->zThreadTitle)</h1>
675 }
676 zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
677 zDisplayName = display_name_from_login(pOPost->zUser);
678 @ <h3 class='forumPostHdr'>\
679 @ (%d(p->sid)) By %h(zDisplayName) on %h(zDate)
680 fossil_free(zDisplayName);
681 fossil_free(zDate);
682 if( g.perm.Debug ){
683 @ <span class="debug">\
684 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
685 }
686 if( p->pLeaf ){
687 zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
688 if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
689 @ and edited on %h(zDate)
690 }else{
691 @ as edited by %h(pPost->zUser) on %h(zDate)
692 }
693 fossil_free(zDate);
694 if( g.perm.Debug ){
695 @ <span class="debug">\
696 @ <a href="%R/artifact/%h(p->pLeaf->zUuid)">\
697 @ (artifact-%d(p->pLeaf->fpid))</a></span>
698 }
699 @ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a>
700 manifest_destroy(pOPost);
701 }
702 if( fpid!=target ){
703 @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
704 }
705 @ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
706 if( p->firt ){
707 ForumEntry *pIrt = p->pPrev;
708 while( pIrt && pIrt->fpid!=p->mfirt ) pIrt = pIrt->pPrev;
709 if( pIrt ){
710 @ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
711 @ %d(pIrt->sid)</a>
712 }
713 }
714 @ </h3>
715 isPrivate = content_is_private(fpid);
716 sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
717 if( isPrivate && !g.perm.ModForum && !sameUser ){
718 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
719 }else{
720 forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1);
721 }
722 if( g.perm.WrForum ){
723 @ <div><form action="%R/forumedit" method="POST">
724 @ <input type="hidden" name="fpid" value="%s(zUuid)">
725 if( !isPrivate ){
726 /* Reply and Edit are only available if the post has already
727 ** been approved */
728 @ <input type="submit" name="reply" value="Reply">
729 if( g.perm.Admin || sameUser ){
730 @ <input type="submit" name="edit" value="Edit">
731 @ <input type="submit" name="nullout" value="Delete">
732 }
733 }else if( g.perm.ModForum ){
734 /* Provide moderators with moderation buttons for posts that
735 ** are pending moderation */
736 @ <input type="submit" name="approve" value="Approve">
737 @ <input type="submit" name="reject" value="Reject">
738 generateTrustControls(pPost);
739 }else if( sameUser ){
740 /* A post that is pending moderation can be deleted by the
741 ** person who originally submitted the post */
742 @ <input type="submit" name="reject" value="Delete">
743 }
744 @ </form></div>
745 }
746 manifest_destroy(pPost);
747 @ </div>
748 }
749 forumthread_delete(pThread);
750 return target;
751 }
752
753 /*
754 ** Emits all JS code required by /forumpost.
755 */
756 static void forumpost_emit_page_js(){
757 static int once = 0;
758 if(0==once){
759 once = 1;
760 style_emit_script_fossil_bootstrap(1);
761 builtin_request_js("forum.js");
762 builtin_request_js("fossil.dom.js");
763 builtin_request_js("fossil.page.forumpost.js");
764 }
765 }
766
767 /*
768 ** WEBPAGE: forumpost
769 **
770 ** Show a single forum posting. The posting is shown in context with
771 ** it's entire thread. The selected posting is enclosed within
772 ** <div class='forumSel'>...</div>. Javascript is used to move the
773 ** selected posting into view after the page loads.
774 **
775 ** Query parameters:
776 **
777 ** name=X REQUIRED. The hash of the post to display
778 ** t=MODE Display mode.
779 ** 'c' for chronological
780 ** 'h' for hierarchical
781 ** 'a' for automatic
782 ** 'r' for raw
783 ** 'y' for history of post X only
784 ** raw If present, show only the post specified and
785 ** show its original unformatted source text.
 
 
 
 
786 */
787 void forumpost_page(void){
788 forumthread_page();
789 }
790
791 /*
792 ** Add an appropriate style_header() to include title of the
793 ** given forum post.
794 */
795 static int forumthread_page_header(int froot, int fpid){
796 char *zThreadTitle = 0;
797
798 zThreadTitle = db_text("",
799 "SELECT"
800 " substr(event.comment,instr(event.comment,':')+2)"
801 " FROM forumpost, event"
802 " WHERE event.objid=forumpost.fpid"
803 " AND forumpost.fpid=%d;",
804 fpid
805 );
806 style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
807 fossil_free(zThreadTitle);
808 return 0;
809 }
810
811 /*
812 ** WEBPAGE: forumthread
813 **
814 ** Show all forum messages associated with a particular message thread.
815 ** The result is basically the same as /forumpost except that none of
@@ -816,24 +715,28 @@
816 ** the postings in the thread are selected.
817 **
818 ** Query parameters:
819 **
820 ** name=X REQUIRED. The hash of any post of the thread.
821 ** t=MODE Display mode. MODE is...
822 ** 'c' for chronological, or
823 ** 'h' for hierarchical, or
824 ** 'a' for automatic, or
825 ** 'r' for raw.
826 ** raw Show only the post given by name= and show it unformatted
827 ** hist Show only the edit history for the name= post
828 */
829 void forumthread_page(void){
830 int fpid;
831 int froot;
 
832 const char *zName = P("name");
833 const char *zMode = PD("t","a");
834 int bRaw = PB("raw");
 
 
 
835 login_check_credentials();
836 if( !g.perm.RdForum ){
837 login_needed(g.anon.RdForum);
838 return;
839 }
@@ -847,54 +750,70 @@
847 froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
848 if( froot==0 ){
849 webpage_error("Not a forum post: \"%s\"", zName);
850 }
851 if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
852 if( zMode[0]=='a' ){
853 if( cgi_from_mobile() ){
854 zMode = "c"; /* Default to chronological on mobile */
855 }else{
856 zMode = "h";
857 }
858 }
859 if( zMode[0]!='y' ){
860 forumthread_page_header(froot, fpid);
861 }
862 if( bRaw && fpid ){
863 Manifest *pPost;
864 pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
865 if( pPost==0 ){
866 @ <p>No such forum post: %h(zName)
867 }else{
868 int isPrivate = content_is_private(fpid);
869 int notAnon = login_is_individual();
870 int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
871 if( isPrivate && !g.perm.ModForum && !sameUser ){
872 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
873 }else{
874 forum_render(0, "text/plain", pPost->zWiki, 0, 0);
875 }
876 manifest_destroy(pPost);
877 }
878 }else if( zMode[0]=='c' ){
879 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
880 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
881 forum_display_chronological(froot, fpid, 0);
882 }else if( zMode[0]=='r' ){
883 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
884 style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
885 forum_display_chronological(froot, fpid, 1);
886 }else if( zMode[0]=='y' ){
887 style_header("Edit History Of A Forum Post");
888 style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName);
889 forum_display_history(froot, fpid, 1);
890 }else{
891 style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
892 style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
893 forum_display_hierarchical(froot, fpid);
894 }
895 forumpost_emit_page_js();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896 style_footer();
897 }
898
899 /*
900 ** Return true if a forum post should be moderated.
@@ -949,11 +868,11 @@
949 webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 );
950 blob_init(&x, 0, 0);
951 zDate = date_in_standard_format("now");
952 blob_appendf(&x, "D %s\n", zDate);
953 fossil_free(zDate);
954 zG = db_text(0,
955 "SELECT uuid FROM blob, forumpost"
956 " WHERE blob.rid==forumpost.froot"
957 " AND forumpost.fpid=%d", iBasis);
958 if( zG ){
959 blob_appendf(&x, "G %s\n", zG);
@@ -1017,11 +936,11 @@
1017 }
1018
1019 /*
1020 ** Paint the form elements for entering a Forum post
1021 */
1022 static void forum_entry_widget(
1023 const char *zTitle,
1024 const char *zMimetype,
1025 const char *zContent
1026 ){
1027 if( zTitle ){
@@ -1126,11 +1045,11 @@
1126 }
1127 style_header("New Forum Thread");
1128 @ <form action="%R/forume1" method="POST">
1129 @ <h1>New Thread:</h1>
1130 forum_from_line();
1131 forum_entry_widget(zTitle, zMimetype, zContent);
1132 @ <input type="submit" name="preview" value="Preview">
1133 if( P("preview") && !whitespace_only(zContent) ){
1134 @ <input type="submit" name="submit" value="Submit">
1135 }else{
1136 @ <input type="submit" name="submit" value="Submit" disabled>
@@ -1195,19 +1114,21 @@
1195 moderation_approve('f', fpid);
1196 if( g.perm.AdminForum
1197 && PB("trust")
1198 && (zUserToTrust = P("trustuser"))!=0
1199 ){
 
1200 db_multi_exec("UPDATE user SET cap=cap||'4' "
1201 "WHERE login=%Q AND cap NOT GLOB '*4*'",
1202 zUserToTrust);
 
1203 }
1204 cgi_redirectf("%R/forumpost/%S",P("fpid"));
1205 return;
1206 }
1207 if( P("reject") ){
1208 char *zParent =
1209 db_text(0,
1210 "SELECT uuid FROM forumpost, blob"
1211 " WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt",
1212 fpid
1213 );
@@ -1276,11 +1197,11 @@
1276 @ <h2>Revised Message:</h2>
1277 @ <form action="%R/forume2" method="POST">
1278 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1279 @ <input type="hidden" name="edit" value="1">
1280 forum_from_line();
1281 forum_entry_widget(zTitle, zMimetype, zContent);
1282 }else{
1283 /* Reply */
1284 char *zDisplayName;
1285 zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
1286 zContent = PDT("content","");
@@ -1302,11 +1223,11 @@
1302 @ <h2>Enter Reply:</h2>
1303 @ <form action="%R/forume2" method="POST">
1304 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1305 @ <input type="hidden" name="reply" value="1">
1306 forum_from_line();
1307 forum_entry_widget(0, zMimetype, zContent);
1308 }
1309 if( !isDelete ){
1310 @ <input type="submit" name="preview" value="Preview">
1311 }
1312 @ <input type="submit" name="cancel" value="Cancel">
1313
--- src/forum.c
+++ src/forum.c
@@ -26,44 +26,45 @@
26 */
27 #define DEFAULT_FORUM_MIMETYPE "text/x-markdown"
28
29 #if INTERFACE
30 /*
31 ** Each instance of the following object represents a single message -
32 ** either the initial post, an edit to a post, a reply, or an edit to
33 ** a reply.
34 */
35 struct ForumPost {
36 int fpid; /* rid for this post */
 
 
 
 
37 int sid; /* Serial ID number */
38 int rev; /* Revision number */
39 char *zUuid; /* Artifact hash */
40 ForumPost *pIrt; /* This post replies to pIrt */
41 ForumPost *pEditHead; /* Original, unedited post */
42 ForumPost *pEditTail; /* Most recent edit for this post */
43 ForumPost *pEditNext; /* This post is edited by pEditNext */
44 ForumPost *pEditPrev; /* This post is an edit of pEditPrev */
45 ForumPost *pNext; /* Next in chronological order */
46 ForumPost *pPrev; /* Previous in chronological order */
47 ForumPost *pDisplay; /* Next in display order */
48 int nEdit; /* Number of edits to this post */
49 int nIndent; /* Number of levels of indentation for this post */
50 };
51
52 /*
53 ** A single instance of the following tracks all entries for a thread.
54 */
55 struct ForumThread {
56 ForumPost *pFirst; /* First post in chronological order */
57 ForumPost *pLast; /* Last post in chronological order */
58 ForumPost *pDisplay; /* Entries in display order */
59 ForumPost *pTail; /* Last on the display list */
60 int mxIndent; /* Maximum indentation level */
61 };
62 #endif /* INTERFACE */
63
64 /*
65 ** Return true if the forum post with the given rid has been
66 ** subsequently edited.
67 */
68 int forum_rid_has_been_edited(int rid){
69 static Stmt q;
70 int res;
@@ -79,41 +80,39 @@
80
81 /*
82 ** Delete a complete ForumThread and all its entries.
83 */
84 static void forumthread_delete(ForumThread *pThread){
85 ForumPost *pPost, *pNext;
86 for(pPost=pThread->pFirst; pPost; pPost = pNext){
87 pNext = pPost->pNext;
88 fossil_free(pPost->zUuid);
89 fossil_free(pPost);
90 }
91 fossil_free(pThread);
92 }
93
 
94 /*
95 ** Search a ForumPost list forwards looking for the post with fpid
96 */
97 static ForumPost *forumpost_forward(ForumPost *p, int fpid){
98 while( p && p->fpid!=fpid ) p = p->pNext;
99 return p;
100 }
 
101
102 /*
103 ** Search backwards for a ForumPost
104 */
105 static ForumPost *forumpost_backward(ForumPost *p, int fpid){
106 while( p && p->fpid!=fpid ) p = p->pPrev;
107 return p;
108 }
109
110 /*
111 ** Add a post to the display list
112 */
113 static void forumpost_add_to_display(ForumThread *pThread, ForumPost *p){
114 if( pThread->pDisplay==0 ){
115 pThread->pDisplay = p;
116 }else{
117 pThread->pTail->pDisplay = p;
118 }
@@ -120,108 +119,112 @@
119 pThread->pTail = p;
120 }
121
122 /*
123 ** Extend the display list for pThread by adding all entries that
124 ** reference fpid. The first such post will be no earlier then
125 ** post "p".
126 */
127 static void forumthread_display_order(
128 ForumThread *pThread, /* The complete thread */
129 ForumPost *pBase /* Add replies to this post */
130 ){
131 ForumPost *p;
132 ForumPost *pPrev = 0;
133 ForumPost *pBaseIrt;
134 for(p=pBase->pNext; p; p=p->pNext){
135 if( !p->pEditPrev && p->pIrt ){
136 pBaseIrt = p->pIrt->pEditHead ? p->pIrt->pEditHead : p->pIrt;
137 if( pBaseIrt==pBase ){
138 if( pPrev ){
139 pPrev->nIndent = pBase->nIndent + 1;
140 forumpost_add_to_display(pThread, pPrev);
141 forumthread_display_order(pThread, pPrev);
142 }
143 pPrev = p;
144 }
145 }
146 }
147 if( pPrev ){
148 pPrev->nIndent = pBase->nIndent + 1;
149 if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent;
150 forumpost_add_to_display(pThread, pPrev);
151 forumthread_display_order(pThread, pPrev);
152 }
153 }
154
155 /*
156 ** Construct a ForumThread object given the root record id.
157 */
158 static ForumThread *forumthread_create(int froot, int computeHierarchy){
159 ForumThread *pThread;
160 ForumPost *pPost;
161 ForumPost *p;
162 Stmt q;
163 int sid = 1;
164 int firt, fprev;
165 pThread = fossil_malloc( sizeof(*pThread) );
166 memset(pThread, 0, sizeof(*pThread));
167 db_prepare(&q,
168 "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
169 " FROM forumpost"
170 " WHERE froot=%d ORDER BY fmtime",
171 froot
172 );
173 while( db_step(&q)==SQLITE_ROW ){
174 pPost = fossil_malloc( sizeof(*pPost) );
175 memset(pPost, 0, sizeof(*pPost));
176 pPost->fpid = db_column_int(&q, 0);
177 firt = db_column_int(&q, 1);
178 fprev = db_column_int(&q, 2);
179 pPost->zUuid = fossil_strdup(db_column_text(&q,3));
180 if( !fprev ) pPost->sid = sid++;
181 pPost->pPrev = pThread->pLast;
182 pPost->pNext = 0;
 
 
183 if( pThread->pLast==0 ){
184 pThread->pFirst = pPost;
185 }else{
186 pThread->pLast->pNext = pPost;
187 }
188 pThread->pLast = pPost;
189
190 /* Find the in-reply-to post. Default to the topic post if the replied-to
191 ** post cannot be found. */
192 if( firt ){
193 pPost->pIrt = pThread->pFirst;
194 for(p=pThread->pFirst; p; p=p->pNext){
195 if( p->fpid==firt ){
196 pPost->pIrt = p;
197 break;
198 }
199 }
200 }
201
202 /* Maintain the linked list of post edits. */
203 if( fprev ){
204 p = forumpost_backward(pPost->pPrev, fprev);
205 p->pEditNext = pPost;
206 pPost->sid = p->sid;
207 pPost->rev = p->rev+1;
208 pPost->nEdit = p->nEdit+1;
209 pPost->pEditPrev = p;
210 pPost->pEditHead = p->pEditHead ? p->pEditHead : p;
211 for(; p; p=p->pEditPrev ){
212 p->nEdit = pPost->nEdit;
213 p->pEditTail = pPost;
214 }
215 }
216 }
217 db_finalize(&q);
218
219 if( computeHierarchy ){
220 /* Compute the hierarchical display order */
221 pPost = pThread->pFirst;
222 pPost->nIndent = 1;
223 pThread->mxIndent = 1;
224 forumpost_add_to_display(pThread, pPost);
225 forumthread_display_order(pThread, pPost);
226 }
227
228 /* Return the result */
229 return pThread;
230 }
@@ -265,11 +268,11 @@
268 void forumthread_cmd(void){
269 int fpid;
270 int froot;
271 const char *zName;
272 ForumThread *pThread;
273 ForumPost *p;
274
275 db_find_and_open_repository(0,0);
276 verify_all_options();
277 if( g.argc==2 ){
278 forum_thread_list();
@@ -293,21 +296,22 @@
296 pThread = forumthread_create(froot, 1);
297 fossil_print("Chronological:\n");
298 fossil_print(
299 /* 0 1 2 3 4 5 6 7 */
300 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
301 " sid rev fpid pIrt pEditPrev pEditTail hash\n");
302 for(p=pThread->pFirst; p; p=p->pNext){
303 fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev,
304 p->fpid, p->pIrt ? p->pIrt->fpid : 0,
305 p->pEditPrev ? p->pEditPrev->fpid : 0,
306 p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid);
307 }
308 fossil_print("\nDisplay\n");
309 for(p=pThread->pDisplay; p; p=p->pDisplay){
310 fossil_print("%*s", (p->nIndent-1)*3, "");
311 if( p->pEditTail ){
312 fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid);
313 }else{
314 fossil_print("%d\n", p->fpid);
315 }
316 }
317 forumthread_delete(pThread);
@@ -352,28 +356,10 @@
356 if( zClass ){
357 @ </div>
358 }
359 }
360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361 /*
362 ** Compute a display name from a login name.
363 **
364 ** If the input login is found in the USER table, then check the USER.INFO
365 ** field to see if it has display-name followed by an email address.
@@ -403,413 +389,326 @@
389 db_reset(&q);
390 return zResult;
391 }
392
393 /*
394 ** Display a single post in a forum thread.
395 */
396 static void forum_display_post(
397 ForumPost *p, /* Forum post to display */
398 int iIndentScale, /* Indent scale factor */
399 int bRaw, /* True to omit the border */
400 int bUnf, /* True to leave the post unformatted */
401 int bHist, /* True if showing edit history */
402 int bSelect, /* True if this is the selected post */
403 char *zQuery /* Common query string */
404 ){
405 char *zDisplayName; /* The display name */
406 char *zDate; /* The time/date string */
407 char *zHist; /* History query string */
408 Manifest *pManifest; /* Manifest comprising the current post */
409 int bPrivate; /* True for posts awaiting moderation */
410 int bSameUser; /* True if author is also the reader */
411 int iIndent; /* Indent level */
412 const char *zMimetype;/* Formatting MIME type */
413
414 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
415 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
416 if( !pManifest ) return;
417
418 /* When not in raw mode, create the border around the post. */
419 if( !bRaw ){
420 /* Open the <div> enclosing the post. Set the class string to mark the post
421 ** as selected and/or obsolete. */
422 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
423 @ <div id='forum%d(p->fpid)' class='forumTime\
424 @ %s(bSelect ? " forumSel" : "")\
425 @ %s(p->pEditTail ? " forumObs" : "")'\
426 if( iIndent && iIndentScale ){
427 @ style='margin-left: %d(iIndent*iIndentScale)ex'
428 }
429 @ >
430
431 /* If this is the first post (or an edit thereof), emit the thread title. */
432 if( pManifest->zThreadTitle ){
433 @ <h1>%h(pManifest->zThreadTitle)</h1>
434 }
435
436 /* Emit the serial number, revision number, author, and date. */
437 zDisplayName = display_name_from_login(pManifest->zUser);
438 zDate = db_text(0, "SELECT datetime(%.17g)", pManifest->rDate);
439 @ <h3 class='forumPostHdr'>(%d(p->sid)\
440 if( p->nEdit ){
441 @ .%.*d(fossil_num_digits(p->nEdit))(p->rev)\
442 }
443 @ ) By %h(zDisplayName) on %h(zDate)
444 fossil_free(zDisplayName);
445 fossil_free(zDate);
446
447 /* If this is an edit, refer back to the old version. Be sure "hist" is in
448 ** the query string so the old version will actually be shown. */
449 if( p->pEditPrev ){
450 zHist = bHist ? "" : "&hist";
451 @ edit of \
452 @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\
453 @ %d(p->sid).%.*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev)</a>
454 }
455
456 /* If debugging is enabled, link to the artifact page. */
457 if( g.perm.Debug ){
458 @ <span class="debug">\
459 @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
460 }
461
462 /* If this is a reply, refer back to the parent post. */
463 if( p->pIrt ){
464 @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\
465 @ %d(p->pIrt->sid)\
466 if( p->pIrt->nEdit ){
467 @ .%.*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\
468 }
469 @ </a>
470 }
471
472 /* If this post was later edited, refer forward to the next edit. */
473 if( p->pEditNext ){
474 @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\
475 @ %d(p->pEditNext->sid)\
476 @ .%.*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev)</a>
477 }
478
479 /* Provide a link to select the individual post. */
480 if( !bSelect ){
481 @ %z(href("%R/forumpost/%S?%s",p->zUuid,zQuery))[link]</a>
482 }
483
484 /* Provide a link to the raw source code. */
485 if( !bUnf ){
486 @ %z(href("%R/forumpost/%S?raw",p->zUuid))[source]</a>
487 }
488 @ </h3>
489 }
490
491 /* Check if this post is approved, also if it's by the current user. */
492 bPrivate = content_is_private(p->fpid);
493 bSameUser = login_is_individual()
494 && fossil_strcmp(pManifest->zUser, g.zLogin)==0;
495
496 /* Render the post if the user is able to see it. */
497 if( bPrivate && !g.perm.ModForum && !bSameUser ){
498 @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
499 }else{
500 if( bRaw || bUnf || p->pEditTail ){
501 zMimetype = "text/plain";
502 }else{
503 zMimetype = pManifest->zMimetype;
504 }
505 forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw);
506 }
507
508 /* When not in raw mode, finish creating the border around the post. */
509 if( !bRaw ){
510 /* If the user is able to write to the forum and if this post has not been
511 ** edited, create a form with various interaction buttons. */
512 if( g.perm.WrForum && !p->pEditTail ){
513 @ <div><form action="%R/forumedit" method="POST">
514 @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
515 if( !bPrivate ){
516 /* Reply and Edit are only available if the post has been approved. */
 
517 @ <input type="submit" name="reply" value="Reply">
518 if( g.perm.Admin || bSameUser ){
519 @ <input type="submit" name="edit" value="Edit">
520 @ <input type="submit" name="nullout" value="Delete">
521 }
522 }else if( g.perm.ModForum ){
523 /* Allow moderators to approve or reject pending posts. Also allow
524 ** forum supervisors to mark non-special users as trusted and therefore
525 ** able to post unmoderated. */
526 @ <input type="submit" name="approve" value="Approve">
527 @ <input type="submit" name="reject" value="Reject">
528 if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){
529 @ <br><label><input type="checkbox" name="trust">
530 @ Trust user "%h(pManifest->zUser)" so that future posts by \
531 @ "%h(pManifest->zUser)" do not require moderation.
532 @ </label>
533 @ <input type="hidden" name="trustuser" value="%h(pManifest->zUser)">
534 }
535 }else if( bSameUser ){
536 /* Allow users to delete (reject) their own pending posts. */
537 @ <input type="submit" name="reject" value="Delete">
538 }
539 @ </form></div>
540 }
 
541 @ </div>
542 }
543
544 /* Clean up. */
545 manifest_destroy(pManifest);
546 }
547
548 /*
549 ** Possible display modes for forum_display_thread().
550 */
551 enum {
552 FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force
553 ** unformatted mode, and inhibit history mode */
554 FD_SINGLE, /* Render a single post and (optionally) its edit history */
555 FD_CHRONO, /* Render all posts in chronological order */
556 FD_HIER, /* Render all posts in an indented hierarchy */
557 };
558
559 /*
560 ** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a
561 ** single post from the thread and (optionally) its edit history.
562 */
563 static void forum_display_thread(
564 int froot, /* Forum thread root post ID */
565 int fpid, /* Selected forum post ID, or 0 if none selected */
566 int mode, /* Forum display mode, one of the FD_* enumerations */
567 int bUnf, /* True if rendering unformatted */
568 int bHist /* True if showing edit history, ignored for FD_RAW */
569 ){
570 ForumThread *pThread; /* Thread structure */
571 ForumPost *pSelect; /* Currently selected post, or NULL if none */
572 ForumPost *p; /* Post iterator pointer */
573 char *zQuery; /* Common query string */
574 int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */
575 int sid; /* Comparison serial ID */
576
577 /* In raw mode, force unformatted display and disable history. */
578 if( mode == FD_RAW ){
579 bUnf = 1;
580 bHist = 0;
581 }
582
583 /* Thread together the posts and (optionally) compute the hierarchy. */
584 pThread = forumthread_create(froot, mode==FD_HIER);
585
586 /* Compute the appropriate indent scaling. */
587 if( mode==FD_HIER ){
588 iIndentScale = 4;
589 while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
590 iIndentScale--;
591 }
592 }else{
593 iIndentScale = 0;
594 }
595
596 /* Find the selected post, or (depending on parameters) its latest edit. */
597 pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0;
598 if( !bHist && mode!=FD_RAW && pSelect && pSelect->pEditTail ){
599 pSelect = pSelect->pEditTail;
600 }
601
602 /* When displaying only a single post, abort if no post was selected or the
603 ** selected forum post does not exist in the thread. Otherwise proceed to
604 ** display the entire thread without marking any posts as selected. */
605 if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){
606 return;
607 }
608
609 /* Create the common query string to append to nearly all post links. */
610 zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s",
611 mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h',
612 bUnf ? "&unf" : "", bHist ? "&hist" : "");
613
614 /* Identify which post to display first. If history is shown, start with the
615 ** original, unedited post. Otherwise advance to the post's latest edit. */
616 if( mode==FD_RAW || mode==FD_SINGLE ){
617 p = pSelect;
618 if( bHist && p->pEditHead ) p = p->pEditHead;
619 }else{
620 p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
621 if( !bHist && p->pEditTail ) p = p->pEditTail;
622 }
623
624 /* Display the appropriate subset of posts in sequence. */
625 while( p ){
626 /* Display the post. */
627 forum_display_post(p, iIndentScale, mode==FD_RAW,
628 bUnf, bHist, p==pSelect, zQuery);
629
630 /* Advance to the next post in the thread. */
631 if( mode==FD_CHRONO ){
632 /* Chronological mode: display posts (optionally including edits) in their
633 ** original commit order. */
634 if( bHist ){
635 p = p->pNext;
636 }else{
637 sid = p->sid;
638 if( p->pEditHead ) p = p->pEditHead;
639 do p = p->pNext; while( p && p->sid<=sid );
640 if( p && p->pEditTail ) p = p->pEditTail;
641 }
642 }else if( bHist && p->pEditNext ){
643 /* Hierarchical and single mode: display each post's edits in sequence. */
644 p = p->pEditNext;
645 }else if( mode==FD_HIER ){
646 /* Hierarchical mode: after displaying with each post (optionally
647 ** including edits), go to the next post in computed display order. */
648 p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay;
649 if( !bHist && p && p->pEditTail ) p = p->pEditTail;
650 }else{
651 /* Single and raw mode: terminate after displaying the selected post and
652 ** (optionally) its edits. */
653 break;
654 }
655 }
656
657 /* Undocumented "threadtable" query parameter causes thread table to be
658 ** displayed for debugging purposes. */
659 if( PB("threadtable") ){
660 @ <hr>
661 @ <table border="1" cellpadding="3" cellspacing="0">
662 @ <tr><th>sid<th>rev<th>fpid<th>pIrt<th>pEditHead<th>pEditTail\
663 @ <th>pEditNext<th>pEditPrev<th>pDisplay<th>hash
664 for(p=pThread->pFirst; p; p=p->pNext){
665 @ <tr><td>%d(p->sid)<td>%d(p->rev)<td>%d(p->fpid)\
666 @ <td>%d(p->pIrt ? p->pIrt->fpid : 0)\
667 @ <td>%d(p->pEditHead ? p->pEditHead->fpid : 0)\
668 @ <td>%d(p->pEditTail ? p->pEditTail->fpid : 0)\
669 @ <td>%d(p->pEditNext ? p->pEditNext->fpid : 0)\
670 @ <td>%d(p->pEditPrev ? p->pEditPrev->fpid : 0)\
671 @ <td>%d(p->pDisplay ? p->pDisplay->fpid : 0)\
672 @ <td>%S(p->zUuid)</tr>
673 }
674 @ </table>
675 }
676
677 /* Clean up. */
678 forumthread_delete(pThread);
679 fossil_free(zQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680 }
681
682 /*
683 ** WEBPAGE: forumpost
684 **
685 ** Show a single forum posting. The posting is shown in context with
686 ** its entire thread. The selected posting is enclosed within
687 ** <div class='forumSel'>...</div>. Javascript is used to move the
688 ** selected posting into view after the page loads.
689 **
690 ** Query parameters:
691 **
692 ** name=X REQUIRED. The hash of the post to display.
693 ** t=a Automatic display mode, i.e. hierarchical for
694 ** desktop and chronological for mobile. This is the
695 ** default if the "t" query parameter is omitted.
696 ** t=c Show posts in the order they were written.
697 ** t=h Show posts usin hierarchical indenting.
698 ** t=s Show only the post specified by "name=X".
699 ** t=r Alias for "t=c&unf&hist".
700 ** t=y Alias for "t=s&unf&hist".
701 ** raw Alias for "t=s&unf". Additionally, omit the border
702 ** around the post, and ignore "t" and "hist".
703 ** unf Show the original, unformatted source text.
704 ** hist Show edit history in addition to current posts.
705 */
706 void forumpost_page(void){
707 forumthread_page();
708 }
709
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710 /*
711 ** WEBPAGE: forumthread
712 **
713 ** Show all forum messages associated with a particular message thread.
714 ** The result is basically the same as /forumpost except that none of
@@ -816,24 +715,28 @@
715 ** the postings in the thread are selected.
716 **
717 ** Query parameters:
718 **
719 ** name=X REQUIRED. The hash of any post of the thread.
720 ** t=a Automatic display mode, i.e. hierarchical for
721 ** desktop and chronological for mobile. This is the
722 ** default if the "t" query parameter is omitted.
723 ** t=c Show posts in the order they were written.
724 ** t=h Show posts using hierarchical indenting.
725 ** unf Show the original, unformatted source text.
726 ** hist Show edit history in addition to current posts.
727 */
728 void forumthread_page(void){
729 int fpid;
730 int froot;
731 char *zThreadTitle;
732 const char *zName = P("name");
733 const char *zMode = PD("t","a");
734 int bRaw = PB("raw");
735 int bUnf = PB("unf");
736 int bHist = PB("hist");
737 int mode = 0;
738 login_check_credentials();
739 if( !g.perm.RdForum ){
740 login_needed(g.anon.RdForum);
741 return;
742 }
@@ -847,54 +750,70 @@
750 froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
751 if( froot==0 ){
752 webpage_error("Not a forum post: \"%s\"", zName);
753 }
754 if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
755
756 /* Decode the mode parameters. */
757 if( bRaw ){
758 mode = FD_RAW;
759 bUnf = 1;
760 bHist = 0;
761 cgi_replace_query_parameter("unf", "on");
762 cgi_delete_query_parameter("hist");
763 cgi_delete_query_parameter("raw");
764 }else{
765 switch( *zMode ){
766 case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break;
767 case 'c': mode = FD_CHRONO; break;
768 case 'h': mode = FD_HIER; break;
769 case 's': mode = FD_SINGLE; break;
770 case 'r': mode = FD_CHRONO; break;
771 case 'y': mode = FD_SINGLE; break;
772 default: webpage_error("Invalid thread mode: \"%s\"", zMode);
773 }
774 if( *zMode=='r' || *zMode=='y') {
775 bUnf = 1;
776 bHist = 1;
777 cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s");
778 cgi_replace_query_parameter("unf", "on");
779 cgi_replace_query_parameter("hist", "on");
780 }
781 }
782
783 /* Define the page header. */
784 zThreadTitle = db_text("",
785 "SELECT"
786 " substr(event.comment,instr(event.comment,':')+2)"
787 " FROM forumpost, event"
788 " WHERE event.objid=forumpost.fpid"
789 " AND forumpost.fpid=%d;",
790 fpid
791 );
792 style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum");
793 fossil_free(zThreadTitle);
794 if( mode!=FD_CHRONO ){
795 style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName,
796 bUnf ? "&unf" : "", bHist ? "&hist" : "");
797 }
798 if( mode!=FD_HIER ){
799 style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName,
800 bUnf ? "&unf" : "", bHist ? "&hist" : "");
801 }
802 style_submenu_checkbox("unf", "Unformatted", 0, 0);
803 style_submenu_checkbox("hist", "History", 0, 0);
804
805 /* Display the thread. */
806 forum_display_thread(froot, fpid, mode, bUnf, bHist);
807
808 /* Emit Forum Javascript. */
809 style_emit_script_fossil_bootstrap(1);
810 builtin_request_js("forum.js");
811 builtin_request_js("fossil.dom.js");
812 builtin_request_js("fossil.page.forumpost.js");
813
814 /* Emit the page style. */
815 style_footer();
816 }
817
818 /*
819 ** Return true if a forum post should be moderated.
@@ -949,11 +868,11 @@
868 webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 );
869 blob_init(&x, 0, 0);
870 zDate = date_in_standard_format("now");
871 blob_appendf(&x, "D %s\n", zDate);
872 fossil_free(zDate);
873 zG = db_text(0,
874 "SELECT uuid FROM blob, forumpost"
875 " WHERE blob.rid==forumpost.froot"
876 " AND forumpost.fpid=%d", iBasis);
877 if( zG ){
878 blob_appendf(&x, "G %s\n", zG);
@@ -1017,11 +936,11 @@
936 }
937
938 /*
939 ** Paint the form elements for entering a Forum post
940 */
941 static void forum_post_widget(
942 const char *zTitle,
943 const char *zMimetype,
944 const char *zContent
945 ){
946 if( zTitle ){
@@ -1126,11 +1045,11 @@
1045 }
1046 style_header("New Forum Thread");
1047 @ <form action="%R/forume1" method="POST">
1048 @ <h1>New Thread:</h1>
1049 forum_from_line();
1050 forum_post_widget(zTitle, zMimetype, zContent);
1051 @ <input type="submit" name="preview" value="Preview">
1052 if( P("preview") && !whitespace_only(zContent) ){
1053 @ <input type="submit" name="submit" value="Submit">
1054 }else{
1055 @ <input type="submit" name="submit" value="Submit" disabled>
@@ -1195,19 +1114,21 @@
1114 moderation_approve('f', fpid);
1115 if( g.perm.AdminForum
1116 && PB("trust")
1117 && (zUserToTrust = P("trustuser"))!=0
1118 ){
1119 db_unprotect(PROTECT_USER);
1120 db_multi_exec("UPDATE user SET cap=cap||'4' "
1121 "WHERE login=%Q AND cap NOT GLOB '*4*'",
1122 zUserToTrust);
1123 db_protect_pop();
1124 }
1125 cgi_redirectf("%R/forumpost/%S",P("fpid"));
1126 return;
1127 }
1128 if( P("reject") ){
1129 char *zParent =
1130 db_text(0,
1131 "SELECT uuid FROM forumpost, blob"
1132 " WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt",
1133 fpid
1134 );
@@ -1276,11 +1197,11 @@
1197 @ <h2>Revised Message:</h2>
1198 @ <form action="%R/forume2" method="POST">
1199 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1200 @ <input type="hidden" name="edit" value="1">
1201 forum_from_line();
1202 forum_post_widget(zTitle, zMimetype, zContent);
1203 }else{
1204 /* Reply */
1205 char *zDisplayName;
1206 zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
1207 zContent = PDT("content","");
@@ -1302,11 +1223,11 @@
1223 @ <h2>Enter Reply:</h2>
1224 @ <form action="%R/forume2" method="POST">
1225 @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
1226 @ <input type="hidden" name="reply" value="1">
1227 forum_from_line();
1228 forum_post_widget(0, zMimetype, zContent);
1229 }
1230 if( !isDelete ){
1231 @ <input type="submit" name="preview" value="Preview">
1232 }
1233 @ <input type="submit" name="cancel" value="Cancel">
1234
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -1,6 +1,18 @@
11
"use strict";
2
+(function () {
3
+ /* CustomEvent polyfill, courtesy of Mozilla:
4
+ https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
5
+ */
6
+ if(typeof window.CustomEvent === "function") return false;
7
+ window.CustomEvent = function(event, params) {
8
+ if(!params) params = {bubbles: false, cancelable: false, detail: null};
9
+ const evt = document.createEvent('CustomEvent');
10
+ evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
11
+ return evt;
12
+ };
13
+})();
214
(function(global){
315
/* Bootstrapping bits for the global.fossil object. Must be
416
loaded after style.c:style_emit_script_tag() has initialized
517
that object.
618
*/
719
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -1,6 +1,18 @@
1 "use strict";
 
 
 
 
 
 
 
 
 
 
 
 
2 (function(global){
3 /* Bootstrapping bits for the global.fossil object. Must be
4 loaded after style.c:style_emit_script_tag() has initialized
5 that object.
6 */
7
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -1,6 +1,18 @@
1 "use strict";
2 (function () {
3 /* CustomEvent polyfill, courtesy of Mozilla:
4 https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
5 */
6 if(typeof window.CustomEvent === "function") return false;
7 window.CustomEvent = function(event, params) {
8 if(!params) params = {bubbles: false, cancelable: false, detail: null};
9 const evt = document.createEvent('CustomEvent');
10 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
11 return evt;
12 };
13 })();
14 (function(global){
15 /* Bootstrapping bits for the global.fossil object. Must be
16 loaded after style.c:style_emit_script_tag() has initialized
17 that object.
18 */
19
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -164,15 +164,18 @@
164164
if(opt.pinSize && opt.confirmText){
165165
/* Try to pin the element's width the the greater of its
166166
current width or its waiting-on-confirmation width
167167
to avoid layout reflow when it's activated. */
168168
const digits = (''+(opt.timeout/1000 || opt.ticks)).length;
169
- const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits));
170
- const w1 = parseFloat(window.getComputedStyle(target).width);
169
+ const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1));
170
+ const w1 = parseInt(target.getBoundingClientRect().width);
171171
updateText(lblLong);
172
- const w2 = parseFloat(window.getComputedStyle(target).width);
173
- target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px";
172
+ const w2 = parseInt(target.getBoundingClientRect().width);
173
+ if(w1 || w2){
174
+ /* If target is not in visible part of the DOM, those values may be 0. */
175
+ target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px";
176
+ }
174177
}
175178
updateText(this.opt.initialText);
176179
if(this.opt.ticks && !this.opt.ontick){
177180
this.opt.ontick = function(tick){
178181
updateText(formatCountdown(self.opt.confirmText,tick));
179182
180183
ADDED src/fossil.copybutton.js
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -164,15 +164,18 @@
164 if(opt.pinSize && opt.confirmText){
165 /* Try to pin the element's width the the greater of its
166 current width or its waiting-on-confirmation width
167 to avoid layout reflow when it's activated. */
168 const digits = (''+(opt.timeout/1000 || opt.ticks)).length;
169 const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits));
170 const w1 = parseFloat(window.getComputedStyle(target).width);
171 updateText(lblLong);
172 const w2 = parseFloat(window.getComputedStyle(target).width);
173 target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px";
 
 
 
174 }
175 updateText(this.opt.initialText);
176 if(this.opt.ticks && !this.opt.ontick){
177 this.opt.ontick = function(tick){
178 updateText(formatCountdown(self.opt.confirmText,tick));
179
180 DDED src/fossil.copybutton.js
--- src/fossil.confirmer.js
+++ src/fossil.confirmer.js
@@ -164,15 +164,18 @@
164 if(opt.pinSize && opt.confirmText){
165 /* Try to pin the element's width the the greater of its
166 current width or its waiting-on-confirmation width
167 to avoid layout reflow when it's activated. */
168 const digits = (''+(opt.timeout/1000 || opt.ticks)).length;
169 const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1));
170 const w1 = parseInt(target.getBoundingClientRect().width);
171 updateText(lblLong);
172 const w2 = parseInt(target.getBoundingClientRect().width);
173 if(w1 || w2){
174 /* If target is not in visible part of the DOM, those values may be 0. */
175 target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px";
176 }
177 }
178 updateText(this.opt.initialText);
179 if(this.opt.ticks && !this.opt.ontick){
180 this.opt.ontick = function(tick){
181 updateText(formatCountdown(self.opt.confirmText,tick));
182
183 DDED src/fossil.copybutton.js
--- a/src/fossil.copybutton.js
+++ b/src/fossil.copybutton.js
@@ -0,0 +1,3 @@
1
+(function(F/*fossil object*/){
2
+ /**
3
+ A basic API for creating and
--- a/src/fossil.copybutton.js
+++ b/src/fossil.copybutton.js
@@ -0,0 +1,3 @@
 
 
 
--- a/src/fossil.copybutton.js
+++ b/src/fossil.copybutton.js
@@ -0,0 +1,3 @@
1 (function(F/*fossil object*/){
2 /**
3 A basic API for creating and
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -218,14 +218,16 @@
218218
const a = argsToArray(arguments);
219219
a.shift();
220220
for(let i in a) {
221221
var e = a[i];
222222
if(isArray(e) || e.forEach){
223
- e.forEach((x)=>f.call(this, parent,e));
223
+ e.forEach((x)=>f.call(this, parent,x));
224224
continue;
225225
}
226
- if('string'===typeof e || 'number'===typeof e) e = this.text(e);
226
+ if('string'===typeof e
227
+ || 'number'===typeof e
228
+ || 'boolean'===typeof e) e = this.text(e);
227229
parent.appendChild(e);
228230
}
229231
return parent;
230232
};
231233
@@ -465,11 +467,104 @@
465467
e = src.querySelector(x);
466468
if(!e){
467469
e = new Error("Cannot find DOM element: "+x);
468470
console.error(e, src);
469471
throw e;
472
+ }
473
+ return e;
474
+ };
475
+
476
+ /**
477
+ "Blinks" the given element a single time for the given number of
478
+ milliseconds, defaulting (if the 2nd argument is falsy or not a
479
+ number) to flashOnce.defaultTimeMs. If a 3rd argument is passed
480
+ in, it must be a function, and it gets callback back at the end
481
+ of the asynchronous flashing processes.
482
+
483
+ This will only activate once per element during that timeframe -
484
+ further calls will become no-ops until the blink is
485
+ completed. This routine adds a dataset member to the element for
486
+ the duration of the blink, to allow it to block multiple blinks.
487
+
488
+ If passed 2 arguments and the 2nd is a function, it behaves as if
489
+ it were called as (arg1, undefined, arg2).
490
+
491
+ Returns e, noting that the flash itself is asynchronous and may
492
+ still be running, or not yet started, when this function returns.
493
+ */
494
+ dom.flashOnce = function f(e,howLongMs,afterFlashCallback){
495
+ if(e.dataset.isBlinking){
496
+ return;
497
+ }
498
+ if(2===arguments.length && 'function' ===typeof howLongMs){
499
+ afterFlashCallback = howLongMs;
500
+ howLongMs = f.defaultTimeMs;
501
+ }
502
+ if(!howLongMs || 'number'!==typeof howLongMs){
503
+ howLongMs = f.defaultTimeMs;
504
+ }
505
+ e.dataset.isBlinking = true;
506
+ const transition = e.style.transition;
507
+ e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
508
+ const opacity = e.style.opacity;
509
+ e.style.opacity = 0;
510
+ setTimeout(function(){
511
+ e.style.transition = transition;
512
+ e.style.opacity = opacity;
513
+ delete e.dataset.isBlinking;
514
+ if(afterFlashCallback) afterFlashCallback();
515
+ }, howLongMs);
516
+ return e;
517
+ };
518
+ dom.flashOnce.defaultTimeMs = 400;
519
+
520
+ /**
521
+ Attempts to copy the given text to the system clipboard. Returns
522
+ true if it succeeds, else false.
523
+ */
524
+ dom.copyTextToClipboard = function(text){
525
+ if( window.clipboardData && window.clipboardData.setData ){
526
+ window.clipboardData.setData('Text',text);
527
+ return true;
528
+ }else{
529
+ const x = document.createElement("textarea");
530
+ x.style.position = 'fixed';
531
+ x.value = text;
532
+ document.body.appendChild(x);
533
+ x.select();
534
+ var rc;
535
+ try{
536
+ document.execCommand('copy');
537
+ rc = true;
538
+ }catch(err){
539
+ rc = false;
540
+ }finally{
541
+ document.body.removeChild(x);
542
+ }
543
+ return rc;
544
+ }
545
+ };
546
+
547
+ /**
548
+ Copies all properties from the 2nd argument (a plain object) into
549
+ the style member of the first argument (DOM element or a
550
+ forEach-capable list of elements). If the 2nd argument is falsy
551
+ or empty, this is a no-op.
552
+
553
+ Returns its first argument.
554
+ */
555
+ dom.copyStyle = function f(e, style){
556
+ if(e.forEach){
557
+ e.forEach((x)=>f(x, style));
558
+ return e;
559
+ }
560
+ if(style){
561
+ let k;
562
+ for(k in style){
563
+ if(style.hasOwnProperty(k)) e.style[k] = style[k];
564
+ }
470565
}
471566
return e;
472567
};
473568
474569
return F.dom = dom;
475570
})(window.fossil);
476571
477572
ADDED src/fossil.numbered-lines.js
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -218,14 +218,16 @@
218 const a = argsToArray(arguments);
219 a.shift();
220 for(let i in a) {
221 var e = a[i];
222 if(isArray(e) || e.forEach){
223 e.forEach((x)=>f.call(this, parent,e));
224 continue;
225 }
226 if('string'===typeof e || 'number'===typeof e) e = this.text(e);
 
 
227 parent.appendChild(e);
228 }
229 return parent;
230 };
231
@@ -465,11 +467,104 @@
465 e = src.querySelector(x);
466 if(!e){
467 e = new Error("Cannot find DOM element: "+x);
468 console.error(e, src);
469 throw e;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470 }
471 return e;
472 };
473
474 return F.dom = dom;
475 })(window.fossil);
476
477 DDED src/fossil.numbered-lines.js
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -218,14 +218,16 @@
218 const a = argsToArray(arguments);
219 a.shift();
220 for(let i in a) {
221 var e = a[i];
222 if(isArray(e) || e.forEach){
223 e.forEach((x)=>f.call(this, parent,x));
224 continue;
225 }
226 if('string'===typeof e
227 || 'number'===typeof e
228 || 'boolean'===typeof e) e = this.text(e);
229 parent.appendChild(e);
230 }
231 return parent;
232 };
233
@@ -465,11 +467,104 @@
467 e = src.querySelector(x);
468 if(!e){
469 e = new Error("Cannot find DOM element: "+x);
470 console.error(e, src);
471 throw e;
472 }
473 return e;
474 };
475
476 /**
477 "Blinks" the given element a single time for the given number of
478 milliseconds, defaulting (if the 2nd argument is falsy or not a
479 number) to flashOnce.defaultTimeMs. If a 3rd argument is passed
480 in, it must be a function, and it gets callback back at the end
481 of the asynchronous flashing processes.
482
483 This will only activate once per element during that timeframe -
484 further calls will become no-ops until the blink is
485 completed. This routine adds a dataset member to the element for
486 the duration of the blink, to allow it to block multiple blinks.
487
488 If passed 2 arguments and the 2nd is a function, it behaves as if
489 it were called as (arg1, undefined, arg2).
490
491 Returns e, noting that the flash itself is asynchronous and may
492 still be running, or not yet started, when this function returns.
493 */
494 dom.flashOnce = function f(e,howLongMs,afterFlashCallback){
495 if(e.dataset.isBlinking){
496 return;
497 }
498 if(2===arguments.length && 'function' ===typeof howLongMs){
499 afterFlashCallback = howLongMs;
500 howLongMs = f.defaultTimeMs;
501 }
502 if(!howLongMs || 'number'!==typeof howLongMs){
503 howLongMs = f.defaultTimeMs;
504 }
505 e.dataset.isBlinking = true;
506 const transition = e.style.transition;
507 e.style.transition = "opacity "+howLongMs+"ms ease-in-out";
508 const opacity = e.style.opacity;
509 e.style.opacity = 0;
510 setTimeout(function(){
511 e.style.transition = transition;
512 e.style.opacity = opacity;
513 delete e.dataset.isBlinking;
514 if(afterFlashCallback) afterFlashCallback();
515 }, howLongMs);
516 return e;
517 };
518 dom.flashOnce.defaultTimeMs = 400;
519
520 /**
521 Attempts to copy the given text to the system clipboard. Returns
522 true if it succeeds, else false.
523 */
524 dom.copyTextToClipboard = function(text){
525 if( window.clipboardData && window.clipboardData.setData ){
526 window.clipboardData.setData('Text',text);
527 return true;
528 }else{
529 const x = document.createElement("textarea");
530 x.style.position = 'fixed';
531 x.value = text;
532 document.body.appendChild(x);
533 x.select();
534 var rc;
535 try{
536 document.execCommand('copy');
537 rc = true;
538 }catch(err){
539 rc = false;
540 }finally{
541 document.body.removeChild(x);
542 }
543 return rc;
544 }
545 };
546
547 /**
548 Copies all properties from the 2nd argument (a plain object) into
549 the style member of the first argument (DOM element or a
550 forEach-capable list of elements). If the 2nd argument is falsy
551 or empty, this is a no-op.
552
553 Returns its first argument.
554 */
555 dom.copyStyle = function f(e, style){
556 if(e.forEach){
557 e.forEach((x)=>f(x, style));
558 return e;
559 }
560 if(style){
561 let k;
562 for(k in style){
563 if(style.hasOwnProperty(k)) e.style[k] = style[k];
564 }
565 }
566 return e;
567 };
568
569 return F.dom = dom;
570 })(window.fossil);
571
572 DDED src/fossil.numbered-lines.js
--- a/src/fossil.numbered-lines.js
+++ b/src/fossil.numbered-lines.js
@@ -0,0 +1,7 @@
1
+(function callee(arg){
2
+ /*
3
+ JSelse if(!arg){tion callee(arg){
4
+ /*
5
+ JS(function callee(arg){
6
+ /*
7
+ JS
--- a/src/fossil.numbered-lines.js
+++ b/src/fossil.numbered-lines.js
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
--- a/src/fossil.numbered-lines.js
+++ b/src/fossil.numbered-lines.js
@@ -0,0 +1,7 @@
1 (function callee(arg){
2 /*
3 JSelse if(!arg){tion callee(arg){
4 /*
5 JS(function callee(arg){
6 /*
7 JS
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -638,11 +638,11 @@
638638
};
639639
640640
F.onPageLoad(function() {
641641
P.base = {tag: E('base')};
642642
P.base.originalHref = P.base.tag.href;
643
- P.tabs = new fossil.TabManager('#fileedit-tabs');
643
+ P.tabs = new F.TabManager('#fileedit-tabs');
644644
P.e = { /* various DOM elements we work with... */
645645
taEditor: E('#fileedit-content-editor'),
646646
taCommentSmall: E('#fileedit-comment'),
647647
taCommentBig: E('#fileedit-comment-big'),
648648
taComment: undefined/*gets set to one of taComment{Big,Small}*/,
@@ -1142,11 +1142,11 @@
11421142
mimetype: P.finfo.mimetype,
11431143
element: P.e.previewTarget
11441144
});
11451145
},
11461146
onerror: (e)=>{
1147
- fossil.fetch.onerror(e);
1147
+ F.fetch.onerror(e);
11481148
callback("Error fetching preview: "+e);
11491149
}
11501150
});
11511151
return this;
11521152
};
11531153
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -638,11 +638,11 @@
638 };
639
640 F.onPageLoad(function() {
641 P.base = {tag: E('base')};
642 P.base.originalHref = P.base.tag.href;
643 P.tabs = new fossil.TabManager('#fileedit-tabs');
644 P.e = { /* various DOM elements we work with... */
645 taEditor: E('#fileedit-content-editor'),
646 taCommentSmall: E('#fileedit-comment'),
647 taCommentBig: E('#fileedit-comment-big'),
648 taComment: undefined/*gets set to one of taComment{Big,Small}*/,
@@ -1142,11 +1142,11 @@
1142 mimetype: P.finfo.mimetype,
1143 element: P.e.previewTarget
1144 });
1145 },
1146 onerror: (e)=>{
1147 fossil.fetch.onerror(e);
1148 callback("Error fetching preview: "+e);
1149 }
1150 });
1151 return this;
1152 };
1153
--- src/fossil.page.fileedit.js
+++ src/fossil.page.fileedit.js
@@ -638,11 +638,11 @@
638 };
639
640 F.onPageLoad(function() {
641 P.base = {tag: E('base')};
642 P.base.originalHref = P.base.tag.href;
643 P.tabs = new F.TabManager('#fileedit-tabs');
644 P.e = { /* various DOM elements we work with... */
645 taEditor: E('#fileedit-content-editor'),
646 taCommentSmall: E('#fileedit-comment'),
647 taCommentBig: E('#fileedit-comment-big'),
648 taComment: undefined/*gets set to one of taComment{Big,Small}*/,
@@ -1142,11 +1142,11 @@
1142 mimetype: P.finfo.mimetype,
1143 element: P.e.previewTarget
1144 });
1145 },
1146 onerror: (e)=>{
1147 F.fetch.onerror(e);
1148 callback("Error fetching preview: "+e);
1149 }
1150 });
1151 return this;
1152 };
1153
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1,9 +1,9 @@
11
(function(F/*the fossil object*/){
22
"use strict";
33
/* JS code for /forumpage and friends. Requires fossil.dom. */
4
- const P = fossil.page, D = fossil.dom;
4
+ const P = F.page, D = F.dom;
55
66
F.onPageLoad(function(){
77
const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight;
88
/* Returns an event handler which implements the post expand/collapse toggle
99
on contentElem when the given widget is activated. */
1010
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1,9 +1,9 @@
1 (function(F/*the fossil object*/){
2 "use strict";
3 /* JS code for /forumpage and friends. Requires fossil.dom. */
4 const P = fossil.page, D = fossil.dom;
5
6 F.onPageLoad(function(){
7 const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight;
8 /* Returns an event handler which implements the post expand/collapse toggle
9 on contentElem when the given widget is activated. */
10
--- src/fossil.page.forumpost.js
+++ src/fossil.page.forumpost.js
@@ -1,9 +1,9 @@
1 (function(F/*the fossil object*/){
2 "use strict";
3 /* JS code for /forumpage and friends. Requires fossil.dom. */
4 const P = F.page, D = F.dom;
5
6 F.onPageLoad(function(){
7 const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight;
8 /* Returns an event handler which implements the post expand/collapse toggle
9 on contentElem when the given widget is activated. */
10
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -855,11 +855,11 @@
855855
diff: E('#wikiedit-tab-diff'),
856856
misc: E('#wikiedit-tab-misc')
857857
//commit: E('#wikiedit-tab-commit')
858858
}
859859
};
860
- P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
860
+ P.tabs = new F.TabManager(D.clearElement(P.e.tabContainer));
861861
P.tabs.e.container.insertBefore(
862862
/* Move the status bar between the tab buttons and
863863
tab panels. Seems to be the best fit in terms of
864864
functionality and visibility. */
865865
E('#fossil-status-bar'), P.tabs.e.tabs
@@ -1322,11 +1322,11 @@
13221322
mimetype: mimetype,
13231323
element: P.e.previewTarget
13241324
});
13251325
},
13261326
onerror: (e)=>{
1327
- fossil.fetch.onerror(e);
1327
+ F.fetch.onerror(e);
13281328
callback("Error fetching preview: "+e);
13291329
}
13301330
});
13311331
return this;
13321332
};
13331333
13341334
ADDED src/fossil.popupwidget.js
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -855,11 +855,11 @@
855 diff: E('#wikiedit-tab-diff'),
856 misc: E('#wikiedit-tab-misc')
857 //commit: E('#wikiedit-tab-commit')
858 }
859 };
860 P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer));
861 P.tabs.e.container.insertBefore(
862 /* Move the status bar between the tab buttons and
863 tab panels. Seems to be the best fit in terms of
864 functionality and visibility. */
865 E('#fossil-status-bar'), P.tabs.e.tabs
@@ -1322,11 +1322,11 @@
1322 mimetype: mimetype,
1323 element: P.e.previewTarget
1324 });
1325 },
1326 onerror: (e)=>{
1327 fossil.fetch.onerror(e);
1328 callback("Error fetching preview: "+e);
1329 }
1330 });
1331 return this;
1332 };
1333
1334 DDED src/fossil.popupwidget.js
--- src/fossil.page.wikiedit.js
+++ src/fossil.page.wikiedit.js
@@ -855,11 +855,11 @@
855 diff: E('#wikiedit-tab-diff'),
856 misc: E('#wikiedit-tab-misc')
857 //commit: E('#wikiedit-tab-commit')
858 }
859 };
860 P.tabs = new F.TabManager(D.clearElement(P.e.tabContainer));
861 P.tabs.e.container.insertBefore(
862 /* Move the status bar between the tab buttons and
863 tab panels. Seems to be the best fit in terms of
864 functionality and visibility. */
865 E('#fossil-status-bar'), P.tabs.e.tabs
@@ -1322,11 +1322,11 @@
1322 mimetype: mimetype,
1323 element: P.e.previewTarget
1324 });
1325 },
1326 onerror: (e)=>{
1327 F.fetch.onerror(e);
1328 callback("Error fetching preview: "+e);
1329 }
1330 });
1331 return this;
1332 };
1333
1334 DDED src/fossil.popupwidget.js
--- a/src/fossil.popupwidget.js
+++ b/src/fossil.popupwidget.js
@@ -0,0 +1,40 @@
1
+(function(F/*fossil object*/){
2
+ 30 */
3
+ installClickToHiden or basic user intera(function(F/*fossil object*/){
4
+ 3000sClass = cssClass;
5
+ unct}, true);isplay basic information ClickToHide(nction(F/*fossil object*/fossil object*/){
6
+ 3000sClass = cssClass;
7
+ unction(F/*fossil object*/){
8
+ /**
9
+ A very basic tooltip-like widget. It's intended to be popped up
10
+ to display basic information or basic user interaction
11
+ components, e.g. a cop or movopy-to-clipboard butt
12
+ if needed,l.dom
13
+ */
14
+ const D = F.dom
15
+ base DOMwidget using the
16
+). If theallback whic
17
+ h is called just before*/){
18
+ 30 */
19
+ instfunction(F/*fossil object*/){
20
+ 30 */
21
+ installClickToHiden or basic user intera(function(F/*fossil object*/){
22
+ 3000sClass = cssClass;
23
+ unct}, true);isplay basic information ClickToHide(nction(F/*fossil object*/fossil object*/){
24
+ 3000sClass = cssClass;
25
+ unction(F/*fossil object*/){
26
+ /**
27
+ A very basic tooltip-like widget. It's intended to be popped up
28
+ to display basic information or basic user interaction
29
+ components, e.g. a copy-to-clipboard button.
30
+
31
+ Requires: fossil.bootstrap, fossil.dom
32
+ */
33
+ conbjecthe popup when either
34
+ call this
35
+ show(falseshow(falseshow(falseconst hide Just be careful to mess only with the X coordinate
36
+ and the width. The browser will try to keep the widget
37
+ from being truncated off-screen on the right, shifting it
38
+ to the left if needed, and we cannot generically be sure
39
+ that an enforced fully on-screen size will actually fit
40
+ the current help textclickHandler){const rect1unction(F/*fossil object*(function(F/*fossil object*/){})(window.fossil);
--- a/src/fossil.popupwidget.js
+++ b/src/fossil.popupwidget.js
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/fossil.popupwidget.js
+++ b/src/fossil.popupwidget.js
@@ -0,0 +1,40 @@
1 (function(F/*fossil object*/){
2 30 */
3 installClickToHiden or basic user intera(function(F/*fossil object*/){
4 3000sClass = cssClass;
5 unct}, true);isplay basic information ClickToHide(nction(F/*fossil object*/fossil object*/){
6 3000sClass = cssClass;
7 unction(F/*fossil object*/){
8 /**
9 A very basic tooltip-like widget. It's intended to be popped up
10 to display basic information or basic user interaction
11 components, e.g. a cop or movopy-to-clipboard butt
12 if needed,l.dom
13 */
14 const D = F.dom
15 base DOMwidget using the
16 ). If theallback whic
17 h is called just before*/){
18 30 */
19 instfunction(F/*fossil object*/){
20 30 */
21 installClickToHiden or basic user intera(function(F/*fossil object*/){
22 3000sClass = cssClass;
23 unct}, true);isplay basic information ClickToHide(nction(F/*fossil object*/fossil object*/){
24 3000sClass = cssClass;
25 unction(F/*fossil object*/){
26 /**
27 A very basic tooltip-like widget. It's intended to be popped up
28 to display basic information or basic user interaction
29 components, e.g. a copy-to-clipboard button.
30
31 Requires: fossil.bootstrap, fossil.dom
32 */
33 conbjecthe popup when either
34 call this
35 show(falseshow(falseshow(falseconst hide Just be careful to mess only with the X coordinate
36 and the width. The browser will try to keep the widget
37 from being truncated off-screen on the right, shifting it
38 to the left if needed, and we cannot generically be sure
39 that an enforced fully on-screen size will actually fit
40 the current help textclickHandler){const rect1unction(F/*fossil object*(function(F/*fossil object*/){})(window.fossil);
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -89,11 +89,11 @@
8989
page-instance-local proxy, if neither one is availble.
9090
9191
Which exact storage implementation is uses is unspecified, and
9292
apps must not rely on it.
9393
*/
94
- fossil.storage = {
94
+ F.storage = {
9595
storageKeyPrefix: storageKeyPrefix,
9696
/** Sets the storage key k to value v, implicitly converting
9797
it to a string. */
9898
set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
9999
/** Sets storage key k to JSON.stringify(v). */
100100
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -89,11 +89,11 @@
89 page-instance-local proxy, if neither one is availble.
90
91 Which exact storage implementation is uses is unspecified, and
92 apps must not rely on it.
93 */
94 fossil.storage = {
95 storageKeyPrefix: storageKeyPrefix,
96 /** Sets the storage key k to value v, implicitly converting
97 it to a string. */
98 set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
99 /** Sets storage key k to JSON.stringify(v). */
100
--- src/fossil.storage.js
+++ src/fossil.storage.js
@@ -89,11 +89,11 @@
89 page-instance-local proxy, if neither one is availble.
90
91 Which exact storage implementation is uses is unspecified, and
92 apps must not rely on it.
93 */
94 F.storage = {
95 storageKeyPrefix: storageKeyPrefix,
96 /** Sets the storage key k to value v, implicitly converting
97 it to a string. */
98 set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
99 /** Sets storage key k to JSON.stringify(v). */
100
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -30,42 +30,28 @@
3030
tabAccessKeys: true
3131
};
3232
3333
/**
3434
Internal helper to normalize a method argument to a tab
35
- element. arg may be a tab DOM element or an index into
36
- tabMgr.e.tabs.childNodes. Returns the corresponding tab element.
35
+ element. arg may be a tab DOM element, a selector string, or an
36
+ index into tabMgr.e.tabs.childNodes. Returns the corresponding
37
+ tab element.
3738
*/
3839
const tabArg = function(arg,tabMgr){
3940
if('string'===typeof arg) arg = E(arg);
4041
else if(tabMgr && 'number'===typeof arg && arg>=0){
4142
arg = tabMgr.e.tabs.childNodes[arg];
4243
}
43
- if(arg){
44
- if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){
45
- arg = arg.firstElementChild;
46
- }
47
- }
4844
return arg;
4945
};
5046
5147
/**
5248
Sets sets the visibility of tab element e to on or off. e MUST be
53
- a TabManager tab element which has been wrapped in a
54
- FIELDSET.tab-wrapper parent element. We disable the hidden
55
- FIELDSET.tab-wrapper elements so that any access keys assigned
56
- to their children cannot be inadvertently triggered
49
+ a TabManager tab element.
5750
*/
5851
const setVisible = function(e,yes){
59
- const fsWrapper = e.parentElement/*FIELDSET wrapper*/;
60
- if(yes){
61
- D.removeClass(e, 'hidden');
62
- D.enable(fsWrapper);
63
- }else{
64
- D.addClass(e, 'hidden');
65
- D.disable(fsWrapper);
66
- }
52
+ D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
6753
};
6854
6955
TabManager.prototype = {
7056
/**
7157
Initializes the tabs associated with the given tab container
@@ -164,13 +150,11 @@
164150
e.target.$manager.switchToTab(e.target.$tab);
165151
};
166152
}
167153
tab = tabArg(tab);
168154
tab.remove();
169
- const eFs = D.addClass(D.fieldset(), 'tab-wrapper');
170
- D.append(eFs, D.addClass(tab,'tab-panel'));
171
- D.append(this.e.tabs, eFs);
155
+ D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
172156
const tabCount = this.e.tabBar.childNodes.length+1;
173157
const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount;
174158
const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
175159
D.append(this.e.tabBar,btn);
176160
btn.$manager = this;
@@ -238,11 +222,10 @@
238222
this._dispatchEvent('before-switch-from', this._currentTab);
239223
}
240224
delete this._currentTab;
241225
this.e.tabs.childNodes.forEach((e,ndx)=>{
242226
const btn = this.e.tabBar.childNodes[ndx];
243
- e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */;
244227
if(e===tab){
245228
if(D.hasClass(e,'selected')){
246229
return;
247230
}
248231
self._dispatchEvent('before-switch-to',tab);
249232
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -30,42 +30,28 @@
30 tabAccessKeys: true
31 };
32
33 /**
34 Internal helper to normalize a method argument to a tab
35 element. arg may be a tab DOM element or an index into
36 tabMgr.e.tabs.childNodes. Returns the corresponding tab element.
 
37 */
38 const tabArg = function(arg,tabMgr){
39 if('string'===typeof arg) arg = E(arg);
40 else if(tabMgr && 'number'===typeof arg && arg>=0){
41 arg = tabMgr.e.tabs.childNodes[arg];
42 }
43 if(arg){
44 if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){
45 arg = arg.firstElementChild;
46 }
47 }
48 return arg;
49 };
50
51 /**
52 Sets sets the visibility of tab element e to on or off. e MUST be
53 a TabManager tab element which has been wrapped in a
54 FIELDSET.tab-wrapper parent element. We disable the hidden
55 FIELDSET.tab-wrapper elements so that any access keys assigned
56 to their children cannot be inadvertently triggered
57 */
58 const setVisible = function(e,yes){
59 const fsWrapper = e.parentElement/*FIELDSET wrapper*/;
60 if(yes){
61 D.removeClass(e, 'hidden');
62 D.enable(fsWrapper);
63 }else{
64 D.addClass(e, 'hidden');
65 D.disable(fsWrapper);
66 }
67 };
68
69 TabManager.prototype = {
70 /**
71 Initializes the tabs associated with the given tab container
@@ -164,13 +150,11 @@
164 e.target.$manager.switchToTab(e.target.$tab);
165 };
166 }
167 tab = tabArg(tab);
168 tab.remove();
169 const eFs = D.addClass(D.fieldset(), 'tab-wrapper');
170 D.append(eFs, D.addClass(tab,'tab-panel'));
171 D.append(this.e.tabs, eFs);
172 const tabCount = this.e.tabBar.childNodes.length+1;
173 const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount;
174 const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
175 D.append(this.e.tabBar,btn);
176 btn.$manager = this;
@@ -238,11 +222,10 @@
238 this._dispatchEvent('before-switch-from', this._currentTab);
239 }
240 delete this._currentTab;
241 this.e.tabs.childNodes.forEach((e,ndx)=>{
242 const btn = this.e.tabBar.childNodes[ndx];
243 e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */;
244 if(e===tab){
245 if(D.hasClass(e,'selected')){
246 return;
247 }
248 self._dispatchEvent('before-switch-to',tab);
249
--- src/fossil.tabs.js
+++ src/fossil.tabs.js
@@ -30,42 +30,28 @@
30 tabAccessKeys: true
31 };
32
33 /**
34 Internal helper to normalize a method argument to a tab
35 element. arg may be a tab DOM element, a selector string, or an
36 index into tabMgr.e.tabs.childNodes. Returns the corresponding
37 tab element.
38 */
39 const tabArg = function(arg,tabMgr){
40 if('string'===typeof arg) arg = E(arg);
41 else if(tabMgr && 'number'===typeof arg && arg>=0){
42 arg = tabMgr.e.tabs.childNodes[arg];
43 }
 
 
 
 
 
44 return arg;
45 };
46
47 /**
48 Sets sets the visibility of tab element e to on or off. e MUST be
49 a TabManager tab element.
 
 
 
50 */
51 const setVisible = function(e,yes){
52 D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
 
 
 
 
 
 
 
53 };
54
55 TabManager.prototype = {
56 /**
57 Initializes the tabs associated with the given tab container
@@ -164,13 +150,11 @@
150 e.target.$manager.switchToTab(e.target.$tab);
151 };
152 }
153 tab = tabArg(tab);
154 tab.remove();
155 D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
 
 
156 const tabCount = this.e.tabBar.childNodes.length+1;
157 const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount;
158 const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
159 D.append(this.e.tabBar,btn);
160 btn.$manager = this;
@@ -238,11 +222,10 @@
222 this._dispatchEvent('before-switch-from', this._currentTab);
223 }
224 delete this._currentTab;
225 this.e.tabs.childNodes.forEach((e,ndx)=>{
226 const btn = this.e.tabBar.childNodes[ndx];
 
227 if(e===tab){
228 if(D.hasClass(e,'selected')){
229 return;
230 }
231 self._dispatchEvent('before-switch-to',tab);
232
+8
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123123
** If N==0, then there is no expectation of new artifacts arriving
124124
** soon and so post-receive hooks can be run without delay.
125125
*/
126126
void hook_expecting_more_artifacts(int N){
127127
if( N>0 ){
128
+ db_unprotect(PROTECT_CONFIG);
128129
db_multi_exec(
129130
"REPLACE INTO config(name,value,mtime)"
130131
"VALUES('hook-embargo',now()+%d,now())",
131132
N
132133
);
134
+ db_protect_pop();
133135
}else{
134136
db_unset("hook-embargo",0);
135137
}
136138
}
137139
@@ -243,10 +245,11 @@
243245
fossil_fatal("the --command and --type options are required");
244246
}
245247
validate_type(zType);
246248
nSeq = zSeq ? atoi(zSeq) : 10;
247249
db_begin_write();
250
+ db_unprotect(PROTECT_CONFIG);
248251
db_multi_exec(
249252
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
250253
"UPDATE config"
251254
" SET value=json_insert("
252255
" CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
253256
" json_object('cmd',%Q,'type',%Q,'seq',%d)),"
254257
" mtime=now()"
255258
" WHERE name='hooks';",
256259
zCmd, zType, nSeq
257260
);
261
+ db_protect_pop();
258262
db_commit_transaction();
259263
}else
260264
if( strncmp(zCmd, "edit", nCmd)==0 ){
261265
const char *zCmd = find_option("command",0,1);
262266
const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
290294
}
291295
if( zSeq ){
292296
blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
293297
}
294298
blob_append_sql(&sql,") WHERE name='hooks';");
299
+ db_unprotect(PROTECT_CONFIG);
295300
db_multi_exec("%s", blob_sql_text(&sql));
301
+ db_protect_pop();
296302
blob_reset(&sql);
297303
}
298304
db_commit_transaction();
299305
}else
300306
if( strncmp(zCmd, "delete", nCmd)==0 ){
301307
int i;
302308
verify_all_options();
303309
if( g.argc<4 ) usage("delete ID ...");
304310
db_begin_write();
311
+ db_unprotect(PROTECT_CONFIG);
305312
db_multi_exec(
306313
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
307314
);
308315
for(i=3; i<g.argc; i++){
309316
const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
321328
" mtime=now()"
322329
" WHERE name='hooks';",
323330
atoi(zId)
324331
);
325332
}
333
+ db_protect_pop();
326334
db_commit_transaction();
327335
}else
328336
if( strncmp(zCmd, "list", nCmd)==0 ){
329337
Stmt q;
330338
int n = 0;
331339
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123 ** If N==0, then there is no expectation of new artifacts arriving
124 ** soon and so post-receive hooks can be run without delay.
125 */
126 void hook_expecting_more_artifacts(int N){
127 if( N>0 ){
 
128 db_multi_exec(
129 "REPLACE INTO config(name,value,mtime)"
130 "VALUES('hook-embargo',now()+%d,now())",
131 N
132 );
 
133 }else{
134 db_unset("hook-embargo",0);
135 }
136 }
137
@@ -243,10 +245,11 @@
243 fossil_fatal("the --command and --type options are required");
244 }
245 validate_type(zType);
246 nSeq = zSeq ? atoi(zSeq) : 10;
247 db_begin_write();
 
248 db_multi_exec(
249 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
250 "UPDATE config"
251 " SET value=json_insert("
252 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
253 " json_object('cmd',%Q,'type',%Q,'seq',%d)),"
254 " mtime=now()"
255 " WHERE name='hooks';",
256 zCmd, zType, nSeq
257 );
 
258 db_commit_transaction();
259 }else
260 if( strncmp(zCmd, "edit", nCmd)==0 ){
261 const char *zCmd = find_option("command",0,1);
262 const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
290 }
291 if( zSeq ){
292 blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
293 }
294 blob_append_sql(&sql,") WHERE name='hooks';");
 
295 db_multi_exec("%s", blob_sql_text(&sql));
 
296 blob_reset(&sql);
297 }
298 db_commit_transaction();
299 }else
300 if( strncmp(zCmd, "delete", nCmd)==0 ){
301 int i;
302 verify_all_options();
303 if( g.argc<4 ) usage("delete ID ...");
304 db_begin_write();
 
305 db_multi_exec(
306 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
307 );
308 for(i=3; i<g.argc; i++){
309 const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
321 " mtime=now()"
322 " WHERE name='hooks';",
323 atoi(zId)
324 );
325 }
 
326 db_commit_transaction();
327 }else
328 if( strncmp(zCmd, "list", nCmd)==0 ){
329 Stmt q;
330 int n = 0;
331
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123 ** If N==0, then there is no expectation of new artifacts arriving
124 ** soon and so post-receive hooks can be run without delay.
125 */
126 void hook_expecting_more_artifacts(int N){
127 if( N>0 ){
128 db_unprotect(PROTECT_CONFIG);
129 db_multi_exec(
130 "REPLACE INTO config(name,value,mtime)"
131 "VALUES('hook-embargo',now()+%d,now())",
132 N
133 );
134 db_protect_pop();
135 }else{
136 db_unset("hook-embargo",0);
137 }
138 }
139
@@ -243,10 +245,11 @@
245 fossil_fatal("the --command and --type options are required");
246 }
247 validate_type(zType);
248 nSeq = zSeq ? atoi(zSeq) : 10;
249 db_begin_write();
250 db_unprotect(PROTECT_CONFIG);
251 db_multi_exec(
252 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
253 "UPDATE config"
254 " SET value=json_insert("
255 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
256 " json_object('cmd',%Q,'type',%Q,'seq',%d)),"
257 " mtime=now()"
258 " WHERE name='hooks';",
259 zCmd, zType, nSeq
260 );
261 db_protect_pop();
262 db_commit_transaction();
263 }else
264 if( strncmp(zCmd, "edit", nCmd)==0 ){
265 const char *zCmd = find_option("command",0,1);
266 const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
294 }
295 if( zSeq ){
296 blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
297 }
298 blob_append_sql(&sql,") WHERE name='hooks';");
299 db_unprotect(PROTECT_CONFIG);
300 db_multi_exec("%s", blob_sql_text(&sql));
301 db_protect_pop();
302 blob_reset(&sql);
303 }
304 db_commit_transaction();
305 }else
306 if( strncmp(zCmd, "delete", nCmd)==0 ){
307 int i;
308 verify_all_options();
309 if( g.argc<4 ) usage("delete ID ...");
310 db_begin_write();
311 db_unprotect(PROTECT_CONFIG);
312 db_multi_exec(
313 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
314 );
315 for(i=3; i<g.argc; i++){
316 const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
328 " mtime=now()"
329 " WHERE name='hooks';",
330 atoi(zId)
331 );
332 }
333 db_protect_pop();
334 db_commit_transaction();
335 }else
336 if( strncmp(zCmd, "list", nCmd)==0 ){
337 Stmt q;
338 int n = 0;
339
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576576
Blob sql;
577577
char *zSep = "(";
578578
db_begin_transaction();
579579
blob_init(&sql, 0, 0);
580580
if( g.argc==4 && find_option("all",0,0)!=0 ){
581
+ db_unprotect(PROTECT_CONFIG);
581582
blob_append_sql(&sql,
582583
"DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
583584
"DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
584585
"DELETE FROM config WHERE name GLOB 'cert:*';\n"
585586
"DELETE FROM config WHERE name GLOB 'trusted:*';\n"
586587
);
588
+ db_protect_pop();
587589
}else{
588590
if( g.argc<4 ){
589591
usage("remove-exception DOMAIN-NAME ...");
590592
}
591593
blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
592594
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576 Blob sql;
577 char *zSep = "(";
578 db_begin_transaction();
579 blob_init(&sql, 0, 0);
580 if( g.argc==4 && find_option("all",0,0)!=0 ){
 
581 blob_append_sql(&sql,
582 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
583 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
584 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
585 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
586 );
 
587 }else{
588 if( g.argc<4 ){
589 usage("remove-exception DOMAIN-NAME ...");
590 }
591 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
592
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576 Blob sql;
577 char *zSep = "(";
578 db_begin_transaction();
579 blob_init(&sql, 0, 0);
580 if( g.argc==4 && find_option("all",0,0)!=0 ){
581 db_unprotect(PROTECT_CONFIG);
582 blob_append_sql(&sql,
583 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
584 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
585 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
586 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
587 );
588 db_protect_pop();
589 }else{
590 if( g.argc<4 ){
591 usage("remove-exception DOMAIN-NAME ...");
592 }
593 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
594
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
17591759
if( forceFlag ) file_delete(g.argv[2]);
17601760
db_create_repository(g.argv[2]);
17611761
}
17621762
db_open_repository(g.argv[2]);
17631763
db_open_config(0, 0);
1764
+ db_unprotect(PROTECT_ALL);
17641765
17651766
db_begin_transaction();
17661767
if( !incrFlag ){
17671768
db_initial_setup(0, 0, zDefaultUser);
17681769
db_set("main-branch", gimport.zTrunkName, 0);
17691770
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
1759 if( forceFlag ) file_delete(g.argv[2]);
1760 db_create_repository(g.argv[2]);
1761 }
1762 db_open_repository(g.argv[2]);
1763 db_open_config(0, 0);
 
1764
1765 db_begin_transaction();
1766 if( !incrFlag ){
1767 db_initial_setup(0, 0, zDefaultUser);
1768 db_set("main-branch", gimport.zTrunkName, 0);
1769
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
1759 if( forceFlag ) file_delete(g.argv[2]);
1760 db_create_repository(g.argv[2]);
1761 }
1762 db_open_repository(g.argv[2]);
1763 db_open_config(0, 0);
1764 db_unprotect(PROTECT_ALL);
1765
1766 db_begin_transaction();
1767 if( !incrFlag ){
1768 db_initial_setup(0, 0, zDefaultUser);
1769 db_set("main-branch", gimport.zTrunkName, 0);
1770
+140 -47
--- src/info.c
+++ src/info.c
@@ -2011,27 +2011,37 @@
20112011
manifest_destroy(pManifest);
20122012
return rid;
20132013
}
20142014
20152015
/*
2016
-** The "z" argument is a string that contains the text of a source code
2017
-** file. This routine appends that text to the HTTP reply with line numbering.
2016
+** The "z" argument is a string that contains the text of a source
2017
+** code file and nZ is its length in bytes. This routine appends that
2018
+** text to the HTTP reply with line numbering.
2019
+**
2020
+** zName is the content's file name, if any (it may be NULL). If that
2021
+** name contains a '.' then the part after the final '.' is used as
2022
+** the X part of a "language-X" CSS class on the generated CODE block.
20182023
**
20192024
** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
20202025
** then highlight that line number and scroll to it once the page loads.
20212026
** If there are two line numbers, highlight the range of lines.
20222027
** Multiple ranges can be highlighed by adding additional line numbers
20232028
** separated by a non-digit character (also not one of [-,.]).
20242029
*/
20252030
void output_text_with_line_numbers(
20262031
const char *z,
2032
+ int nZ,
2033
+ const char *zName,
20272034
const char *zLn
20282035
){
20292036
int iStart, iEnd; /* Start and end of region to highlight */
20302037
int n = 0; /* Current line number */
20312038
int i = 0; /* Loop index */
20322039
int iTop = 0; /* Scroll so that this line is on top of screen. */
2040
+ int nLine = 0; /* content line count */
2041
+ int nSpans = 0; /* number of distinct zLn spans */
2042
+ const char *zExt = file_extension(zName);
20332043
Stmt q;
20342044
20352045
iStart = iEnd = atoi(zLn);
20362046
db_multi_exec(
20372047
"CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2047,56 +2057,101 @@
20472057
while( fossil_isdigit(zLn[i]) ) i++;
20482058
if( iEnd<iStart ) iEnd = iStart;
20492059
db_multi_exec(
20502060
"INSERT OR REPLACE INTO lnos VALUES(%d,%d)", iStart, iEnd
20512061
);
2062
+ ++nSpans;
20522063
iStart = iEnd = atoi(&zLn[i++]);
20532064
}while( zLn[i] && iStart && iEnd );
20542065
}
2055
- db_prepare(&q, "SELECT min(iStart), max(iEnd) FROM lnos");
2056
- if( db_step(&q)==SQLITE_ROW ){
2057
- iStart = db_column_int(&q, 0);
2058
- iEnd = db_column_int(&q, 1);
2059
- iTop = iStart - 15 + (iEnd-iStart)/4;
2060
- if( iTop>iStart - 2 ) iTop = iStart-2;
2061
- }
2062
- db_finalize(&q);
2063
- @ <pre>
2064
- while( z[0] ){
2065
- n++;
2066
- db_prepare(&q,
2067
- "SELECT min(iStart), max(iEnd) FROM lnos"
2068
- " WHERE iStart <= %d AND iEnd >= %d", n, n);
2069
- if( db_step(&q)==SQLITE_ROW ){
2070
- iStart = db_column_int(&q, 0);
2071
- iEnd = db_column_int(&q, 1);
2072
- }
2073
- db_finalize(&q);
2074
- for(i=0; z[i] && z[i]!='\n'; i++){}
2075
- if( n==iTop ) cgi_append_content("<span id=\"scrollToMe\">", -1);
2076
- if( n==iStart ){
2077
- cgi_append_content("<div class=\"selectedText\">",-1);
2078
- }
2079
- cgi_printf("%6d ", n);
2080
- if( i>0 ){
2081
- char *zHtml = htmlize(z, i);
2082
- cgi_append_content(zHtml, -1);
2083
- fossil_free(zHtml);
2084
- }
2085
- if( n==iTop ) cgi_append_content("</span>", -1);
2086
- if( n==iEnd ) cgi_append_content("</div>", -1);
2087
- else cgi_append_content("\n", 1);
2088
- z += i;
2089
- if( z[0]=='\n' ) z++;
2090
- }
2091
- if( n<iEnd ) cgi_printf("</div>");
2092
- @ </pre>
2066
+ /*cgi_printf("<!-- ln span count=%d -->", nSpans);*/
2067
+ cgi_append_content("<table class='numbered-lines'><tbody>"
2068
+ "<tr><td class='line-numbers'>", -1);
2069
+ iStart = iEnd = 0;
2070
+ count_lines(z, nZ, &nLine);
2071
+ for( n=1 ; n<=nLine; ++n ){
2072
+ const char * zAttr = "";
2073
+ const char * zId = "";
2074
+ if(nSpans>0 && iEnd==0){/*Grab the next range of zLn marking*/
2075
+ db_prepare(&q, "SELECT iStart, iEnd FROM lnos "
2076
+ "WHERE iStart >= %d ORDER BY iStart", n);
2077
+ if( db_step(&q)==SQLITE_ROW ){
2078
+ iStart = db_column_int(&q, 0);
2079
+ iEnd = db_column_int(&q, 1);
2080
+ if(!iTop){
2081
+ iTop = iStart - 15 + (iEnd-iStart)/4;
2082
+ if( iTop>iStart - 2 ) iTop = iStart-2;
2083
+ }
2084
+ }else{
2085
+ /* Note that overlapping multi-spans, e.g. 10-15+12-20,
2086
+ can cause us to miss a row. */
2087
+ iStart = iEnd = 0;
2088
+ }
2089
+ db_finalize(&q);
2090
+ --nSpans;
2091
+ /*cgi_printf("<!-- iStart=%d, iEnd=%d -->", iStart, iEnd);*/
2092
+ }
2093
+ if(n==iTop) {
2094
+ zId = " id='scrollToMe'";
2095
+ }
2096
+ if(n==iStart){/*Figure out which CSS class(es) this line needs...*/
2097
+ if(n==iEnd){
2098
+ zAttr = " class='selected-line start end'";
2099
+ iEnd = 0;
2100
+ }else{
2101
+ zAttr = " class='selected-line start'";
2102
+ }
2103
+ iStart = 0;
2104
+ }else if(n==iEnd){
2105
+ zAttr = " class='selected-line end'";
2106
+ iEnd = 0;
2107
+ }else if( n>iStart && n<iEnd ){
2108
+ zAttr = " class='selected-line'";
2109
+ }
2110
+ cgi_printf("<span%s%s>%6d</span>", zId, zAttr, n);
2111
+ }
2112
+ cgi_append_content("</td><td class='file-content'><pre>",-1);
2113
+ if(zExt && *zExt){
2114
+ cgi_printf("<code class='language-%h'>",zExt);
2115
+ }else{
2116
+ cgi_append_content("<code>", -1);
2117
+ }
2118
+ cgi_printf("%z", htmlize(z, nZ));
2119
+ CX("</code></pre></td></tr></tbody></table>\n");
20932120
if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
20942121
builtin_request_js("scroll.js");
20952122
}
2123
+ style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget",
2124
+ "numbered-lines", 0);
20962125
}
20972126
2127
+/*
2128
+** COMMAND: test-line-numbers
2129
+**
2130
+** Usage: %fossil test-line-numbers FILE ?LN-SPEC?
2131
+**
2132
+*/
2133
+void cmd_test_line_numbers(void){
2134
+ Blob content = empty_blob;
2135
+ const char * zLn = "";
2136
+ const char * zFilename = 0;
2137
+
2138
+ if(g.argc < 3){
2139
+ usage("FILE");
2140
+ }else if(g.argc>3){
2141
+ zLn = g.argv[3];
2142
+ }
2143
+ db_find_and_open_repository(0,0);
2144
+ zFilename = g.argv[2];
2145
+ fossil_print("%s %s\n", zFilename, zLn);
2146
+
2147
+ blob_read_from_file(&content, zFilename, ExtFILE);
2148
+ output_text_with_line_numbers(blob_str(&content), blob_size(&content),
2149
+ zFilename, zLn);
2150
+ blob_reset(&content);
2151
+ fossil_print("%b\n", cgi_output_blob());
2152
+}
20982153
20992154
/*
21002155
** WEBPAGE: artifact
21012156
** WEBPAGE: file
21022157
** WEBPAGE: whatis
@@ -2251,17 +2306,19 @@
22512306
const char *zPath;
22522307
Blob path;
22532308
blob_zero(&path);
22542309
hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO);
22552310
zPath = blob_str(&path);
2256
- @ <h2>File %s(zPath) \
2311
+ @ <h2>File %s(zPath) artifact \
2312
+ style_copy_button(1,"hash-fid",0,0,"%z%S</a> ",
2313
+ href("%R/info/%s",zUuid),zUuid);
22572314
if( isBranchCI ){
22582315
@ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
22592316
}else if( isSymbolicCI ){
2260
- @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
2317
+ @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
22612318
}else{
2262
- @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
2319
+ @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
22632320
}
22642321
blob_reset(&path);
22652322
}
22662323
style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
22672324
style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
@@ -2387,25 +2444,26 @@
23872444
if( zLn==0 || atoi(zLn)==0 ){
23882445
style_submenu_checkbox("ln", "Line Numbers", 0, 0);
23892446
}
23902447
blob_to_utf8_no_bom(&content, 0);
23912448
zMime = mimetype_from_content(&content);
2392
- @ <blockquote>
2449
+ @ <blockquote class="file-content">
23932450
if( zMime==0 ){
23942451
const char *z, *zFileName, *zExt;
23952452
z = blob_str(&content);
23962453
zFileName = db_text(0,
23972454
"SELECT name FROM mlink, filename"
23982455
" WHERE filename.fnid=mlink.fnid"
23992456
" AND mlink.fid=%d",
24002457
rid);
2401
- zExt = zFileName ? strrchr(zFileName, '.') : 0;
2458
+ zExt = zFileName ? file_extension(zFileName) : 0;
24022459
if( zLn ){
2403
- output_text_with_line_numbers(z, zLn);
2460
+ output_text_with_line_numbers(z, blob_size(&content),
2461
+ zFileName, zLn);
24042462
}else if( zExt && zExt[1] ){
24052463
@ <pre>
2406
- @ <code class="language-%s(zExt+1)">%h(z)</code>
2464
+ @ <code class="language-%s(zExt)">%h(z)</code>
24072465
@ </pre>
24082466
}else{
24092467
@ <pre>
24102468
@ %h(z)
24112469
@ </pre>
@@ -3343,5 +3401,40 @@
33433401
}
33443402
if( g.localOpen ){
33453403
manifest_to_disk(rid);
33463404
}
33473405
}
3406
+
3407
+
3408
+/*
3409
+** COMMAND: test-symlink-list
3410
+**
3411
+** Show all symlinks that have been checked into a Fossil repository.
3412
+**
3413
+** This command does a linear scan through all check-ins and so might take
3414
+** several seconds on a large repository.
3415
+*/
3416
+void test_symlink_list_cmd(void){
3417
+ Stmt q;
3418
+ db_find_and_open_repository(0,0);
3419
+ add_content_sql_commands(g.db);
3420
+ db_prepare(&q,
3421
+ "SELECT min(date(e.mtime)),"
3422
+ " b.uuid,"
3423
+ " f.filename,"
3424
+ " content(f.uuid)"
3425
+ " FROM event AS e, blob AS b, files_of_checkin(b.uuid) AS f"
3426
+ " WHERE e.type='ci'"
3427
+ " AND b.rid=e.objid"
3428
+ " AND f.perm LIKE '%%l%%'"
3429
+ " GROUP BY 3, 4"
3430
+ " ORDER BY 1 DESC"
3431
+ );
3432
+ while( db_step(&q)==SQLITE_ROW ){
3433
+ fossil_print("%s %.16s %s -> %s\n",
3434
+ db_column_text(&q,0),
3435
+ db_column_text(&q,1),
3436
+ db_column_text(&q,2),
3437
+ db_column_text(&q,3));
3438
+ }
3439
+ db_finalize(&q);
3440
+}
33483441
33493442
ADDED src/interwiki.c
--- src/info.c
+++ src/info.c
@@ -2011,27 +2011,37 @@
2011 manifest_destroy(pManifest);
2012 return rid;
2013 }
2014
2015 /*
2016 ** The "z" argument is a string that contains the text of a source code
2017 ** file. This routine appends that text to the HTTP reply with line numbering.
 
 
 
 
 
2018 **
2019 ** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
2020 ** then highlight that line number and scroll to it once the page loads.
2021 ** If there are two line numbers, highlight the range of lines.
2022 ** Multiple ranges can be highlighed by adding additional line numbers
2023 ** separated by a non-digit character (also not one of [-,.]).
2024 */
2025 void output_text_with_line_numbers(
2026 const char *z,
 
 
2027 const char *zLn
2028 ){
2029 int iStart, iEnd; /* Start and end of region to highlight */
2030 int n = 0; /* Current line number */
2031 int i = 0; /* Loop index */
2032 int iTop = 0; /* Scroll so that this line is on top of screen. */
 
 
 
2033 Stmt q;
2034
2035 iStart = iEnd = atoi(zLn);
2036 db_multi_exec(
2037 "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2047,56 +2057,101 @@
2047 while( fossil_isdigit(zLn[i]) ) i++;
2048 if( iEnd<iStart ) iEnd = iStart;
2049 db_multi_exec(
2050 "INSERT OR REPLACE INTO lnos VALUES(%d,%d)", iStart, iEnd
2051 );
 
2052 iStart = iEnd = atoi(&zLn[i++]);
2053 }while( zLn[i] && iStart && iEnd );
2054 }
2055 db_prepare(&q, "SELECT min(iStart), max(iEnd) FROM lnos");
2056 if( db_step(&q)==SQLITE_ROW ){
2057 iStart = db_column_int(&q, 0);
2058 iEnd = db_column_int(&q, 1);
2059 iTop = iStart - 15 + (iEnd-iStart)/4;
2060 if( iTop>iStart - 2 ) iTop = iStart-2;
2061 }
2062 db_finalize(&q);
2063 @ <pre>
2064 while( z[0] ){
2065 n++;
2066 db_prepare(&q,
2067 "SELECT min(iStart), max(iEnd) FROM lnos"
2068 " WHERE iStart <= %d AND iEnd >= %d", n, n);
2069 if( db_step(&q)==SQLITE_ROW ){
2070 iStart = db_column_int(&q, 0);
2071 iEnd = db_column_int(&q, 1);
2072 }
2073 db_finalize(&q);
2074 for(i=0; z[i] && z[i]!='\n'; i++){}
2075 if( n==iTop ) cgi_append_content("<span id=\"scrollToMe\">", -1);
2076 if( n==iStart ){
2077 cgi_append_content("<div class=\"selectedText\">",-1);
2078 }
2079 cgi_printf("%6d ", n);
2080 if( i>0 ){
2081 char *zHtml = htmlize(z, i);
2082 cgi_append_content(zHtml, -1);
2083 fossil_free(zHtml);
2084 }
2085 if( n==iTop ) cgi_append_content("</span>", -1);
2086 if( n==iEnd ) cgi_append_content("</div>", -1);
2087 else cgi_append_content("\n", 1);
2088 z += i;
2089 if( z[0]=='\n' ) z++;
2090 }
2091 if( n<iEnd ) cgi_printf("</div>");
2092 @ </pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2093 if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2094 builtin_request_js("scroll.js");
2095 }
 
 
2096 }
2097
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2098
2099 /*
2100 ** WEBPAGE: artifact
2101 ** WEBPAGE: file
2102 ** WEBPAGE: whatis
@@ -2251,17 +2306,19 @@
2251 const char *zPath;
2252 Blob path;
2253 blob_zero(&path);
2254 hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO);
2255 zPath = blob_str(&path);
2256 @ <h2>File %s(zPath) \
 
 
2257 if( isBranchCI ){
2258 @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
2259 }else if( isSymbolicCI ){
2260 @ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
2261 }else{
2262 @ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
2263 }
2264 blob_reset(&path);
2265 }
2266 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2267 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
@@ -2387,25 +2444,26 @@
2387 if( zLn==0 || atoi(zLn)==0 ){
2388 style_submenu_checkbox("ln", "Line Numbers", 0, 0);
2389 }
2390 blob_to_utf8_no_bom(&content, 0);
2391 zMime = mimetype_from_content(&content);
2392 @ <blockquote>
2393 if( zMime==0 ){
2394 const char *z, *zFileName, *zExt;
2395 z = blob_str(&content);
2396 zFileName = db_text(0,
2397 "SELECT name FROM mlink, filename"
2398 " WHERE filename.fnid=mlink.fnid"
2399 " AND mlink.fid=%d",
2400 rid);
2401 zExt = zFileName ? strrchr(zFileName, '.') : 0;
2402 if( zLn ){
2403 output_text_with_line_numbers(z, zLn);
 
2404 }else if( zExt && zExt[1] ){
2405 @ <pre>
2406 @ <code class="language-%s(zExt+1)">%h(z)</code>
2407 @ </pre>
2408 }else{
2409 @ <pre>
2410 @ %h(z)
2411 @ </pre>
@@ -3343,5 +3401,40 @@
3343 }
3344 if( g.localOpen ){
3345 manifest_to_disk(rid);
3346 }
3347 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3348
3349 DDED src/interwiki.c
--- src/info.c
+++ src/info.c
@@ -2011,27 +2011,37 @@
2011 manifest_destroy(pManifest);
2012 return rid;
2013 }
2014
2015 /*
2016 ** The "z" argument is a string that contains the text of a source
2017 ** code file and nZ is its length in bytes. This routine appends that
2018 ** text to the HTTP reply with line numbering.
2019 **
2020 ** zName is the content's file name, if any (it may be NULL). If that
2021 ** name contains a '.' then the part after the final '.' is used as
2022 ** the X part of a "language-X" CSS class on the generated CODE block.
2023 **
2024 ** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
2025 ** then highlight that line number and scroll to it once the page loads.
2026 ** If there are two line numbers, highlight the range of lines.
2027 ** Multiple ranges can be highlighed by adding additional line numbers
2028 ** separated by a non-digit character (also not one of [-,.]).
2029 */
2030 void output_text_with_line_numbers(
2031 const char *z,
2032 int nZ,
2033 const char *zName,
2034 const char *zLn
2035 ){
2036 int iStart, iEnd; /* Start and end of region to highlight */
2037 int n = 0; /* Current line number */
2038 int i = 0; /* Loop index */
2039 int iTop = 0; /* Scroll so that this line is on top of screen. */
2040 int nLine = 0; /* content line count */
2041 int nSpans = 0; /* number of distinct zLn spans */
2042 const char *zExt = file_extension(zName);
2043 Stmt q;
2044
2045 iStart = iEnd = atoi(zLn);
2046 db_multi_exec(
2047 "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)");
@@ -2047,56 +2057,101 @@
2057 while( fossil_isdigit(zLn[i]) ) i++;
2058 if( iEnd<iStart ) iEnd = iStart;
2059 db_multi_exec(
2060 "INSERT OR REPLACE INTO lnos VALUES(%d,%d)", iStart, iEnd
2061 );
2062 ++nSpans;
2063 iStart = iEnd = atoi(&zLn[i++]);
2064 }while( zLn[i] && iStart && iEnd );
2065 }
2066 /*cgi_printf("<!-- ln span count=%d -->", nSpans);*/
2067 cgi_append_content("<table class='numbered-lines'><tbody>"
2068 "<tr><td class='line-numbers'>", -1);
2069 iStart = iEnd = 0;
2070 count_lines(z, nZ, &nLine);
2071 for( n=1 ; n<=nLine; ++n ){
2072 const char * zAttr = "";
2073 const char * zId = "";
2074 if(nSpans>0 && iEnd==0){/*Grab the next range of zLn marking*/
2075 db_prepare(&q, "SELECT iStart, iEnd FROM lnos "
2076 "WHERE iStart >= %d ORDER BY iStart", n);
2077 if( db_step(&q)==SQLITE_ROW ){
2078 iStart = db_column_int(&q, 0);
2079 iEnd = db_column_int(&q, 1);
2080 if(!iTop){
2081 iTop = iStart - 15 + (iEnd-iStart)/4;
2082 if( iTop>iStart - 2 ) iTop = iStart-2;
2083 }
2084 }else{
2085 /* Note that overlapping multi-spans, e.g. 10-15+12-20,
2086 can cause us to miss a row. */
2087 iStart = iEnd = 0;
2088 }
2089 db_finalize(&q);
2090 --nSpans;
2091 /*cgi_printf("<!-- iStart=%d, iEnd=%d -->", iStart, iEnd);*/
2092 }
2093 if(n==iTop) {
2094 zId = " id='scrollToMe'";
2095 }
2096 if(n==iStart){/*Figure out which CSS class(es) this line needs...*/
2097 if(n==iEnd){
2098 zAttr = " class='selected-line start end'";
2099 iEnd = 0;
2100 }else{
2101 zAttr = " class='selected-line start'";
2102 }
2103 iStart = 0;
2104 }else if(n==iEnd){
2105 zAttr = " class='selected-line end'";
2106 iEnd = 0;
2107 }else if( n>iStart && n<iEnd ){
2108 zAttr = " class='selected-line'";
2109 }
2110 cgi_printf("<span%s%s>%6d</span>", zId, zAttr, n);
2111 }
2112 cgi_append_content("</td><td class='file-content'><pre>",-1);
2113 if(zExt && *zExt){
2114 cgi_printf("<code class='language-%h'>",zExt);
2115 }else{
2116 cgi_append_content("<code>", -1);
2117 }
2118 cgi_printf("%z", htmlize(z, nZ));
2119 CX("</code></pre></td></tr></tbody></table>\n");
2120 if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){
2121 builtin_request_js("scroll.js");
2122 }
2123 style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget",
2124 "numbered-lines", 0);
2125 }
2126
2127 /*
2128 ** COMMAND: test-line-numbers
2129 **
2130 ** Usage: %fossil test-line-numbers FILE ?LN-SPEC?
2131 **
2132 */
2133 void cmd_test_line_numbers(void){
2134 Blob content = empty_blob;
2135 const char * zLn = "";
2136 const char * zFilename = 0;
2137
2138 if(g.argc < 3){
2139 usage("FILE");
2140 }else if(g.argc>3){
2141 zLn = g.argv[3];
2142 }
2143 db_find_and_open_repository(0,0);
2144 zFilename = g.argv[2];
2145 fossil_print("%s %s\n", zFilename, zLn);
2146
2147 blob_read_from_file(&content, zFilename, ExtFILE);
2148 output_text_with_line_numbers(blob_str(&content), blob_size(&content),
2149 zFilename, zLn);
2150 blob_reset(&content);
2151 fossil_print("%b\n", cgi_output_blob());
2152 }
2153
2154 /*
2155 ** WEBPAGE: artifact
2156 ** WEBPAGE: file
2157 ** WEBPAGE: whatis
@@ -2251,17 +2306,19 @@
2306 const char *zPath;
2307 Blob path;
2308 blob_zero(&path);
2309 hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO);
2310 zPath = blob_str(&path);
2311 @ <h2>File %s(zPath) artifact \
2312 style_copy_button(1,"hash-fid",0,0,"%z%S</a> ",
2313 href("%R/info/%s",zUuid),zUuid);
2314 if( isBranchCI ){
2315 @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
2316 }else if( isSymbolicCI ){
2317 @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
2318 }else{
2319 @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
2320 }
2321 blob_reset(&path);
2322 }
2323 style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
2324 style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
@@ -2387,25 +2444,26 @@
2444 if( zLn==0 || atoi(zLn)==0 ){
2445 style_submenu_checkbox("ln", "Line Numbers", 0, 0);
2446 }
2447 blob_to_utf8_no_bom(&content, 0);
2448 zMime = mimetype_from_content(&content);
2449 @ <blockquote class="file-content">
2450 if( zMime==0 ){
2451 const char *z, *zFileName, *zExt;
2452 z = blob_str(&content);
2453 zFileName = db_text(0,
2454 "SELECT name FROM mlink, filename"
2455 " WHERE filename.fnid=mlink.fnid"
2456 " AND mlink.fid=%d",
2457 rid);
2458 zExt = zFileName ? file_extension(zFileName) : 0;
2459 if( zLn ){
2460 output_text_with_line_numbers(z, blob_size(&content),
2461 zFileName, zLn);
2462 }else if( zExt && zExt[1] ){
2463 @ <pre>
2464 @ <code class="language-%s(zExt)">%h(z)</code>
2465 @ </pre>
2466 }else{
2467 @ <pre>
2468 @ %h(z)
2469 @ </pre>
@@ -3343,5 +3401,40 @@
3401 }
3402 if( g.localOpen ){
3403 manifest_to_disk(rid);
3404 }
3405 }
3406
3407
3408 /*
3409 ** COMMAND: test-symlink-list
3410 **
3411 ** Show all symlinks that have been checked into a Fossil repository.
3412 **
3413 ** This command does a linear scan through all check-ins and so might take
3414 ** several seconds on a large repository.
3415 */
3416 void test_symlink_list_cmd(void){
3417 Stmt q;
3418 db_find_and_open_repository(0,0);
3419 add_content_sql_commands(g.db);
3420 db_prepare(&q,
3421 "SELECT min(date(e.mtime)),"
3422 " b.uuid,"
3423 " f.filename,"
3424 " content(f.uuid)"
3425 " FROM event AS e, blob AS b, files_of_checkin(b.uuid) AS f"
3426 " WHERE e.type='ci'"
3427 " AND b.rid=e.objid"
3428 " AND f.perm LIKE '%%l%%'"
3429 " GROUP BY 3, 4"
3430 " ORDER BY 1 DESC"
3431 );
3432 while( db_step(&q)==SQLITE_ROW ){
3433 fossil_print("%s %.16s %s -> %s\n",
3434 db_column_text(&q,0),
3435 db_column_text(&q,1),
3436 db_column_text(&q,2),
3437 db_column_text(&q,3));
3438 }
3439 db_finalize(&q);
3440 }
3441
3442 DDED src/interwiki.c
--- a/src/interwiki.c
+++ b/src/interwiki.c
@@ -0,0 +1,26 @@
1
+/*
2
+** Copyright (c) 2020 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 Ljson_extract(value,'$.base'),"
7
+re; you can redist/*
8
+** Copyright (c) 2020 Dre; you can redist/*
9
+** Copyright (c) 202json_extract(value,'$.base'json_extract(value,'$.hash'json_extract(value,'$.wiki')json_extract(value,'$.base')json_extract(value,'$.base'),"
10
+ " json_extract(value,'$.hash'),"
11
+ " j"interwiki");
12
+}
13
+c) 2020 D. Richard Hipp
14
+**
15
+** This program is free software; you can redistribute it and/or
16
+** modify it under the terms of the Simplified BSD License (also
17
+** known as the "2-Clause Ljson_extract(value,'$.base'),"
18
+re; you can/*
19
+** Copyright (c) 2020 D. Richard Hipp
20
+**
21
+** This program is free software; you can redistribute it and/or
22
+** modify it under the terms of the Simplifi'interwiki:%finalize(&qbody_and_footerbody_and_footer("interwiki");
23
+}
24
+footer(footer();
25
+}
26
+Setup
--- a/src/interwiki.c
+++ b/src/interwiki.c
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/interwiki.c
+++ b/src/interwiki.c
@@ -0,0 +1,26 @@
1 /*
2 ** Copyright (c) 2020 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 Ljson_extract(value,'$.base'),"
7 re; you can redist/*
8 ** Copyright (c) 2020 Dre; you can redist/*
9 ** Copyright (c) 202json_extract(value,'$.base'json_extract(value,'$.hash'json_extract(value,'$.wiki')json_extract(value,'$.base')json_extract(value,'$.base'),"
10 " json_extract(value,'$.hash'),"
11 " j"interwiki");
12 }
13 c) 2020 D. Richard Hipp
14 **
15 ** This program is free software; you can redistribute it and/or
16 ** modify it under the terms of the Simplified BSD License (also
17 ** known as the "2-Clause Ljson_extract(value,'$.base'),"
18 re; you can/*
19 ** Copyright (c) 2020 D. Richard Hipp
20 **
21 ** This program is free software; you can redistribute it and/or
22 ** modify it under the terms of the Simplifi'interwiki:%finalize(&qbody_and_footerbody_and_footer("interwiki");
23 }
24 footer(footer();
25 }
26 Setup
--- src/json_config.c
+++ src/json_config.c
@@ -83,13 +83,10 @@
8383
{ "keep-glob", CONFIGSET_PROJ },
8484
{ "crlf-glob", CONFIGSET_PROJ },
8585
{ "crnl-glob", CONFIGSET_PROJ },
8686
{ "encoding-glob", CONFIGSET_PROJ },
8787
{ "empty-dirs", CONFIGSET_PROJ },
88
-#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
89
-{ "allow-symlinks", CONFIGSET_PROJ },
90
-#endif
9188
{ "dotfiles", CONFIGSET_PROJ },
9289
9390
{ "ticket-table", CONFIGSET_TKT },
9491
{ "ticket-common", CONFIGSET_TKT },
9592
{ "ticket-change", CONFIGSET_TKT },
9693
--- src/json_config.c
+++ src/json_config.c
@@ -83,13 +83,10 @@
83 { "keep-glob", CONFIGSET_PROJ },
84 { "crlf-glob", CONFIGSET_PROJ },
85 { "crnl-glob", CONFIGSET_PROJ },
86 { "encoding-glob", CONFIGSET_PROJ },
87 { "empty-dirs", CONFIGSET_PROJ },
88 #ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS
89 { "allow-symlinks", CONFIGSET_PROJ },
90 #endif
91 { "dotfiles", CONFIGSET_PROJ },
92
93 { "ticket-table", CONFIGSET_TKT },
94 { "ticket-common", CONFIGSET_TKT },
95 { "ticket-change", CONFIGSET_TKT },
96
--- src/json_config.c
+++ src/json_config.c
@@ -83,13 +83,10 @@
83 { "keep-glob", CONFIGSET_PROJ },
84 { "crlf-glob", CONFIGSET_PROJ },
85 { "crnl-glob", CONFIGSET_PROJ },
86 { "encoding-glob", CONFIGSET_PROJ },
87 { "empty-dirs", CONFIGSET_PROJ },
 
 
 
88 { "dotfiles", CONFIGSET_PROJ },
89
90 { "ticket-table", CONFIGSET_TKT },
91 { "ticket-common", CONFIGSET_TKT },
92 { "ticket-change", CONFIGSET_TKT },
93
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212212
json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213213
"User %s already exists.", zName);
214214
goto error;
215215
}else{
216216
Stmt ins = empty_Stmt;
217
+ db_unprotect(PROTECT_USER);
217218
db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
218219
db_step( &ins );
219220
db_finalize(&ins);
221
+ db_protect_pop();
220222
uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
221223
assert(uid>0);
222224
zNameNew = zName;
223225
cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
224226
}
@@ -345,13 +347,15 @@
345347
#endif
346348
#if 0
347349
puts(blob_str(&sql));
348350
cson_output_FILE( cson_object_value(pUser), stdout, NULL );
349351
#endif
352
+ db_unprotect(PROTECT_USER);
350353
db_prepare(&q, "%s", blob_sql_text(&sql));
351354
db_exec(&q);
352355
db_finalize(&q);
356
+ db_protect_pop();
353357
#if TRY_LOGIN_GROUP
354358
if( zPW || cson_value_get_bool(forceLogout) ){
355359
Blob groupSql = empty_blob;
356360
char * zErr = NULL;
357361
blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
358362
"INSERT INTO user(login)"
359363
" SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
360364
zName, zName
361365
);
362366
blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
367
+ db_unprotect(PROTECT_USER);
363368
login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
369
+ db_protect_pop();
364370
blob_reset(&groupSql);
365371
if( zErr ){
366372
json_set_err( FSL_JSON_E_UNKNOWN,
367373
"Repo-group update at least partially failed: %s",
368374
zErr);
369375
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212 json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213 "User %s already exists.", zName);
214 goto error;
215 }else{
216 Stmt ins = empty_Stmt;
 
217 db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
218 db_step( &ins );
219 db_finalize(&ins);
 
220 uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
221 assert(uid>0);
222 zNameNew = zName;
223 cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
224 }
@@ -345,13 +347,15 @@
345 #endif
346 #if 0
347 puts(blob_str(&sql));
348 cson_output_FILE( cson_object_value(pUser), stdout, NULL );
349 #endif
 
350 db_prepare(&q, "%s", blob_sql_text(&sql));
351 db_exec(&q);
352 db_finalize(&q);
 
353 #if TRY_LOGIN_GROUP
354 if( zPW || cson_value_get_bool(forceLogout) ){
355 Blob groupSql = empty_blob;
356 char * zErr = NULL;
357 blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
358 "INSERT INTO user(login)"
359 " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
360 zName, zName
361 );
362 blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
 
363 login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
 
364 blob_reset(&groupSql);
365 if( zErr ){
366 json_set_err( FSL_JSON_E_UNKNOWN,
367 "Repo-group update at least partially failed: %s",
368 zErr);
369
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212 json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213 "User %s already exists.", zName);
214 goto error;
215 }else{
216 Stmt ins = empty_Stmt;
217 db_unprotect(PROTECT_USER);
218 db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
219 db_step( &ins );
220 db_finalize(&ins);
221 db_protect_pop();
222 uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
223 assert(uid>0);
224 zNameNew = zName;
225 cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
226 }
@@ -345,13 +347,15 @@
347 #endif
348 #if 0
349 puts(blob_str(&sql));
350 cson_output_FILE( cson_object_value(pUser), stdout, NULL );
351 #endif
352 db_unprotect(PROTECT_USER);
353 db_prepare(&q, "%s", blob_sql_text(&sql));
354 db_exec(&q);
355 db_finalize(&q);
356 db_protect_pop();
357 #if TRY_LOGIN_GROUP
358 if( zPW || cson_value_get_bool(forceLogout) ){
359 Blob groupSql = empty_blob;
360 char * zErr = NULL;
361 blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
362 "INSERT INTO user(login)"
363 " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
364 zName, zName
365 );
366 blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
367 db_unprotect(PROTECT_USER);
368 login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
369 db_protect_pop();
370 blob_reset(&groupSql);
371 if( zErr ){
372 json_set_err( FSL_JSON_E_UNKNOWN,
373 "Repo-group update at least partially failed: %s",
374 zErr);
375
+3 -2
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -458,12 +458,13 @@
458458
json_set_err(FSL_JSON_E_DENIED,
459459
"Requires 'j' or 'o' permissions.");
460460
return NULL;
461461
}
462462
blob_append(&sql,"SELECT"
463
- " substr(tagname,6) as name"
464
- " FROM tag WHERE tagname GLOB 'wiki-*'",
463
+ " DISTINCT substr(tagname,6) as name"
464
+ " FROM tag JOIN tagxref USING('tagid')"
465
+ " WHERE tagname GLOB 'wiki-*'",
465466
-1);
466467
zGlob = json_find_option_cstr("glob",NULL,"g");
467468
if(zGlob && *zGlob){
468469
blob_append_sql(&sql," AND name %s GLOB %Q",
469470
fInvert ? "NOT" : "", zGlob);
470471
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -458,12 +458,13 @@
458 json_set_err(FSL_JSON_E_DENIED,
459 "Requires 'j' or 'o' permissions.");
460 return NULL;
461 }
462 blob_append(&sql,"SELECT"
463 " substr(tagname,6) as name"
464 " FROM tag WHERE tagname GLOB 'wiki-*'",
 
465 -1);
466 zGlob = json_find_option_cstr("glob",NULL,"g");
467 if(zGlob && *zGlob){
468 blob_append_sql(&sql," AND name %s GLOB %Q",
469 fInvert ? "NOT" : "", zGlob);
470
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -458,12 +458,13 @@
458 json_set_err(FSL_JSON_E_DENIED,
459 "Requires 'j' or 'o' permissions.");
460 return NULL;
461 }
462 blob_append(&sql,"SELECT"
463 " DISTINCT substr(tagname,6) as name"
464 " FROM tag JOIN tagxref USING('tagid')"
465 " WHERE tagname GLOB 'wiki-*'",
466 -1);
467 zGlob = json_find_option_cstr("glob",NULL,"g");
468 if(zGlob && *zGlob){
469 blob_append_sql(&sql," AND name %s GLOB %Q",
470 fInvert ? "NOT" : "", zGlob);
471
+21 -2
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293293
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294294
zCookie = login_gen_user_cookie_value(zUsername, zHash);
295295
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296296
bSessionCookie ? 0 : expires);
297297
record_login_attempt(zUsername, zIpAddr, 1);
298
+ db_unprotect(PROTECT_USER);
298299
db_multi_exec("UPDATE user SET cookie=%Q,"
299300
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300301
zHash, expires, uid);
302
+ db_protect_pop();
301303
fossil_free(zHash);
302304
if( zDest ){
303305
*zDest = zCookie;
304306
}else{
305307
free(zCookie);
@@ -356,14 +358,16 @@
356358
}else{
357359
const char *cookie = login_cookie_name();
358360
/* To logout, change the cookie value to an empty string */
359361
cgi_set_cookie(cookie, "",
360362
login_cookie_path(), -86400);
363
+ db_unprotect(PROTECT_USER);
361364
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
362365
" cexpire=0 WHERE uid=%d"
363366
" AND login NOT IN ('anonymous','nobody',"
364367
" 'developer','reader')", g.userUid);
368
+ db_protect_pop();
365369
cgi_replace_parameter(cookie, NULL);
366370
cgi_replace_parameter("anon", NULL);
367371
}
368372
}
369373
@@ -580,22 +584,27 @@
580584
;
581585
}else{
582586
char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
583587
char *zChngPw;
584588
char *zErr;
589
+ int rc;
590
+
591
+ db_unprotect(PROTECT_USER);
585592
db_multi_exec(
586593
"UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
587594
);
588
- fossil_free(zNewPw);
589595
zChngPw = mprintf(
590596
"UPDATE user"
591597
" SET pw=shared_secret(%Q,%Q,"
592598
" (SELECT value FROM config WHERE name='project-code'))"
593599
" WHERE login=%Q",
594600
zNew1, g.zLogin, g.zLogin
595601
);
596
- if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){
602
+ fossil_free(zNewPw);
603
+ rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr);
604
+ db_protect_pop();
605
+ if( rc ){
597606
zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
598607
fossil_free(zErr);
599608
}else{
600609
redirect_to_g();
601610
return;
@@ -835,16 +844,18 @@
835844
zLogin, zHash
836845
);
837846
pStmt = 0;
838847
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
839848
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
849
+ db_unprotect(PROTECT_USER);
840850
db_multi_exec(
841851
"UPDATE user SET cookie=%Q, cexpire=%.17g"
842852
" WHERE login=%Q",
843853
zHash,
844854
sqlite3_column_double(pStmt, 0), zLogin
845855
);
856
+ db_protect_pop();
846857
nXfer++;
847858
}
848859
sqlite3_finalize(pStmt);
849860
}
850861
sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
16191630
"INSERT INTO user(login,pw,cap,info,mtime)\n"
16201631
"VALUES(%Q,%Q,%Q,"
16211632
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
16221633
zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
16231634
fossil_free(zPass);
1635
+ db_unprotect(PROTECT_USER);
16241636
db_multi_exec("%s", blob_sql_text(&sql));
1637
+ db_protect_pop();
16251638
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
16261639
login_set_user_cookie(zUserID, uid, NULL, 0);
16271640
if( doAlerts ){
16281641
/* Also make the new user a subscriber. */
16291642
Blob hdr, body;
@@ -1832,14 +1845,16 @@
18321845
while( db_step(&q)==SQLITE_ROW ){
18331846
const char *zRepoName = db_column_text(&q, 1);
18341847
if( file_size(zRepoName, ExtFILE)<0 ){
18351848
/* Silently remove non-existent repositories from the login group. */
18361849
const char *zLabel = db_column_text(&q, 0);
1850
+ db_unprotect(PROTECT_CONFIG);
18371851
db_multi_exec(
18381852
"DELETE FROM config WHERE name GLOB 'peer-*-%q'",
18391853
&zLabel[10]
18401854
);
1855
+ db_protect_pop();
18411856
continue;
18421857
}
18431858
rc = sqlite3_open_v2(
18441859
zRepoName, &pPeer,
18451860
SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
20042019
"REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
20052020
"REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
20062021
"COMMIT;",
20072022
zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
20082023
);
2024
+ db_unprotect(PROTECT_CONFIG);
20092025
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2026
+ db_protect_pop();
20102027
fossil_free(zSql);
20112028
}
20122029
20132030
/*
20142031
** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
20252042
" WHERE name='login-group-name'"
20262043
" AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
20272044
zProjCode
20282045
);
20292046
fossil_free(zProjCode);
2047
+ db_unprotect(PROTECT_CONFIG);
20302048
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
20312049
fossil_free(zSql);
20322050
db_multi_exec(
20332051
"DELETE FROM config "
20342052
" WHERE name GLOB 'peer-*'"
20352053
" OR name GLOB 'login-group-*';"
20362054
);
2055
+ db_protect_pop();
20372056
}
20382057
20392058
/*
20402059
** COMMAND: login-group*
20412060
**
20422061
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294 zCookie = login_gen_user_cookie_value(zUsername, zHash);
295 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296 bSessionCookie ? 0 : expires);
297 record_login_attempt(zUsername, zIpAddr, 1);
 
298 db_multi_exec("UPDATE user SET cookie=%Q,"
299 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300 zHash, expires, uid);
 
301 fossil_free(zHash);
302 if( zDest ){
303 *zDest = zCookie;
304 }else{
305 free(zCookie);
@@ -356,14 +358,16 @@
356 }else{
357 const char *cookie = login_cookie_name();
358 /* To logout, change the cookie value to an empty string */
359 cgi_set_cookie(cookie, "",
360 login_cookie_path(), -86400);
 
361 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
362 " cexpire=0 WHERE uid=%d"
363 " AND login NOT IN ('anonymous','nobody',"
364 " 'developer','reader')", g.userUid);
 
365 cgi_replace_parameter(cookie, NULL);
366 cgi_replace_parameter("anon", NULL);
367 }
368 }
369
@@ -580,22 +584,27 @@
580 ;
581 }else{
582 char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
583 char *zChngPw;
584 char *zErr;
 
 
 
585 db_multi_exec(
586 "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
587 );
588 fossil_free(zNewPw);
589 zChngPw = mprintf(
590 "UPDATE user"
591 " SET pw=shared_secret(%Q,%Q,"
592 " (SELECT value FROM config WHERE name='project-code'))"
593 " WHERE login=%Q",
594 zNew1, g.zLogin, g.zLogin
595 );
596 if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){
 
 
 
597 zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
598 fossil_free(zErr);
599 }else{
600 redirect_to_g();
601 return;
@@ -835,16 +844,18 @@
835 zLogin, zHash
836 );
837 pStmt = 0;
838 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
839 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 
840 db_multi_exec(
841 "UPDATE user SET cookie=%Q, cexpire=%.17g"
842 " WHERE login=%Q",
843 zHash,
844 sqlite3_column_double(pStmt, 0), zLogin
845 );
 
846 nXfer++;
847 }
848 sqlite3_finalize(pStmt);
849 }
850 sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
1619 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1620 "VALUES(%Q,%Q,%Q,"
1621 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1622 zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
1623 fossil_free(zPass);
 
1624 db_multi_exec("%s", blob_sql_text(&sql));
 
1625 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1626 login_set_user_cookie(zUserID, uid, NULL, 0);
1627 if( doAlerts ){
1628 /* Also make the new user a subscriber. */
1629 Blob hdr, body;
@@ -1832,14 +1845,16 @@
1832 while( db_step(&q)==SQLITE_ROW ){
1833 const char *zRepoName = db_column_text(&q, 1);
1834 if( file_size(zRepoName, ExtFILE)<0 ){
1835 /* Silently remove non-existent repositories from the login group. */
1836 const char *zLabel = db_column_text(&q, 0);
 
1837 db_multi_exec(
1838 "DELETE FROM config WHERE name GLOB 'peer-*-%q'",
1839 &zLabel[10]
1840 );
 
1841 continue;
1842 }
1843 rc = sqlite3_open_v2(
1844 zRepoName, &pPeer,
1845 SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
2004 "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
2005 "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
2006 "COMMIT;",
2007 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
2008 );
 
2009 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
 
2010 fossil_free(zSql);
2011 }
2012
2013 /*
2014 ** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
2025 " WHERE name='login-group-name'"
2026 " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
2027 zProjCode
2028 );
2029 fossil_free(zProjCode);
 
2030 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2031 fossil_free(zSql);
2032 db_multi_exec(
2033 "DELETE FROM config "
2034 " WHERE name GLOB 'peer-*'"
2035 " OR name GLOB 'login-group-*';"
2036 );
 
2037 }
2038
2039 /*
2040 ** COMMAND: login-group*
2041 **
2042
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294 zCookie = login_gen_user_cookie_value(zUsername, zHash);
295 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296 bSessionCookie ? 0 : expires);
297 record_login_attempt(zUsername, zIpAddr, 1);
298 db_unprotect(PROTECT_USER);
299 db_multi_exec("UPDATE user SET cookie=%Q,"
300 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
301 zHash, expires, uid);
302 db_protect_pop();
303 fossil_free(zHash);
304 if( zDest ){
305 *zDest = zCookie;
306 }else{
307 free(zCookie);
@@ -356,14 +358,16 @@
358 }else{
359 const char *cookie = login_cookie_name();
360 /* To logout, change the cookie value to an empty string */
361 cgi_set_cookie(cookie, "",
362 login_cookie_path(), -86400);
363 db_unprotect(PROTECT_USER);
364 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
365 " cexpire=0 WHERE uid=%d"
366 " AND login NOT IN ('anonymous','nobody',"
367 " 'developer','reader')", g.userUid);
368 db_protect_pop();
369 cgi_replace_parameter(cookie, NULL);
370 cgi_replace_parameter("anon", NULL);
371 }
372 }
373
@@ -580,22 +584,27 @@
584 ;
585 }else{
586 char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
587 char *zChngPw;
588 char *zErr;
589 int rc;
590
591 db_unprotect(PROTECT_USER);
592 db_multi_exec(
593 "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
594 );
 
595 zChngPw = mprintf(
596 "UPDATE user"
597 " SET pw=shared_secret(%Q,%Q,"
598 " (SELECT value FROM config WHERE name='project-code'))"
599 " WHERE login=%Q",
600 zNew1, g.zLogin, g.zLogin
601 );
602 fossil_free(zNewPw);
603 rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr);
604 db_protect_pop();
605 if( rc ){
606 zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
607 fossil_free(zErr);
608 }else{
609 redirect_to_g();
610 return;
@@ -835,16 +844,18 @@
844 zLogin, zHash
845 );
846 pStmt = 0;
847 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
848 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
849 db_unprotect(PROTECT_USER);
850 db_multi_exec(
851 "UPDATE user SET cookie=%Q, cexpire=%.17g"
852 " WHERE login=%Q",
853 zHash,
854 sqlite3_column_double(pStmt, 0), zLogin
855 );
856 db_protect_pop();
857 nXfer++;
858 }
859 sqlite3_finalize(pStmt);
860 }
861 sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
1630 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1631 "VALUES(%Q,%Q,%Q,"
1632 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1633 zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
1634 fossil_free(zPass);
1635 db_unprotect(PROTECT_USER);
1636 db_multi_exec("%s", blob_sql_text(&sql));
1637 db_protect_pop();
1638 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1639 login_set_user_cookie(zUserID, uid, NULL, 0);
1640 if( doAlerts ){
1641 /* Also make the new user a subscriber. */
1642 Blob hdr, body;
@@ -1832,14 +1845,16 @@
1845 while( db_step(&q)==SQLITE_ROW ){
1846 const char *zRepoName = db_column_text(&q, 1);
1847 if( file_size(zRepoName, ExtFILE)<0 ){
1848 /* Silently remove non-existent repositories from the login group. */
1849 const char *zLabel = db_column_text(&q, 0);
1850 db_unprotect(PROTECT_CONFIG);
1851 db_multi_exec(
1852 "DELETE FROM config WHERE name GLOB 'peer-*-%q'",
1853 &zLabel[10]
1854 );
1855 db_protect_pop();
1856 continue;
1857 }
1858 rc = sqlite3_open_v2(
1859 zRepoName, &pPeer,
1860 SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
2019 "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
2020 "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
2021 "COMMIT;",
2022 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
2023 );
2024 db_unprotect(PROTECT_CONFIG);
2025 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2026 db_protect_pop();
2027 fossil_free(zSql);
2028 }
2029
2030 /*
2031 ** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
2042 " WHERE name='login-group-name'"
2043 " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
2044 zProjCode
2045 );
2046 fossil_free(zProjCode);
2047 db_unprotect(PROTECT_CONFIG);
2048 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2049 fossil_free(zSql);
2050 db_multi_exec(
2051 "DELETE FROM config "
2052 " WHERE name GLOB 'peer-*'"
2053 " OR name GLOB 'login-group-*';"
2054 );
2055 db_protect_pop();
2056 }
2057
2058 /*
2059 ** COMMAND: login-group*
2060 **
2061
+3 -3
--- src/main.c
+++ src/main.c
@@ -1227,13 +1227,10 @@
12271227
#if defined(FOSSIL_DYNAMIC_BUILD)
12281228
blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
12291229
#else
12301230
blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
12311231
#endif
1232
-#if defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
1233
- blob_append(pOut, "FOSSIL_LEGACY_ALLOW_SYMLINKS\n", -1);
1234
-#endif
12351232
#if defined(HAVE_PLEDGE)
12361233
blob_append(pOut, "HAVE_PLEDGE\n", -1);
12371234
#endif
12381235
#if defined(USE_MMAN_H)
12391236
blob_append(pOut, "USE_MMAN_H\n", -1);
@@ -1375,19 +1372,21 @@
13751372
g.zTop = &g.zBaseURL[7+strlen(zHost)];
13761373
g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
13771374
}
13781375
}
13791376
if( db_is_writeable("repository") ){
1377
+ db_unprotect(PROTECT_CONFIG);
13801378
if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
13811379
db_multi_exec("INSERT INTO config(name,value,mtime)"
13821380
"VALUES('baseurl:%q',1,now())", g.zBaseURL);
13831381
}else{
13841382
db_optional_sql("repository",
13851383
"REPLACE INTO config(name,value,mtime)"
13861384
"VALUES('baseurl:%q',1,now())", g.zBaseURL
13871385
);
13881386
}
1387
+ db_protect_pop();
13891388
}
13901389
}
13911390
13921391
/*
13931392
** Send an HTTP redirect back to the designated Index Page.
@@ -2637,10 +2636,11 @@
26372636
g.zExtRoot = find_option("extroot",0,1);
26382637
find_server_repository(2, 0);
26392638
g.cgiOutput = 1;
26402639
g.fNoHttpCompress = 1;
26412640
g.fullHttpReply = 1;
2641
+ g.sslNotAvailable = 1; /* Avoid attempts to redirect */
26422642
zIpAddr = cgi_ssh_remote_addr(0);
26432643
if( zIpAddr && zIpAddr[0] ){
26442644
g.fSshClient |= CGI_SSH_CLIENT;
26452645
ssh_request_loop(zIpAddr, 0);
26462646
}else{
26472647
--- src/main.c
+++ src/main.c
@@ -1227,13 +1227,10 @@
1227 #if defined(FOSSIL_DYNAMIC_BUILD)
1228 blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
1229 #else
1230 blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
1231 #endif
1232 #if defined(FOSSIL_LEGACY_ALLOW_SYMLINKS)
1233 blob_append(pOut, "FOSSIL_LEGACY_ALLOW_SYMLINKS\n", -1);
1234 #endif
1235 #if defined(HAVE_PLEDGE)
1236 blob_append(pOut, "HAVE_PLEDGE\n", -1);
1237 #endif
1238 #if defined(USE_MMAN_H)
1239 blob_append(pOut, "USE_MMAN_H\n", -1);
@@ -1375,19 +1372,21 @@
1375 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1376 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1377 }
1378 }
1379 if( db_is_writeable("repository") ){
 
1380 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1381 db_multi_exec("INSERT INTO config(name,value,mtime)"
1382 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1383 }else{
1384 db_optional_sql("repository",
1385 "REPLACE INTO config(name,value,mtime)"
1386 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1387 );
1388 }
 
1389 }
1390 }
1391
1392 /*
1393 ** Send an HTTP redirect back to the designated Index Page.
@@ -2637,10 +2636,11 @@
2637 g.zExtRoot = find_option("extroot",0,1);
2638 find_server_repository(2, 0);
2639 g.cgiOutput = 1;
2640 g.fNoHttpCompress = 1;
2641 g.fullHttpReply = 1;
 
2642 zIpAddr = cgi_ssh_remote_addr(0);
2643 if( zIpAddr && zIpAddr[0] ){
2644 g.fSshClient |= CGI_SSH_CLIENT;
2645 ssh_request_loop(zIpAddr, 0);
2646 }else{
2647
--- src/main.c
+++ src/main.c
@@ -1227,13 +1227,10 @@
1227 #if defined(FOSSIL_DYNAMIC_BUILD)
1228 blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
1229 #else
1230 blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
1231 #endif
 
 
 
1232 #if defined(HAVE_PLEDGE)
1233 blob_append(pOut, "HAVE_PLEDGE\n", -1);
1234 #endif
1235 #if defined(USE_MMAN_H)
1236 blob_append(pOut, "USE_MMAN_H\n", -1);
@@ -1375,19 +1372,21 @@
1372 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1373 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1374 }
1375 }
1376 if( db_is_writeable("repository") ){
1377 db_unprotect(PROTECT_CONFIG);
1378 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1379 db_multi_exec("INSERT INTO config(name,value,mtime)"
1380 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1381 }else{
1382 db_optional_sql("repository",
1383 "REPLACE INTO config(name,value,mtime)"
1384 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1385 );
1386 }
1387 db_protect_pop();
1388 }
1389 }
1390
1391 /*
1392 ** Send an HTTP redirect back to the designated Index Page.
@@ -2637,10 +2636,11 @@
2636 g.zExtRoot = find_option("extroot",0,1);
2637 find_server_repository(2, 0);
2638 g.cgiOutput = 1;
2639 g.fNoHttpCompress = 1;
2640 g.fullHttpReply = 1;
2641 g.sslNotAvailable = 1; /* Avoid attempts to redirect */
2642 zIpAddr = cgi_ssh_remote_addr(0);
2643 if( zIpAddr && zIpAddr[0] ){
2644 g.fSshClient |= CGI_SSH_CLIENT;
2645 ssh_request_loop(zIpAddr, 0);
2646 }else{
2647
+15
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
7373
$(SRCDIR)/http_socket.c \
7474
$(SRCDIR)/http_ssl.c \
7575
$(SRCDIR)/http_transport.c \
7676
$(SRCDIR)/import.c \
7777
$(SRCDIR)/info.c \
78
+ $(SRCDIR)/interwiki.c \
7879
$(SRCDIR)/json.c \
7980
$(SRCDIR)/json_artifact.c \
8081
$(SRCDIR)/json_branch.c \
8182
$(SRCDIR)/json_config.c \
8283
$(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
223224
$(SRCDIR)/default.css \
224225
$(SRCDIR)/diff.tcl \
225226
$(SRCDIR)/forum.js \
226227
$(SRCDIR)/fossil.bootstrap.js \
227228
$(SRCDIR)/fossil.confirmer.js \
229
+ $(SRCDIR)/fossil.copybutton.js \
228230
$(SRCDIR)/fossil.dom.js \
229231
$(SRCDIR)/fossil.fetch.js \
232
+ $(SRCDIR)/fossil.numbered-lines.js \
230233
$(SRCDIR)/fossil.page.fileedit.js \
231234
$(SRCDIR)/fossil.page.forumpost.js \
232235
$(SRCDIR)/fossil.page.whistory.js \
233236
$(SRCDIR)/fossil.page.wikiedit.js \
237
+ $(SRCDIR)/fossil.popupwidget.js \
234238
$(SRCDIR)/fossil.storage.js \
235239
$(SRCDIR)/fossil.tabs.js \
236240
$(SRCDIR)/graph.js \
237241
$(SRCDIR)/href.js \
238242
$(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
323327
$(OBJDIR)/http_socket_.c \
324328
$(OBJDIR)/http_ssl_.c \
325329
$(OBJDIR)/http_transport_.c \
326330
$(OBJDIR)/import_.c \
327331
$(OBJDIR)/info_.c \
332
+ $(OBJDIR)/interwiki_.c \
328333
$(OBJDIR)/json_.c \
329334
$(OBJDIR)/json_artifact_.c \
330335
$(OBJDIR)/json_branch_.c \
331336
$(OBJDIR)/json_config_.c \
332337
$(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
468473
$(OBJDIR)/http_socket.o \
469474
$(OBJDIR)/http_ssl.o \
470475
$(OBJDIR)/http_transport.o \
471476
$(OBJDIR)/import.o \
472477
$(OBJDIR)/info.o \
478
+ $(OBJDIR)/interwiki.o \
473479
$(OBJDIR)/json.o \
474480
$(OBJDIR)/json_artifact.o \
475481
$(OBJDIR)/json_branch.o \
476482
$(OBJDIR)/json_config.o \
477483
$(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
803809
$(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
804810
$(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
805811
$(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
806812
$(OBJDIR)/import_.c:$(OBJDIR)/import.h \
807813
$(OBJDIR)/info_.c:$(OBJDIR)/info.h \
814
+ $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
808815
$(OBJDIR)/json_.c:$(OBJDIR)/json.h \
809816
$(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
810817
$(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
811818
$(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
812819
$(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
13651372
13661373
$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
13671374
$(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
13681375
13691376
$(OBJDIR)/info.h: $(OBJDIR)/headers
1377
+
1378
+$(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(OBJDIR)/translate
1379
+ $(OBJDIR)/translate $(SRCDIR)/interwiki.c >$@
1380
+
1381
+$(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1382
+ $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1383
+
1384
+$(OBJDIR)/interwiki.h: $(OBJDIR)/headers
13701385
13711386
$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
13721387
$(OBJDIR)/translate $(SRCDIR)/json.c >$@
13731388
13741389
$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
13751390
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
73 $(SRCDIR)/http_socket.c \
74 $(SRCDIR)/http_ssl.c \
75 $(SRCDIR)/http_transport.c \
76 $(SRCDIR)/import.c \
77 $(SRCDIR)/info.c \
 
78 $(SRCDIR)/json.c \
79 $(SRCDIR)/json_artifact.c \
80 $(SRCDIR)/json_branch.c \
81 $(SRCDIR)/json_config.c \
82 $(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
223 $(SRCDIR)/default.css \
224 $(SRCDIR)/diff.tcl \
225 $(SRCDIR)/forum.js \
226 $(SRCDIR)/fossil.bootstrap.js \
227 $(SRCDIR)/fossil.confirmer.js \
 
228 $(SRCDIR)/fossil.dom.js \
229 $(SRCDIR)/fossil.fetch.js \
 
230 $(SRCDIR)/fossil.page.fileedit.js \
231 $(SRCDIR)/fossil.page.forumpost.js \
232 $(SRCDIR)/fossil.page.whistory.js \
233 $(SRCDIR)/fossil.page.wikiedit.js \
 
234 $(SRCDIR)/fossil.storage.js \
235 $(SRCDIR)/fossil.tabs.js \
236 $(SRCDIR)/graph.js \
237 $(SRCDIR)/href.js \
238 $(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
323 $(OBJDIR)/http_socket_.c \
324 $(OBJDIR)/http_ssl_.c \
325 $(OBJDIR)/http_transport_.c \
326 $(OBJDIR)/import_.c \
327 $(OBJDIR)/info_.c \
 
328 $(OBJDIR)/json_.c \
329 $(OBJDIR)/json_artifact_.c \
330 $(OBJDIR)/json_branch_.c \
331 $(OBJDIR)/json_config_.c \
332 $(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
468 $(OBJDIR)/http_socket.o \
469 $(OBJDIR)/http_ssl.o \
470 $(OBJDIR)/http_transport.o \
471 $(OBJDIR)/import.o \
472 $(OBJDIR)/info.o \
 
473 $(OBJDIR)/json.o \
474 $(OBJDIR)/json_artifact.o \
475 $(OBJDIR)/json_branch.o \
476 $(OBJDIR)/json_config.o \
477 $(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
803 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
804 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
805 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
806 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
807 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
 
808 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
809 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
810 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
811 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
812 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
1365
1366 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1367 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1368
1369 $(OBJDIR)/info.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1370
1371 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
1372 $(OBJDIR)/translate $(SRCDIR)/json.c >$@
1373
1374 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1375
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
73 $(SRCDIR)/http_socket.c \
74 $(SRCDIR)/http_ssl.c \
75 $(SRCDIR)/http_transport.c \
76 $(SRCDIR)/import.c \
77 $(SRCDIR)/info.c \
78 $(SRCDIR)/interwiki.c \
79 $(SRCDIR)/json.c \
80 $(SRCDIR)/json_artifact.c \
81 $(SRCDIR)/json_branch.c \
82 $(SRCDIR)/json_config.c \
83 $(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
224 $(SRCDIR)/default.css \
225 $(SRCDIR)/diff.tcl \
226 $(SRCDIR)/forum.js \
227 $(SRCDIR)/fossil.bootstrap.js \
228 $(SRCDIR)/fossil.confirmer.js \
229 $(SRCDIR)/fossil.copybutton.js \
230 $(SRCDIR)/fossil.dom.js \
231 $(SRCDIR)/fossil.fetch.js \
232 $(SRCDIR)/fossil.numbered-lines.js \
233 $(SRCDIR)/fossil.page.fileedit.js \
234 $(SRCDIR)/fossil.page.forumpost.js \
235 $(SRCDIR)/fossil.page.whistory.js \
236 $(SRCDIR)/fossil.page.wikiedit.js \
237 $(SRCDIR)/fossil.popupwidget.js \
238 $(SRCDIR)/fossil.storage.js \
239 $(SRCDIR)/fossil.tabs.js \
240 $(SRCDIR)/graph.js \
241 $(SRCDIR)/href.js \
242 $(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
327 $(OBJDIR)/http_socket_.c \
328 $(OBJDIR)/http_ssl_.c \
329 $(OBJDIR)/http_transport_.c \
330 $(OBJDIR)/import_.c \
331 $(OBJDIR)/info_.c \
332 $(OBJDIR)/interwiki_.c \
333 $(OBJDIR)/json_.c \
334 $(OBJDIR)/json_artifact_.c \
335 $(OBJDIR)/json_branch_.c \
336 $(OBJDIR)/json_config_.c \
337 $(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
473 $(OBJDIR)/http_socket.o \
474 $(OBJDIR)/http_ssl.o \
475 $(OBJDIR)/http_transport.o \
476 $(OBJDIR)/import.o \
477 $(OBJDIR)/info.o \
478 $(OBJDIR)/interwiki.o \
479 $(OBJDIR)/json.o \
480 $(OBJDIR)/json_artifact.o \
481 $(OBJDIR)/json_branch.o \
482 $(OBJDIR)/json_config.o \
483 $(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
809 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
810 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
811 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
812 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
813 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
814 $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
815 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
816 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
817 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
818 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
819 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
1372
1373 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1374 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1375
1376 $(OBJDIR)/info.h: $(OBJDIR)/headers
1377
1378 $(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(OBJDIR)/translate
1379 $(OBJDIR)/translate $(SRCDIR)/interwiki.c >$@
1380
1381 $(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1382 $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1383
1384 $(OBJDIR)/interwiki.h: $(OBJDIR)/headers
1385
1386 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
1387 $(OBJDIR)/translate $(SRCDIR)/json.c >$@
1388
1389 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1390
+15
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
7373
$(SRCDIR)/http_socket.c \
7474
$(SRCDIR)/http_ssl.c \
7575
$(SRCDIR)/http_transport.c \
7676
$(SRCDIR)/import.c \
7777
$(SRCDIR)/info.c \
78
+ $(SRCDIR)/interwiki.c \
7879
$(SRCDIR)/json.c \
7980
$(SRCDIR)/json_artifact.c \
8081
$(SRCDIR)/json_branch.c \
8182
$(SRCDIR)/json_config.c \
8283
$(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
223224
$(SRCDIR)/default.css \
224225
$(SRCDIR)/diff.tcl \
225226
$(SRCDIR)/forum.js \
226227
$(SRCDIR)/fossil.bootstrap.js \
227228
$(SRCDIR)/fossil.confirmer.js \
229
+ $(SRCDIR)/fossil.copybutton.js \
228230
$(SRCDIR)/fossil.dom.js \
229231
$(SRCDIR)/fossil.fetch.js \
232
+ $(SRCDIR)/fossil.numbered-lines.js \
230233
$(SRCDIR)/fossil.page.fileedit.js \
231234
$(SRCDIR)/fossil.page.forumpost.js \
232235
$(SRCDIR)/fossil.page.whistory.js \
233236
$(SRCDIR)/fossil.page.wikiedit.js \
237
+ $(SRCDIR)/fossil.popupwidget.js \
234238
$(SRCDIR)/fossil.storage.js \
235239
$(SRCDIR)/fossil.tabs.js \
236240
$(SRCDIR)/graph.js \
237241
$(SRCDIR)/href.js \
238242
$(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
323327
$(OBJDIR)/http_socket_.c \
324328
$(OBJDIR)/http_ssl_.c \
325329
$(OBJDIR)/http_transport_.c \
326330
$(OBJDIR)/import_.c \
327331
$(OBJDIR)/info_.c \
332
+ $(OBJDIR)/interwiki_.c \
328333
$(OBJDIR)/json_.c \
329334
$(OBJDIR)/json_artifact_.c \
330335
$(OBJDIR)/json_branch_.c \
331336
$(OBJDIR)/json_config_.c \
332337
$(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
468473
$(OBJDIR)/http_socket.o \
469474
$(OBJDIR)/http_ssl.o \
470475
$(OBJDIR)/http_transport.o \
471476
$(OBJDIR)/import.o \
472477
$(OBJDIR)/info.o \
478
+ $(OBJDIR)/interwiki.o \
473479
$(OBJDIR)/json.o \
474480
$(OBJDIR)/json_artifact.o \
475481
$(OBJDIR)/json_branch.o \
476482
$(OBJDIR)/json_config.o \
477483
$(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
803809
$(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
804810
$(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
805811
$(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
806812
$(OBJDIR)/import_.c:$(OBJDIR)/import.h \
807813
$(OBJDIR)/info_.c:$(OBJDIR)/info.h \
814
+ $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
808815
$(OBJDIR)/json_.c:$(OBJDIR)/json.h \
809816
$(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
810817
$(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
811818
$(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
812819
$(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
13651372
13661373
$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
13671374
$(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
13681375
13691376
$(OBJDIR)/info.h: $(OBJDIR)/headers
1377
+
1378
+$(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(OBJDIR)/translate
1379
+ $(OBJDIR)/translate $(SRCDIR)/interwiki.c >$@
1380
+
1381
+$(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1382
+ $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1383
+
1384
+$(OBJDIR)/interwiki.h: $(OBJDIR)/headers
13701385
13711386
$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
13721387
$(OBJDIR)/translate $(SRCDIR)/json.c >$@
13731388
13741389
$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
13751390
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
73 $(SRCDIR)/http_socket.c \
74 $(SRCDIR)/http_ssl.c \
75 $(SRCDIR)/http_transport.c \
76 $(SRCDIR)/import.c \
77 $(SRCDIR)/info.c \
 
78 $(SRCDIR)/json.c \
79 $(SRCDIR)/json_artifact.c \
80 $(SRCDIR)/json_branch.c \
81 $(SRCDIR)/json_config.c \
82 $(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
223 $(SRCDIR)/default.css \
224 $(SRCDIR)/diff.tcl \
225 $(SRCDIR)/forum.js \
226 $(SRCDIR)/fossil.bootstrap.js \
227 $(SRCDIR)/fossil.confirmer.js \
 
228 $(SRCDIR)/fossil.dom.js \
229 $(SRCDIR)/fossil.fetch.js \
 
230 $(SRCDIR)/fossil.page.fileedit.js \
231 $(SRCDIR)/fossil.page.forumpost.js \
232 $(SRCDIR)/fossil.page.whistory.js \
233 $(SRCDIR)/fossil.page.wikiedit.js \
 
234 $(SRCDIR)/fossil.storage.js \
235 $(SRCDIR)/fossil.tabs.js \
236 $(SRCDIR)/graph.js \
237 $(SRCDIR)/href.js \
238 $(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
323 $(OBJDIR)/http_socket_.c \
324 $(OBJDIR)/http_ssl_.c \
325 $(OBJDIR)/http_transport_.c \
326 $(OBJDIR)/import_.c \
327 $(OBJDIR)/info_.c \
 
328 $(OBJDIR)/json_.c \
329 $(OBJDIR)/json_artifact_.c \
330 $(OBJDIR)/json_branch_.c \
331 $(OBJDIR)/json_config_.c \
332 $(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
468 $(OBJDIR)/http_socket.o \
469 $(OBJDIR)/http_ssl.o \
470 $(OBJDIR)/http_transport.o \
471 $(OBJDIR)/import.o \
472 $(OBJDIR)/info.o \
 
473 $(OBJDIR)/json.o \
474 $(OBJDIR)/json_artifact.o \
475 $(OBJDIR)/json_branch.o \
476 $(OBJDIR)/json_config.o \
477 $(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
803 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
804 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
805 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
806 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
807 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
 
808 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
809 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
810 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
811 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
812 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
1365
1366 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1367 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1368
1369 $(OBJDIR)/info.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1370
1371 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
1372 $(OBJDIR)/translate $(SRCDIR)/json.c >$@
1373
1374 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1375
--- src/main.mk
+++ src/main.mk
@@ -73,10 +73,11 @@
73 $(SRCDIR)/http_socket.c \
74 $(SRCDIR)/http_ssl.c \
75 $(SRCDIR)/http_transport.c \
76 $(SRCDIR)/import.c \
77 $(SRCDIR)/info.c \
78 $(SRCDIR)/interwiki.c \
79 $(SRCDIR)/json.c \
80 $(SRCDIR)/json_artifact.c \
81 $(SRCDIR)/json_branch.c \
82 $(SRCDIR)/json_config.c \
83 $(SRCDIR)/json_diff.c \
@@ -223,16 +224,19 @@
224 $(SRCDIR)/default.css \
225 $(SRCDIR)/diff.tcl \
226 $(SRCDIR)/forum.js \
227 $(SRCDIR)/fossil.bootstrap.js \
228 $(SRCDIR)/fossil.confirmer.js \
229 $(SRCDIR)/fossil.copybutton.js \
230 $(SRCDIR)/fossil.dom.js \
231 $(SRCDIR)/fossil.fetch.js \
232 $(SRCDIR)/fossil.numbered-lines.js \
233 $(SRCDIR)/fossil.page.fileedit.js \
234 $(SRCDIR)/fossil.page.forumpost.js \
235 $(SRCDIR)/fossil.page.whistory.js \
236 $(SRCDIR)/fossil.page.wikiedit.js \
237 $(SRCDIR)/fossil.popupwidget.js \
238 $(SRCDIR)/fossil.storage.js \
239 $(SRCDIR)/fossil.tabs.js \
240 $(SRCDIR)/graph.js \
241 $(SRCDIR)/href.js \
242 $(SRCDIR)/login.js \
@@ -323,10 +327,11 @@
327 $(OBJDIR)/http_socket_.c \
328 $(OBJDIR)/http_ssl_.c \
329 $(OBJDIR)/http_transport_.c \
330 $(OBJDIR)/import_.c \
331 $(OBJDIR)/info_.c \
332 $(OBJDIR)/interwiki_.c \
333 $(OBJDIR)/json_.c \
334 $(OBJDIR)/json_artifact_.c \
335 $(OBJDIR)/json_branch_.c \
336 $(OBJDIR)/json_config_.c \
337 $(OBJDIR)/json_diff_.c \
@@ -468,10 +473,11 @@
473 $(OBJDIR)/http_socket.o \
474 $(OBJDIR)/http_ssl.o \
475 $(OBJDIR)/http_transport.o \
476 $(OBJDIR)/import.o \
477 $(OBJDIR)/info.o \
478 $(OBJDIR)/interwiki.o \
479 $(OBJDIR)/json.o \
480 $(OBJDIR)/json_artifact.o \
481 $(OBJDIR)/json_branch.o \
482 $(OBJDIR)/json_config.o \
483 $(OBJDIR)/json_diff.o \
@@ -803,10 +809,11 @@
809 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
810 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
811 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
812 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
813 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
814 $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
815 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
816 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
817 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
818 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
819 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1365,10 +1372,18 @@
1372
1373 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1374 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1375
1376 $(OBJDIR)/info.h: $(OBJDIR)/headers
1377
1378 $(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(OBJDIR)/translate
1379 $(OBJDIR)/translate $(SRCDIR)/interwiki.c >$@
1380
1381 $(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1382 $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1383
1384 $(OBJDIR)/interwiki.h: $(OBJDIR)/headers
1385
1386 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate
1387 $(OBJDIR)/translate $(SRCDIR)/json.c >$@
1388
1389 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1390
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -83,10 +83,11 @@
8383
http
8484
http_socket
8585
http_transport
8686
import
8787
info
88
+ interwiki
8889
json
8990
json_artifact
9091
json_branch
9192
json_config
9293
json_diff
9394
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -83,10 +83,11 @@
83 http
84 http_socket
85 http_transport
86 import
87 info
 
88 json
89 json_artifact
90 json_branch
91 json_config
92 json_diff
93
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -83,10 +83,11 @@
83 http
84 http_socket
85 http_transport
86 import
87 info
88 interwiki
89 json
90 json_artifact
91 json_branch
92 json_config
93 json_diff
94
+5 -6
--- src/manifest.c
+++ src/manifest.c
@@ -1814,24 +1814,20 @@
18141814
if( !hname_validate(z, j) ) goto reparent_abort;
18151815
if( z[j]==0 ) break;
18161816
z[j] = 0;
18171817
i += j;
18181818
}
1819
- if( !db_exists("SELECT 1 FROM plink WHERE cid=%d AND pid=%d",
1820
- rid, uuid_to_rid(azParent[0],0))
1821
- ){
1822
- p = manifest_get(rid, CFTYPE_MANIFEST, 0);
1823
- }
1819
+ p = manifest_get(rid, CFTYPE_MANIFEST, 0);
18241820
if( p!=0 ){
18251821
db_multi_exec(
18261822
"DELETE FROM plink WHERE cid=%d;"
18271823
"DELETE FROM mlink WHERE mid=%d;",
18281824
rid, rid
18291825
);
18301826
manifest_add_checkin_linkages(rid,p,nParent,azParent);
1827
+ manifest_destroy(p);
18311828
}
1832
- manifest_destroy(p);
18331829
reparent_abort:
18341830
fossil_free(azParent);
18351831
fossil_free(zCopy);
18361832
}
18371833
@@ -2680,10 +2676,13 @@
26802676
"VALUES('f',%.17g,%d,%Q,'%q: %q')",
26812677
p->rDate, rid, p->zUser, zFType, zTitle
26822678
);
26832679
fossil_free(zTitle);
26842680
}
2681
+ if( p->zWiki[0] ){
2682
+ backlink_extract(p->zWiki, p->zMimetype, rid, BKLNK_FORUM, p->rDate, 1);
2683
+ }
26852684
}
26862685
db_end_transaction(0);
26872686
if( permitHooks ){
26882687
rc = xfer_run_common_script();
26892688
if( rc==TH_OK ){
26902689
--- src/manifest.c
+++ src/manifest.c
@@ -1814,24 +1814,20 @@
1814 if( !hname_validate(z, j) ) goto reparent_abort;
1815 if( z[j]==0 ) break;
1816 z[j] = 0;
1817 i += j;
1818 }
1819 if( !db_exists("SELECT 1 FROM plink WHERE cid=%d AND pid=%d",
1820 rid, uuid_to_rid(azParent[0],0))
1821 ){
1822 p = manifest_get(rid, CFTYPE_MANIFEST, 0);
1823 }
1824 if( p!=0 ){
1825 db_multi_exec(
1826 "DELETE FROM plink WHERE cid=%d;"
1827 "DELETE FROM mlink WHERE mid=%d;",
1828 rid, rid
1829 );
1830 manifest_add_checkin_linkages(rid,p,nParent,azParent);
 
1831 }
1832 manifest_destroy(p);
1833 reparent_abort:
1834 fossil_free(azParent);
1835 fossil_free(zCopy);
1836 }
1837
@@ -2680,10 +2676,13 @@
2680 "VALUES('f',%.17g,%d,%Q,'%q: %q')",
2681 p->rDate, rid, p->zUser, zFType, zTitle
2682 );
2683 fossil_free(zTitle);
2684 }
 
 
 
2685 }
2686 db_end_transaction(0);
2687 if( permitHooks ){
2688 rc = xfer_run_common_script();
2689 if( rc==TH_OK ){
2690
--- src/manifest.c
+++ src/manifest.c
@@ -1814,24 +1814,20 @@
1814 if( !hname_validate(z, j) ) goto reparent_abort;
1815 if( z[j]==0 ) break;
1816 z[j] = 0;
1817 i += j;
1818 }
1819 p = manifest_get(rid, CFTYPE_MANIFEST, 0);
 
 
 
 
1820 if( p!=0 ){
1821 db_multi_exec(
1822 "DELETE FROM plink WHERE cid=%d;"
1823 "DELETE FROM mlink WHERE mid=%d;",
1824 rid, rid
1825 );
1826 manifest_add_checkin_linkages(rid,p,nParent,azParent);
1827 manifest_destroy(p);
1828 }
 
1829 reparent_abort:
1830 fossil_free(azParent);
1831 fossil_free(zCopy);
1832 }
1833
@@ -2680,10 +2676,13 @@
2676 "VALUES('f',%.17g,%d,%Q,'%q: %q')",
2677 p->rDate, rid, p->zUser, zFType, zTitle
2678 );
2679 fossil_free(zTitle);
2680 }
2681 if( p->zWiki[0] ){
2682 backlink_extract(p->zWiki, p->zMimetype, rid, BKLNK_FORUM, p->rDate, 1);
2683 }
2684 }
2685 db_end_transaction(0);
2686 if( permitHooks ){
2687 rc = xfer_run_common_script();
2688 if( rc==TH_OK ){
2689
+1 -1
--- src/markdown.c
+++ src/markdown.c
@@ -1919,11 +1919,11 @@
19191919
19201920
/* fallback on default alignment if not explicit */
19211921
if( align==0 && aligns && col<align_size ) align = aligns[col];
19221922
19231923
/* render cells */
1924
- if( cells && end>beg ){
1924
+ if( cells && end>=beg ){
19251925
parse_table_cell(cells, rndr, data+beg, end-beg, align|flags);
19261926
}
19271927
19281928
col++;
19291929
}
19301930
--- src/markdown.c
+++ src/markdown.c
@@ -1919,11 +1919,11 @@
1919
1920 /* fallback on default alignment if not explicit */
1921 if( align==0 && aligns && col<align_size ) align = aligns[col];
1922
1923 /* render cells */
1924 if( cells && end>beg ){
1925 parse_table_cell(cells, rndr, data+beg, end-beg, align|flags);
1926 }
1927
1928 col++;
1929 }
1930
--- src/markdown.c
+++ src/markdown.c
@@ -1919,11 +1919,11 @@
1919
1920 /* fallback on default alignment if not explicit */
1921 if( align==0 && aligns && col<align_size ) align = aligns[col];
1922
1923 /* render cells */
1924 if( cells && end>=beg ){
1925 parse_table_cell(cells, rndr, data+beg, end-beg, align|flags);
1926 }
1927
1928 col++;
1929 }
1930
+7 -3
--- src/markdown.md
+++ src/markdown.md
@@ -44,15 +44,16 @@
4444
> it may optionally be written **\<URL\>** (format 4).
4545
> Other **URL** formats include:
4646
> <ul>
4747
> <li> A relative pathname.
4848
> <li> A pathname starting with "/" in which case the Fossil server
49
-> URL prefix is prepended
49
+> URL prefix is prepended
5050
> <li> A wiki page name, or a wiki page name preceded by "wiki:"
51
-> <li> An artifact or ticket hash or hash prefix
51
+> <li> An artifact or ticket hash or hash prefix
5252
> <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
53
-> includes at least the day of the month.</ul>
53
+> includes at least the day of the month.
54
+> <li> An [interwiki link](#intermap) of the form "<i>Tag</i><b>:</b><i>PageName</i>"</ul>
5455
5556
> In format 8, then the URL becomes the display text. This is useful for
5657
> hyperlinks that refer to wiki pages and check-in and ticket hashes.
5758
5859
## Fonts ##
@@ -152,5 +153,8 @@
152153
> * For documents that begin with a top-level heading (ex: **# heading #**),
153154
> the heading is omitted from the body of the document and becomes the
154155
> document title displayed at the top of the Fossil page.
155156
156157
[daringfireball.net]: http://daringfireball.net/projects/markdown/syntax
158
+
159
+<a name="intermap"></a>
160
+## Interwiki Tag Map
157161
--- src/markdown.md
+++ src/markdown.md
@@ -44,15 +44,16 @@
44 > it may optionally be written **\<URL\>** (format 4).
45 > Other **URL** formats include:
46 > <ul>
47 > <li> A relative pathname.
48 > <li> A pathname starting with "/" in which case the Fossil server
49 > URL prefix is prepended
50 > <li> A wiki page name, or a wiki page name preceded by "wiki:"
51 > <li> An artifact or ticket hash or hash prefix
52 > <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
53 > includes at least the day of the month.</ul>
 
54
55 > In format 8, then the URL becomes the display text. This is useful for
56 > hyperlinks that refer to wiki pages and check-in and ticket hashes.
57
58 ## Fonts ##
@@ -152,5 +153,8 @@
152 > * For documents that begin with a top-level heading (ex: **# heading #**),
153 > the heading is omitted from the body of the document and becomes the
154 > document title displayed at the top of the Fossil page.
155
156 [daringfireball.net]: http://daringfireball.net/projects/markdown/syntax
 
 
 
157
--- src/markdown.md
+++ src/markdown.md
@@ -44,15 +44,16 @@
44 > it may optionally be written **\<URL\>** (format 4).
45 > Other **URL** formats include:
46 > <ul>
47 > <li> A relative pathname.
48 > <li> A pathname starting with "/" in which case the Fossil server
49 > URL prefix is prepended
50 > <li> A wiki page name, or a wiki page name preceded by "wiki:"
51 > <li> An artifact or ticket hash or hash prefix
52 > <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
53 > includes at least the day of the month.
54 > <li> An [interwiki link](#intermap) of the form "<i>Tag</i><b>:</b><i>PageName</i>"</ul>
55
56 > In format 8, then the URL becomes the display text. This is useful for
57 > hyperlinks that refer to wiki pages and check-in and ticket hashes.
58
59 ## Fonts ##
@@ -152,5 +153,8 @@
153 > * For documents that begin with a top-level heading (ex: **# heading #**),
154 > the heading is omitted from the body of the document and becomes the
155 > document title displayed at the top of the Fossil page.
156
157 [daringfireball.net]: http://daringfireball.net/projects/markdown/syntax
158
159 <a name="intermap"></a>
160 ## Interwiki Tag Map
161
--- src/mkbuiltin.c
+++ src/mkbuiltin.c
@@ -62,10 +62,11 @@
6262
fclose(in);
6363
z[got] = 0;
6464
return z;
6565
}
6666
67
+#ifndef FOSSIL_DEBUG
6768
/*
6869
** Try to compress a javascript file by removing unnecessary whitespace.
6970
**
7071
** Warning: This compression routine does not necessarily work for any
7172
** arbitrary Javascript source file. But it should work ok for the
@@ -99,10 +100,11 @@
99100
z[j++] = c;
100101
}
101102
z[j] = 0;
102103
*pn = j;
103104
}
105
+#endif /* FOSSIL_DEBUG */
104106
105107
/*
106108
** There is an instance of the following for each file translated.
107109
*/
108110
typedef struct Resource Resource;
@@ -279,11 +281,13 @@
279281
int nRes;
280282
unsigned char *pData;
281283
int nErr = 0;
282284
int nSkip;
283285
int nPrefix = 0;
286
+#ifndef FOSSIL_DEBUG
284287
int nName;
288
+#endif
285289
286290
if( argc==1 ){
287291
fprintf(stderr, "usage\t:%s "
288292
"[--prefix path] [--reslist file] [resource-file1 ...]\n",
289293
argv[0]
@@ -350,19 +354,21 @@
350354
while( pData[nSkip]=='#' ){
351355
while( pData[nSkip]!=0 && pData[nSkip]!='\n' ){ nSkip++; }
352356
if( pData[nSkip]=='\n' ) nSkip++;
353357
}
354358
359
+#ifndef FOSSIL_DEBUG
355360
/* Compress javascript source files */
356361
nName = (int)strlen(aRes[i].zName);
357362
if( (nName>3 && strcmp(&aRes[i].zName[nName-3],".js")==0)
358363
|| (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0)
359364
){
360365
int x = sz-nSkip;
361366
compressJavascript(pData+nSkip, &x);
362367
sz = x + nSkip;
363368
}
369
+#endif
364370
365371
aRes[i].nByte = sz - nSkip;
366372
aRes[i].idx = i;
367373
printf("/* Content of file %s */\n", aRes[i].zName);
368374
printf("static const unsigned char bidata%d[%d] = {\n ",
369375
--- src/mkbuiltin.c
+++ src/mkbuiltin.c
@@ -62,10 +62,11 @@
62 fclose(in);
63 z[got] = 0;
64 return z;
65 }
66
 
67 /*
68 ** Try to compress a javascript file by removing unnecessary whitespace.
69 **
70 ** Warning: This compression routine does not necessarily work for any
71 ** arbitrary Javascript source file. But it should work ok for the
@@ -99,10 +100,11 @@
99 z[j++] = c;
100 }
101 z[j] = 0;
102 *pn = j;
103 }
 
104
105 /*
106 ** There is an instance of the following for each file translated.
107 */
108 typedef struct Resource Resource;
@@ -279,11 +281,13 @@
279 int nRes;
280 unsigned char *pData;
281 int nErr = 0;
282 int nSkip;
283 int nPrefix = 0;
 
284 int nName;
 
285
286 if( argc==1 ){
287 fprintf(stderr, "usage\t:%s "
288 "[--prefix path] [--reslist file] [resource-file1 ...]\n",
289 argv[0]
@@ -350,19 +354,21 @@
350 while( pData[nSkip]=='#' ){
351 while( pData[nSkip]!=0 && pData[nSkip]!='\n' ){ nSkip++; }
352 if( pData[nSkip]=='\n' ) nSkip++;
353 }
354
 
355 /* Compress javascript source files */
356 nName = (int)strlen(aRes[i].zName);
357 if( (nName>3 && strcmp(&aRes[i].zName[nName-3],".js")==0)
358 || (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0)
359 ){
360 int x = sz-nSkip;
361 compressJavascript(pData+nSkip, &x);
362 sz = x + nSkip;
363 }
 
364
365 aRes[i].nByte = sz - nSkip;
366 aRes[i].idx = i;
367 printf("/* Content of file %s */\n", aRes[i].zName);
368 printf("static const unsigned char bidata%d[%d] = {\n ",
369
--- src/mkbuiltin.c
+++ src/mkbuiltin.c
@@ -62,10 +62,11 @@
62 fclose(in);
63 z[got] = 0;
64 return z;
65 }
66
67 #ifndef FOSSIL_DEBUG
68 /*
69 ** Try to compress a javascript file by removing unnecessary whitespace.
70 **
71 ** Warning: This compression routine does not necessarily work for any
72 ** arbitrary Javascript source file. But it should work ok for the
@@ -99,10 +100,11 @@
100 z[j++] = c;
101 }
102 z[j] = 0;
103 *pn = j;
104 }
105 #endif /* FOSSIL_DEBUG */
106
107 /*
108 ** There is an instance of the following for each file translated.
109 */
110 typedef struct Resource Resource;
@@ -279,11 +281,13 @@
281 int nRes;
282 unsigned char *pData;
283 int nErr = 0;
284 int nSkip;
285 int nPrefix = 0;
286 #ifndef FOSSIL_DEBUG
287 int nName;
288 #endif
289
290 if( argc==1 ){
291 fprintf(stderr, "usage\t:%s "
292 "[--prefix path] [--reslist file] [resource-file1 ...]\n",
293 argv[0]
@@ -350,19 +354,21 @@
354 while( pData[nSkip]=='#' ){
355 while( pData[nSkip]!=0 && pData[nSkip]!='\n' ){ nSkip++; }
356 if( pData[nSkip]=='\n' ) nSkip++;
357 }
358
359 #ifndef FOSSIL_DEBUG
360 /* Compress javascript source files */
361 nName = (int)strlen(aRes[i].zName);
362 if( (nName>3 && strcmp(&aRes[i].zName[nName-3],".js")==0)
363 || (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0)
364 ){
365 int x = sz-nSkip;
366 compressJavascript(pData+nSkip, &x);
367 sz = x + nSkip;
368 }
369 #endif
370
371 aRes[i].nByte = sz - nSkip;
372 aRes[i].idx = i;
373 printf("/* Content of file %s */\n", aRes[i].zName);
374 printf("static const unsigned char bidata%d[%d] = {\n ",
375
+7 -1
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
9090
#define CMDFLAG_SETTING 0x0020 /* A setting */
9191
#define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
9292
#define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
9393
#define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
9494
#define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
95
+#define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */
9596
/**************************************************************************/
9697
9798
/*
9899
** Each entry looks like this:
99100
*/
@@ -248,10 +249,12 @@
248249
}else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
249250
aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
250251
aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
251252
}else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
252253
aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
254
+ }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){
255
+ aEntry[nUsed].eType |= CMDFLAG_SENSITIVE;
253256
}else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
254257
aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
255258
}else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
256259
aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
257260
}else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -341,10 +344,12 @@
341344
i += 4;
342345
if( !fossil_isspace(zLine[i]) ) goto page_skip;
343346
while( fossil_isspace(zLine[i]) ){ i++; }
344347
for(j=0; fossil_isident(zLine[i+j]); j++){}
345348
if( j==0 ) goto page_skip;
349
+ }else{
350
+ j = 0;
346351
}
347352
for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){}
348353
nHelp = k+1;
349354
zHelp[nHelp] = 0;
350355
for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){}
@@ -477,14 +482,15 @@
477482
if( zVar ){
478483
printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
479484
}else{
480485
printf(" 0,%*s", 16, "");
481486
}
482
- printf(" %3d, %d, %d, \"%s\"%*s },\n",
487
+ printf(" %3d, %d, %d, %d, \"%s\"%*s },\n",
483488
aEntry[i].iWidth,
484489
(aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
485490
(aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
491
+ (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0,
486492
zDef, (int)(10-strlen(zDef)), ""
487493
);
488494
if( aEntry[i].zIf ){
489495
printf("#endif\n");
490496
}
491497
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
90 #define CMDFLAG_SETTING 0x0020 /* A setting */
91 #define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
92 #define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
93 #define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
94 #define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
 
95 /**************************************************************************/
96
97 /*
98 ** Each entry looks like this:
99 */
@@ -248,10 +249,12 @@
248 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
249 aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
250 aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
251 }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
252 aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
 
 
253 }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
254 aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
255 }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
256 aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
257 }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -341,10 +344,12 @@
341 i += 4;
342 if( !fossil_isspace(zLine[i]) ) goto page_skip;
343 while( fossil_isspace(zLine[i]) ){ i++; }
344 for(j=0; fossil_isident(zLine[i+j]); j++){}
345 if( j==0 ) goto page_skip;
 
 
346 }
347 for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){}
348 nHelp = k+1;
349 zHelp[nHelp] = 0;
350 for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){}
@@ -477,14 +482,15 @@
477 if( zVar ){
478 printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
479 }else{
480 printf(" 0,%*s", 16, "");
481 }
482 printf(" %3d, %d, %d, \"%s\"%*s },\n",
483 aEntry[i].iWidth,
484 (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
485 (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
 
486 zDef, (int)(10-strlen(zDef)), ""
487 );
488 if( aEntry[i].zIf ){
489 printf("#endif\n");
490 }
491
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
90 #define CMDFLAG_SETTING 0x0020 /* A setting */
91 #define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
92 #define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
93 #define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
94 #define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
95 #define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */
96 /**************************************************************************/
97
98 /*
99 ** Each entry looks like this:
100 */
@@ -248,10 +249,12 @@
249 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
250 aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
251 aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
252 }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
253 aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
254 }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){
255 aEntry[nUsed].eType |= CMDFLAG_SENSITIVE;
256 }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
257 aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
258 }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
259 aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
260 }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -341,10 +344,12 @@
344 i += 4;
345 if( !fossil_isspace(zLine[i]) ) goto page_skip;
346 while( fossil_isspace(zLine[i]) ){ i++; }
347 for(j=0; fossil_isident(zLine[i+j]); j++){}
348 if( j==0 ) goto page_skip;
349 }else{
350 j = 0;
351 }
352 for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){}
353 nHelp = k+1;
354 zHelp[nHelp] = 0;
355 for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){}
@@ -477,14 +482,15 @@
482 if( zVar ){
483 printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
484 }else{
485 printf(" 0,%*s", 16, "");
486 }
487 printf(" %3d, %d, %d, %d, \"%s\"%*s },\n",
488 aEntry[i].iWidth,
489 (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
490 (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
491 (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0,
492 zDef, (int)(10-strlen(zDef)), ""
493 );
494 if( aEntry[i].zIf ){
495 printf("#endif\n");
496 }
497
--- src/moderate.c
+++ src/moderate.c
@@ -159,10 +159,11 @@
159159
rid, rid, rid
160160
);
161161
db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
162162
admin_log("Approved moderation of rid %c-%d.", class, rid);
163163
if( class!='a' ) search_doc_touch(class, rid, 0);
164
+ setup_incr_cfgcnt();
164165
db_end_transaction(0);
165166
}
166167
167168
/*
168169
** WEBPAGE: modreq
@@ -221,7 +222,8 @@
221222
while( db_step(&q)==SQLITE_ROW ){
222223
int const objid = db_column_int(&q, 0);
223224
moderation_disapprove(objid);
224225
}
225226
db_finalize(&q);
227
+ setup_incr_cfgcnt();
226228
db_end_transaction(0);
227229
}
228230
--- src/moderate.c
+++ src/moderate.c
@@ -159,10 +159,11 @@
159 rid, rid, rid
160 );
161 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
162 admin_log("Approved moderation of rid %c-%d.", class, rid);
163 if( class!='a' ) search_doc_touch(class, rid, 0);
 
164 db_end_transaction(0);
165 }
166
167 /*
168 ** WEBPAGE: modreq
@@ -221,7 +222,8 @@
221 while( db_step(&q)==SQLITE_ROW ){
222 int const objid = db_column_int(&q, 0);
223 moderation_disapprove(objid);
224 }
225 db_finalize(&q);
 
226 db_end_transaction(0);
227 }
228
--- src/moderate.c
+++ src/moderate.c
@@ -159,10 +159,11 @@
159 rid, rid, rid
160 );
161 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
162 admin_log("Approved moderation of rid %c-%d.", class, rid);
163 if( class!='a' ) search_doc_touch(class, rid, 0);
164 setup_incr_cfgcnt();
165 db_end_transaction(0);
166 }
167
168 /*
169 ** WEBPAGE: modreq
@@ -221,7 +222,8 @@
222 while( db_step(&q)==SQLITE_ROW ){
223 int const objid = db_column_int(&q, 0);
224 moderation_disapprove(objid);
225 }
226 db_finalize(&q);
227 setup_incr_cfgcnt();
228 db_end_transaction(0);
229 }
230
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
11481148
rc = fossil_print_error(rc, z);
11491149
abort();
11501150
exit(rc);
11511151
}
11521152
NORETURN void fossil_fatal(const char *zFormat, ...){
1153
+ static int once = 0;
11531154
char *z;
11541155
int rc = 1;
1156
+ if( once ) exit(1);
1157
+ once = 1;
11551158
va_list ap;
11561159
mainInFatalError = 1;
11571160
va_start(ap, zFormat);
11581161
z = vmprintf(zFormat, ap);
11591162
va_end(ap);
11601163
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
1148 rc = fossil_print_error(rc, z);
1149 abort();
1150 exit(rc);
1151 }
1152 NORETURN void fossil_fatal(const char *zFormat, ...){
 
1153 char *z;
1154 int rc = 1;
 
 
1155 va_list ap;
1156 mainInFatalError = 1;
1157 va_start(ap, zFormat);
1158 z = vmprintf(zFormat, ap);
1159 va_end(ap);
1160
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
1148 rc = fossil_print_error(rc, z);
1149 abort();
1150 exit(rc);
1151 }
1152 NORETURN void fossil_fatal(const char *zFormat, ...){
1153 static int once = 0;
1154 char *z;
1155 int rc = 1;
1156 if( once ) exit(1);
1157 once = 1;
1158 va_list ap;
1159 mainInFatalError = 1;
1160 va_start(ap, zFormat);
1161 z = vmprintf(zFormat, ap);
1162 va_end(ap);
1163
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
5252
}
5353
5454
/* Add the user.mtime column if it is missing. (2011-04-27)
5555
*/
5656
if( !db_table_has_column("repository", "user", "mtime") ){
57
+ db_unprotect(PROTECT_ALL);
5758
db_multi_exec(
5859
"CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
5960
"DROP TABLE user;"
6061
"CREATE TABLE user(\n"
6162
" uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
7273
"INSERT OR IGNORE INTO user"
7374
" SELECT uid, login, pw, cap, cookie,"
7475
" ipaddr, cexpire, info, now(), photo FROM temp_user;"
7576
"DROP TABLE temp_user;"
7677
);
78
+ db_protect_pop();
7779
}
7880
7981
/* Add the config.mtime column if it is missing. (2011-04-27)
8082
*/
8183
if( !db_table_has_column("repository", "config", "mtime") ){
84
+ db_unprotect(PROTECT_CONFIG);
8285
db_multi_exec(
8386
"ALTER TABLE config ADD COLUMN mtime INTEGER;"
8487
"UPDATE config SET mtime=now();"
8588
);
89
+ db_protect_pop();
8690
}
8791
8892
/* Add the shun.mtime and shun.scom columns if they are missing.
8993
** (2011-04-27)
9094
*/
@@ -382,10 +386,11 @@
382386
percent_complete(0);
383387
}
384388
alert_triggers_disable();
385389
rebuild_update_schema();
386390
blob_init(&sql, 0, 0);
391
+ db_unprotect(PROTECT_ALL);
387392
db_prepare(&q,
388393
"SELECT name FROM sqlite_schema /*scan*/"
389394
" WHERE type='table'"
390395
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
391396
"'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
475480
alert_triggers_enable();
476481
if(!g.fQuiet && ttyOutput ){
477482
percent_complete(1000);
478483
fossil_print("\n");
479484
}
485
+ db_protect_pop();
480486
return errCnt;
481487
}
482488
483489
/*
484490
** Number of neighbors to search
@@ -667,10 +673,11 @@
667673
668674
/* We should be done with options.. */
669675
verify_all_options();
670676
671677
db_begin_transaction();
678
+ db_unprotect(PROTECT_ALL);
672679
if( !compressOnlyFlag ){
673680
search_drop_index();
674681
ttyOutput = 1;
675682
errCnt = rebuild_db(randomizeFlag, 1, doClustering);
676683
reconstruct_private_table();
@@ -720,10 +727,11 @@
720727
if( activateWal ){
721728
db_multi_exec("PRAGMA journal_mode=WAL;");
722729
}
723730
}
724731
if( runReindex ) search_rebuild_index();
732
+ db_protect_pop();
725733
if( showStats ){
726734
static const struct { int idx; const char *zLabel; } aStat[] = {
727735
{ CFTYPE_ANY, "Artifacts:" },
728736
{ CFTYPE_MANIFEST, "Manifests:" },
729737
{ CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
755763
** testing by cloning a working project repository.
756764
*/
757765
void test_detach_cmd(void){
758766
db_find_and_open_repository(0, 2);
759767
db_begin_transaction();
768
+ db_unprotect(PROTECT_CONFIG);
760769
db_multi_exec(
761770
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
762771
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
763772
"UPDATE config SET value=lower(hex(randomblob(20)))"
764773
" WHERE name='project-code';"
765774
"UPDATE config SET value='detached-' || value"
766775
" WHERE name='project-name' AND value NOT GLOB 'detached-*';"
767776
);
777
+ db_protect_pop();
768778
db_end_transaction(0);
769779
}
770780
771781
/*
772782
** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
910920
if( privateOnly || bVerily ){
911921
bNeedRebuild = db_exists("SELECT 1 FROM private");
912922
delete_private_content();
913923
}
914924
if( !privateOnly ){
925
+ db_unprotect(PROTECT_ALL);
915926
db_multi_exec(
916927
"UPDATE user SET pw='';"
917928
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
918929
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
919930
"DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
933944
"DROP TABLE IF EXISTS purgeitem;\n"
934945
"DROP TABLE IF EXISTS admin_log;\n"
935946
"DROP TABLE IF EXISTS vcache;\n"
936947
);
937948
}
949
+ db_protect_pop();
938950
}
939951
if( !bNeedRebuild ){
940952
db_end_transaction(0);
953
+ db_unprotect(PROTECT_ALL);
941954
db_multi_exec("VACUUM;");
955
+ db_protect_pop();
942956
}else{
943957
rebuild_db(0, 1, 0);
944958
db_end_transaction(0);
945959
}
946960
}
947961
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
52 }
53
54 /* Add the user.mtime column if it is missing. (2011-04-27)
55 */
56 if( !db_table_has_column("repository", "user", "mtime") ){
 
57 db_multi_exec(
58 "CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
59 "DROP TABLE user;"
60 "CREATE TABLE user(\n"
61 " uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
72 "INSERT OR IGNORE INTO user"
73 " SELECT uid, login, pw, cap, cookie,"
74 " ipaddr, cexpire, info, now(), photo FROM temp_user;"
75 "DROP TABLE temp_user;"
76 );
 
77 }
78
79 /* Add the config.mtime column if it is missing. (2011-04-27)
80 */
81 if( !db_table_has_column("repository", "config", "mtime") ){
 
82 db_multi_exec(
83 "ALTER TABLE config ADD COLUMN mtime INTEGER;"
84 "UPDATE config SET mtime=now();"
85 );
 
86 }
87
88 /* Add the shun.mtime and shun.scom columns if they are missing.
89 ** (2011-04-27)
90 */
@@ -382,10 +386,11 @@
382 percent_complete(0);
383 }
384 alert_triggers_disable();
385 rebuild_update_schema();
386 blob_init(&sql, 0, 0);
 
387 db_prepare(&q,
388 "SELECT name FROM sqlite_schema /*scan*/"
389 " WHERE type='table'"
390 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
391 "'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
475 alert_triggers_enable();
476 if(!g.fQuiet && ttyOutput ){
477 percent_complete(1000);
478 fossil_print("\n");
479 }
 
480 return errCnt;
481 }
482
483 /*
484 ** Number of neighbors to search
@@ -667,10 +673,11 @@
667
668 /* We should be done with options.. */
669 verify_all_options();
670
671 db_begin_transaction();
 
672 if( !compressOnlyFlag ){
673 search_drop_index();
674 ttyOutput = 1;
675 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
676 reconstruct_private_table();
@@ -720,10 +727,11 @@
720 if( activateWal ){
721 db_multi_exec("PRAGMA journal_mode=WAL;");
722 }
723 }
724 if( runReindex ) search_rebuild_index();
 
725 if( showStats ){
726 static const struct { int idx; const char *zLabel; } aStat[] = {
727 { CFTYPE_ANY, "Artifacts:" },
728 { CFTYPE_MANIFEST, "Manifests:" },
729 { CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
755 ** testing by cloning a working project repository.
756 */
757 void test_detach_cmd(void){
758 db_find_and_open_repository(0, 2);
759 db_begin_transaction();
 
760 db_multi_exec(
761 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
762 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
763 "UPDATE config SET value=lower(hex(randomblob(20)))"
764 " WHERE name='project-code';"
765 "UPDATE config SET value='detached-' || value"
766 " WHERE name='project-name' AND value NOT GLOB 'detached-*';"
767 );
 
768 db_end_transaction(0);
769 }
770
771 /*
772 ** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
910 if( privateOnly || bVerily ){
911 bNeedRebuild = db_exists("SELECT 1 FROM private");
912 delete_private_content();
913 }
914 if( !privateOnly ){
 
915 db_multi_exec(
916 "UPDATE user SET pw='';"
917 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
918 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
919 "DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
933 "DROP TABLE IF EXISTS purgeitem;\n"
934 "DROP TABLE IF EXISTS admin_log;\n"
935 "DROP TABLE IF EXISTS vcache;\n"
936 );
937 }
 
938 }
939 if( !bNeedRebuild ){
940 db_end_transaction(0);
 
941 db_multi_exec("VACUUM;");
 
942 }else{
943 rebuild_db(0, 1, 0);
944 db_end_transaction(0);
945 }
946 }
947
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
52 }
53
54 /* Add the user.mtime column if it is missing. (2011-04-27)
55 */
56 if( !db_table_has_column("repository", "user", "mtime") ){
57 db_unprotect(PROTECT_ALL);
58 db_multi_exec(
59 "CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
60 "DROP TABLE user;"
61 "CREATE TABLE user(\n"
62 " uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
73 "INSERT OR IGNORE INTO user"
74 " SELECT uid, login, pw, cap, cookie,"
75 " ipaddr, cexpire, info, now(), photo FROM temp_user;"
76 "DROP TABLE temp_user;"
77 );
78 db_protect_pop();
79 }
80
81 /* Add the config.mtime column if it is missing. (2011-04-27)
82 */
83 if( !db_table_has_column("repository", "config", "mtime") ){
84 db_unprotect(PROTECT_CONFIG);
85 db_multi_exec(
86 "ALTER TABLE config ADD COLUMN mtime INTEGER;"
87 "UPDATE config SET mtime=now();"
88 );
89 db_protect_pop();
90 }
91
92 /* Add the shun.mtime and shun.scom columns if they are missing.
93 ** (2011-04-27)
94 */
@@ -382,10 +386,11 @@
386 percent_complete(0);
387 }
388 alert_triggers_disable();
389 rebuild_update_schema();
390 blob_init(&sql, 0, 0);
391 db_unprotect(PROTECT_ALL);
392 db_prepare(&q,
393 "SELECT name FROM sqlite_schema /*scan*/"
394 " WHERE type='table'"
395 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
396 "'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
480 alert_triggers_enable();
481 if(!g.fQuiet && ttyOutput ){
482 percent_complete(1000);
483 fossil_print("\n");
484 }
485 db_protect_pop();
486 return errCnt;
487 }
488
489 /*
490 ** Number of neighbors to search
@@ -667,10 +673,11 @@
673
674 /* We should be done with options.. */
675 verify_all_options();
676
677 db_begin_transaction();
678 db_unprotect(PROTECT_ALL);
679 if( !compressOnlyFlag ){
680 search_drop_index();
681 ttyOutput = 1;
682 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
683 reconstruct_private_table();
@@ -720,10 +727,11 @@
727 if( activateWal ){
728 db_multi_exec("PRAGMA journal_mode=WAL;");
729 }
730 }
731 if( runReindex ) search_rebuild_index();
732 db_protect_pop();
733 if( showStats ){
734 static const struct { int idx; const char *zLabel; } aStat[] = {
735 { CFTYPE_ANY, "Artifacts:" },
736 { CFTYPE_MANIFEST, "Manifests:" },
737 { CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
763 ** testing by cloning a working project repository.
764 */
765 void test_detach_cmd(void){
766 db_find_and_open_repository(0, 2);
767 db_begin_transaction();
768 db_unprotect(PROTECT_CONFIG);
769 db_multi_exec(
770 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
771 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
772 "UPDATE config SET value=lower(hex(randomblob(20)))"
773 " WHERE name='project-code';"
774 "UPDATE config SET value='detached-' || value"
775 " WHERE name='project-name' AND value NOT GLOB 'detached-*';"
776 );
777 db_protect_pop();
778 db_end_transaction(0);
779 }
780
781 /*
782 ** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
920 if( privateOnly || bVerily ){
921 bNeedRebuild = db_exists("SELECT 1 FROM private");
922 delete_private_content();
923 }
924 if( !privateOnly ){
925 db_unprotect(PROTECT_ALL);
926 db_multi_exec(
927 "UPDATE user SET pw='';"
928 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
929 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
930 "DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
944 "DROP TABLE IF EXISTS purgeitem;\n"
945 "DROP TABLE IF EXISTS admin_log;\n"
946 "DROP TABLE IF EXISTS vcache;\n"
947 );
948 }
949 db_protect_pop();
950 }
951 if( !bNeedRebuild ){
952 db_end_transaction(0);
953 db_unprotect(PROTECT_ALL);
954 db_multi_exec("VACUUM;");
955 db_protect_pop();
956 }else{
957 rebuild_db(0, 1, 0);
958 db_end_transaction(0);
959 }
960 }
961
+1 -1
--- src/scroll.js
+++ src/scroll.js
@@ -1,2 +1,2 @@
11
/* Cause the page to scroll so that the #scrollToMe is visible */
2
-document.getElementById('scrollToMe').scrollIntoView(true);
2
+(document.getElementById('scrollToMe')||document.body).scrollIntoView(true);
33
--- src/scroll.js
+++ src/scroll.js
@@ -1,2 +1,2 @@
1 /* Cause the page to scroll so that the #scrollToMe is visible */
2 document.getElementById('scrollToMe').scrollIntoView(true);
3
--- src/scroll.js
+++ src/scroll.js
@@ -1,2 +1,2 @@
1 /* Cause the page to scroll so that the #scrollToMe is visible */
2 (document.getElementById('scrollToMe')||document.body).scrollIntoView(true);
3
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281281
@ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282282
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283283
@ from users "anonymous" and "nobody"
284284
@ on the <a href="setup_ulist">User Configuration</a> page.
285285
}
286
+
287
+ /* The strict-manifest-syntax setting should be on. */
288
+ if( db_get_boolean("strict-manifest-syntax",1)==0 ){
289
+ @ <li><p><b>WARNING:</b>
290
+ @ The "strict-manifest-syntax" flag is off. This is a security
291
+ @ risk. Turn this setting on (its default) to protect the users
292
+ @ of this repository.
293
+ }
286294
287295
/* Obsolete: */
288296
if( hasAnyCap(zAnonCap, "d") ||
289297
hasAnyCap(zDevCap, "d") ||
290298
hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
596604
if( P("cancel") ){
597605
/* User pressed the cancel button. Go back */
598606
cgi_redirect("secaudit0");
599607
}
600608
if( P("apply") ){
609
+ db_unprotect(PROTECT_USER);
601610
db_multi_exec(
602611
"UPDATE user SET cap=''"
603612
" WHERE login IN ('nobody','anonymous');"
604613
"DELETE FROM config WHERE name='public-pages';"
605614
);
615
+ db_protect_pop();
606616
db_set("self-register","0",0);
607617
cgi_redirect("secaudit0");
608618
}
609619
style_header("Make This Website Private");
610620
@ <p>Click the "Make It Private" button below to disable all
611621
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281 @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283 @ from users "anonymous" and "nobody"
284 @ on the <a href="setup_ulist">User Configuration</a> page.
285 }
 
 
 
 
 
 
 
 
286
287 /* Obsolete: */
288 if( hasAnyCap(zAnonCap, "d") ||
289 hasAnyCap(zDevCap, "d") ||
290 hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
596 if( P("cancel") ){
597 /* User pressed the cancel button. Go back */
598 cgi_redirect("secaudit0");
599 }
600 if( P("apply") ){
 
601 db_multi_exec(
602 "UPDATE user SET cap=''"
603 " WHERE login IN ('nobody','anonymous');"
604 "DELETE FROM config WHERE name='public-pages';"
605 );
 
606 db_set("self-register","0",0);
607 cgi_redirect("secaudit0");
608 }
609 style_header("Make This Website Private");
610 @ <p>Click the "Make It Private" button below to disable all
611
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281 @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283 @ from users "anonymous" and "nobody"
284 @ on the <a href="setup_ulist">User Configuration</a> page.
285 }
286
287 /* The strict-manifest-syntax setting should be on. */
288 if( db_get_boolean("strict-manifest-syntax",1)==0 ){
289 @ <li><p><b>WARNING:</b>
290 @ The "strict-manifest-syntax" flag is off. This is a security
291 @ risk. Turn this setting on (its default) to protect the users
292 @ of this repository.
293 }
294
295 /* Obsolete: */
296 if( hasAnyCap(zAnonCap, "d") ||
297 hasAnyCap(zDevCap, "d") ||
298 hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
604 if( P("cancel") ){
605 /* User pressed the cancel button. Go back */
606 cgi_redirect("secaudit0");
607 }
608 if( P("apply") ){
609 db_unprotect(PROTECT_USER);
610 db_multi_exec(
611 "UPDATE user SET cap=''"
612 " WHERE login IN ('nobody','anonymous');"
613 "DELETE FROM config WHERE name='public-pages';"
614 );
615 db_protect_pop();
616 db_set("self-register","0",0);
617 cgi_redirect("secaudit0");
618 }
619 style_header("Make This Website Private");
620 @ <p>Click the "Make It Private" button below to disable all
621
+27
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
2727
*/
2828
void setup_incr_cfgcnt(void){
2929
static int once = 1;
3030
if( once ){
3131
once = 0;
32
+ db_unprotect(PROTECT_CONFIG);
3233
db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
3334
if( db_changes()==0 ){
3435
db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
3536
}
37
+ db_protect_pop();
3638
}
3739
}
3840
3941
/*
4042
** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
195197
}
196198
if( zQ ){
197199
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
198200
if( iQ!=iVal ){
199201
login_verify_csrf_secret();
202
+ db_protect_only(PROTECT_NONE);
200203
db_set(zVar, iQ ? "1" : "0", 0);
204
+ db_protect_pop();
201205
setup_incr_cfgcnt();
202206
admin_log("Set option [%q] to [%q].",
203207
zVar, iQ ? "on" : "off");
204208
iVal = iQ;
205209
}
@@ -230,11 +234,13 @@
230234
const char *zQ = P(zQParm);
231235
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
232236
const int nZQ = (int)strlen(zQ);
233237
login_verify_csrf_secret();
234238
setup_incr_cfgcnt();
239
+ db_protect_only(PROTECT_NONE);
235240
db_set(zVar, zQ, 0);
241
+ db_protect_pop();
236242
admin_log("Set entry_attribute %Q to: %.*s%s",
237243
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
238244
zVal = zQ;
239245
}
240246
@ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
260266
const char *z = db_get(zVar, zDflt);
261267
const char *zQ = P(zQP);
262268
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
263269
const int nZQ = (int)strlen(zQ);
264270
login_verify_csrf_secret();
271
+ db_protect_only(PROTECT_NONE);
265272
db_set(zVar, zQ, 0);
273
+ db_protect_pop();
266274
setup_incr_cfgcnt();
267275
admin_log("Set textarea_attribute %Q to: %.*s%s",
268276
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
269277
z = zQ;
270278
}
@@ -1076,10 +1084,15 @@
10761084
@ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except
10771085
@ in forum posts, make this setting be "<b>btw</b>". The default is an
10781086
@ empty string which means that Fossil never allows Markdown documents
10791087
@ to generate unsafe HTML.
10801088
@ (Property: "safe-html")</p>
1089
+ @ <hr />
1090
+ @ The current interwiki tag map is as follows:
1091
+ interwiki_append_map_table(cgi_output_blob());
1092
+ @ <p>Visit <a href="./intermap">%R/intermap</a> for details or to
1093
+ @ modify the interwiki tag map.
10811094
@ <hr />
10821095
onoff_attribute("Use HTML as wiki markup language",
10831096
"wiki-use-html", "wiki-use-html", 0, 0);
10841097
@ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
10851098
@ but all other wiki formatting will be ignored.</p>
@@ -1157,11 +1170,13 @@
11571170
login_needed(0);
11581171
return;
11591172
}
11601173
db_begin_transaction();
11611174
if( P("clear")!=0 && cgi_csrf_safe(1) ){
1175
+ db_unprotect(PROTECT_CONFIG);
11621176
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1177
+ db_protect_pop();
11631178
cgi_replace_parameter("adunit","");
11641179
cgi_replace_parameter("adright","");
11651180
setup_incr_cfgcnt();
11661181
}
11671182
@@ -1255,10 +1270,11 @@
12551270
if( !g.perm.Admin ){
12561271
login_needed(0);
12571272
return;
12581273
}
12591274
db_begin_transaction();
1275
+ db_unprotect(PROTECT_CONFIG);
12601276
if( !cgi_csrf_safe(1) ){
12611277
/* Allow no state changes if not safe from CSRF */
12621278
}else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
12631279
Blob img;
12641280
Stmt ins;
@@ -1285,10 +1301,11 @@
12851301
cgi_redirect("setup_logo");
12861302
}else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
12871303
Blob img;
12881304
Stmt ins;
12891305
blob_init(&img, aBgImg, szBgImg);
1306
+ db_unprotect(PROTECT_CONFIG);
12901307
db_prepare(&ins,
12911308
"REPLACE INTO config(name,value,mtime)"
12921309
" VALUES('background-image',:bytes,now())"
12931310
);
12941311
db_bind_blob(&ins, ":bytes", &img);
@@ -1297,13 +1314,15 @@
12971314
db_multi_exec(
12981315
"REPLACE INTO config(name,value,mtime)"
12991316
" VALUES('background-mimetype',%Q,now())",
13001317
zBgMime
13011318
);
1319
+ db_protect_pop();
13021320
db_end_transaction(0);
13031321
cgi_redirect("setup_logo");
13041322
}else if( P("clrbg")!=0 ){
1323
+ db_unprotect(PROTECT_CONFIG);
13051324
db_multi_exec(
13061325
"DELETE FROM config WHERE name IN "
13071326
"('background-image','background-mimetype')"
13081327
);
13091328
db_end_transaction(0);
@@ -1310,10 +1329,11 @@
13101329
cgi_redirect("setup_logo");
13111330
}else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
13121331
Blob img;
13131332
Stmt ins;
13141333
blob_init(&img, aIconImg, szIconImg);
1334
+ db_unprotect(PROTECT_CONFIG);
13151335
db_prepare(&ins,
13161336
"REPLACE INTO config(name,value,mtime)"
13171337
" VALUES('icon-image',:bytes,now())"
13181338
);
13191339
db_bind_blob(&ins, ":bytes", &img);
@@ -1322,10 +1342,11 @@
13221342
db_multi_exec(
13231343
"REPLACE INTO config(name,value,mtime)"
13241344
" VALUES('icon-mimetype',%Q,now())",
13251345
zIconMime
13261346
);
1347
+ db_protect_pop();
13271348
db_end_transaction(0);
13281349
cgi_redirect("setup_logo");
13291350
}else if( P("clricon")!=0 ){
13301351
db_multi_exec(
13311352
"DELETE FROM config WHERE name IN "
@@ -1781,22 +1802,27 @@
17811802
const char *zValue
17821803
){
17831804
if( !cgi_csrf_safe(1) ) return;
17841805
if( zNewName[0]==0 || zValue[0]==0 ){
17851806
if( zOldName[0] ){
1807
+ db_unprotect(PROTECT_CONFIG);
17861808
blob_append_sql(pSql,
17871809
"DELETE FROM config WHERE name='walias:%q';\n",
17881810
zOldName);
1811
+ db_protect_pop();
17891812
}
17901813
return;
17911814
}
17921815
if( zOldName[0]==0 ){
1816
+ db_unprotect(PROTECT_CONFIG);
17931817
blob_append_sql(pSql,
17941818
"INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
17951819
zNewName, zValue);
1820
+ db_protect_pop();
17961821
return;
17971822
}
1823
+ db_unprotect(PROTECT_CONFIG);
17981824
if( strcmp(zOldName, zNewName)!=0 ){
17991825
blob_append_sql(pSql,
18001826
"UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
18011827
" WHERE name='walias:%q';\n",
18021828
zNewName, zValue, zOldName);
@@ -1804,10 +1830,11 @@
18041830
blob_append_sql(pSql,
18051831
"UPDATE config SET value=%Q, mtime=now()"
18061832
" WHERE name='walias:%q' AND value<>%Q;\n",
18071833
zValue, zOldName, zValue);
18081834
}
1835
+ db_protect_pop();
18091836
}
18101837
18111838
/*
18121839
** WEBPAGE: waliassetup
18131840
**
18141841
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
27 */
28 void setup_incr_cfgcnt(void){
29 static int once = 1;
30 if( once ){
31 once = 0;
 
32 db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
33 if( db_changes()==0 ){
34 db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
35 }
 
36 }
37 }
38
39 /*
40 ** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
195 }
196 if( zQ ){
197 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
198 if( iQ!=iVal ){
199 login_verify_csrf_secret();
 
200 db_set(zVar, iQ ? "1" : "0", 0);
 
201 setup_incr_cfgcnt();
202 admin_log("Set option [%q] to [%q].",
203 zVar, iQ ? "on" : "off");
204 iVal = iQ;
205 }
@@ -230,11 +234,13 @@
230 const char *zQ = P(zQParm);
231 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
232 const int nZQ = (int)strlen(zQ);
233 login_verify_csrf_secret();
234 setup_incr_cfgcnt();
 
235 db_set(zVar, zQ, 0);
 
236 admin_log("Set entry_attribute %Q to: %.*s%s",
237 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
238 zVal = zQ;
239 }
240 @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
260 const char *z = db_get(zVar, zDflt);
261 const char *zQ = P(zQP);
262 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
263 const int nZQ = (int)strlen(zQ);
264 login_verify_csrf_secret();
 
265 db_set(zVar, zQ, 0);
 
266 setup_incr_cfgcnt();
267 admin_log("Set textarea_attribute %Q to: %.*s%s",
268 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
269 z = zQ;
270 }
@@ -1076,10 +1084,15 @@
1076 @ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except
1077 @ in forum posts, make this setting be "<b>btw</b>". The default is an
1078 @ empty string which means that Fossil never allows Markdown documents
1079 @ to generate unsafe HTML.
1080 @ (Property: "safe-html")</p>
 
 
 
 
 
1081 @ <hr />
1082 onoff_attribute("Use HTML as wiki markup language",
1083 "wiki-use-html", "wiki-use-html", 0, 0);
1084 @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
1085 @ but all other wiki formatting will be ignored.</p>
@@ -1157,11 +1170,13 @@
1157 login_needed(0);
1158 return;
1159 }
1160 db_begin_transaction();
1161 if( P("clear")!=0 && cgi_csrf_safe(1) ){
 
1162 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
 
1163 cgi_replace_parameter("adunit","");
1164 cgi_replace_parameter("adright","");
1165 setup_incr_cfgcnt();
1166 }
1167
@@ -1255,10 +1270,11 @@
1255 if( !g.perm.Admin ){
1256 login_needed(0);
1257 return;
1258 }
1259 db_begin_transaction();
 
1260 if( !cgi_csrf_safe(1) ){
1261 /* Allow no state changes if not safe from CSRF */
1262 }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1263 Blob img;
1264 Stmt ins;
@@ -1285,10 +1301,11 @@
1285 cgi_redirect("setup_logo");
1286 }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
1287 Blob img;
1288 Stmt ins;
1289 blob_init(&img, aBgImg, szBgImg);
 
1290 db_prepare(&ins,
1291 "REPLACE INTO config(name,value,mtime)"
1292 " VALUES('background-image',:bytes,now())"
1293 );
1294 db_bind_blob(&ins, ":bytes", &img);
@@ -1297,13 +1314,15 @@
1297 db_multi_exec(
1298 "REPLACE INTO config(name,value,mtime)"
1299 " VALUES('background-mimetype',%Q,now())",
1300 zBgMime
1301 );
 
1302 db_end_transaction(0);
1303 cgi_redirect("setup_logo");
1304 }else if( P("clrbg")!=0 ){
 
1305 db_multi_exec(
1306 "DELETE FROM config WHERE name IN "
1307 "('background-image','background-mimetype')"
1308 );
1309 db_end_transaction(0);
@@ -1310,10 +1329,11 @@
1310 cgi_redirect("setup_logo");
1311 }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
1312 Blob img;
1313 Stmt ins;
1314 blob_init(&img, aIconImg, szIconImg);
 
1315 db_prepare(&ins,
1316 "REPLACE INTO config(name,value,mtime)"
1317 " VALUES('icon-image',:bytes,now())"
1318 );
1319 db_bind_blob(&ins, ":bytes", &img);
@@ -1322,10 +1342,11 @@
1322 db_multi_exec(
1323 "REPLACE INTO config(name,value,mtime)"
1324 " VALUES('icon-mimetype',%Q,now())",
1325 zIconMime
1326 );
 
1327 db_end_transaction(0);
1328 cgi_redirect("setup_logo");
1329 }else if( P("clricon")!=0 ){
1330 db_multi_exec(
1331 "DELETE FROM config WHERE name IN "
@@ -1781,22 +1802,27 @@
1781 const char *zValue
1782 ){
1783 if( !cgi_csrf_safe(1) ) return;
1784 if( zNewName[0]==0 || zValue[0]==0 ){
1785 if( zOldName[0] ){
 
1786 blob_append_sql(pSql,
1787 "DELETE FROM config WHERE name='walias:%q';\n",
1788 zOldName);
 
1789 }
1790 return;
1791 }
1792 if( zOldName[0]==0 ){
 
1793 blob_append_sql(pSql,
1794 "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
1795 zNewName, zValue);
 
1796 return;
1797 }
 
1798 if( strcmp(zOldName, zNewName)!=0 ){
1799 blob_append_sql(pSql,
1800 "UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
1801 " WHERE name='walias:%q';\n",
1802 zNewName, zValue, zOldName);
@@ -1804,10 +1830,11 @@
1804 blob_append_sql(pSql,
1805 "UPDATE config SET value=%Q, mtime=now()"
1806 " WHERE name='walias:%q' AND value<>%Q;\n",
1807 zValue, zOldName, zValue);
1808 }
 
1809 }
1810
1811 /*
1812 ** WEBPAGE: waliassetup
1813 **
1814
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
27 */
28 void setup_incr_cfgcnt(void){
29 static int once = 1;
30 if( once ){
31 once = 0;
32 db_unprotect(PROTECT_CONFIG);
33 db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
34 if( db_changes()==0 ){
35 db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
36 }
37 db_protect_pop();
38 }
39 }
40
41 /*
42 ** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
197 }
198 if( zQ ){
199 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
200 if( iQ!=iVal ){
201 login_verify_csrf_secret();
202 db_protect_only(PROTECT_NONE);
203 db_set(zVar, iQ ? "1" : "0", 0);
204 db_protect_pop();
205 setup_incr_cfgcnt();
206 admin_log("Set option [%q] to [%q].",
207 zVar, iQ ? "on" : "off");
208 iVal = iQ;
209 }
@@ -230,11 +234,13 @@
234 const char *zQ = P(zQParm);
235 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
236 const int nZQ = (int)strlen(zQ);
237 login_verify_csrf_secret();
238 setup_incr_cfgcnt();
239 db_protect_only(PROTECT_NONE);
240 db_set(zVar, zQ, 0);
241 db_protect_pop();
242 admin_log("Set entry_attribute %Q to: %.*s%s",
243 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
244 zVal = zQ;
245 }
246 @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
266 const char *z = db_get(zVar, zDflt);
267 const char *zQ = P(zQP);
268 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
269 const int nZQ = (int)strlen(zQ);
270 login_verify_csrf_secret();
271 db_protect_only(PROTECT_NONE);
272 db_set(zVar, zQ, 0);
273 db_protect_pop();
274 setup_incr_cfgcnt();
275 admin_log("Set textarea_attribute %Q to: %.*s%s",
276 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
277 z = zQ;
278 }
@@ -1076,10 +1084,15 @@
1084 @ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except
1085 @ in forum posts, make this setting be "<b>btw</b>". The default is an
1086 @ empty string which means that Fossil never allows Markdown documents
1087 @ to generate unsafe HTML.
1088 @ (Property: "safe-html")</p>
1089 @ <hr />
1090 @ The current interwiki tag map is as follows:
1091 interwiki_append_map_table(cgi_output_blob());
1092 @ <p>Visit <a href="./intermap">%R/intermap</a> for details or to
1093 @ modify the interwiki tag map.
1094 @ <hr />
1095 onoff_attribute("Use HTML as wiki markup language",
1096 "wiki-use-html", "wiki-use-html", 0, 0);
1097 @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
1098 @ but all other wiki formatting will be ignored.</p>
@@ -1157,11 +1170,13 @@
1170 login_needed(0);
1171 return;
1172 }
1173 db_begin_transaction();
1174 if( P("clear")!=0 && cgi_csrf_safe(1) ){
1175 db_unprotect(PROTECT_CONFIG);
1176 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1177 db_protect_pop();
1178 cgi_replace_parameter("adunit","");
1179 cgi_replace_parameter("adright","");
1180 setup_incr_cfgcnt();
1181 }
1182
@@ -1255,10 +1270,11 @@
1270 if( !g.perm.Admin ){
1271 login_needed(0);
1272 return;
1273 }
1274 db_begin_transaction();
1275 db_unprotect(PROTECT_CONFIG);
1276 if( !cgi_csrf_safe(1) ){
1277 /* Allow no state changes if not safe from CSRF */
1278 }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1279 Blob img;
1280 Stmt ins;
@@ -1285,10 +1301,11 @@
1301 cgi_redirect("setup_logo");
1302 }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
1303 Blob img;
1304 Stmt ins;
1305 blob_init(&img, aBgImg, szBgImg);
1306 db_unprotect(PROTECT_CONFIG);
1307 db_prepare(&ins,
1308 "REPLACE INTO config(name,value,mtime)"
1309 " VALUES('background-image',:bytes,now())"
1310 );
1311 db_bind_blob(&ins, ":bytes", &img);
@@ -1297,13 +1314,15 @@
1314 db_multi_exec(
1315 "REPLACE INTO config(name,value,mtime)"
1316 " VALUES('background-mimetype',%Q,now())",
1317 zBgMime
1318 );
1319 db_protect_pop();
1320 db_end_transaction(0);
1321 cgi_redirect("setup_logo");
1322 }else if( P("clrbg")!=0 ){
1323 db_unprotect(PROTECT_CONFIG);
1324 db_multi_exec(
1325 "DELETE FROM config WHERE name IN "
1326 "('background-image','background-mimetype')"
1327 );
1328 db_end_transaction(0);
@@ -1310,10 +1329,11 @@
1329 cgi_redirect("setup_logo");
1330 }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
1331 Blob img;
1332 Stmt ins;
1333 blob_init(&img, aIconImg, szIconImg);
1334 db_unprotect(PROTECT_CONFIG);
1335 db_prepare(&ins,
1336 "REPLACE INTO config(name,value,mtime)"
1337 " VALUES('icon-image',:bytes,now())"
1338 );
1339 db_bind_blob(&ins, ":bytes", &img);
@@ -1322,10 +1342,11 @@
1342 db_multi_exec(
1343 "REPLACE INTO config(name,value,mtime)"
1344 " VALUES('icon-mimetype',%Q,now())",
1345 zIconMime
1346 );
1347 db_protect_pop();
1348 db_end_transaction(0);
1349 cgi_redirect("setup_logo");
1350 }else if( P("clricon")!=0 ){
1351 db_multi_exec(
1352 "DELETE FROM config WHERE name IN "
@@ -1781,22 +1802,27 @@
1802 const char *zValue
1803 ){
1804 if( !cgi_csrf_safe(1) ) return;
1805 if( zNewName[0]==0 || zValue[0]==0 ){
1806 if( zOldName[0] ){
1807 db_unprotect(PROTECT_CONFIG);
1808 blob_append_sql(pSql,
1809 "DELETE FROM config WHERE name='walias:%q';\n",
1810 zOldName);
1811 db_protect_pop();
1812 }
1813 return;
1814 }
1815 if( zOldName[0]==0 ){
1816 db_unprotect(PROTECT_CONFIG);
1817 blob_append_sql(pSql,
1818 "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
1819 zNewName, zValue);
1820 db_protect_pop();
1821 return;
1822 }
1823 db_unprotect(PROTECT_CONFIG);
1824 if( strcmp(zOldName, zNewName)!=0 ){
1825 blob_append_sql(pSql,
1826 "UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
1827 " WHERE name='walias:%q';\n",
1828 zNewName, zValue, zOldName);
@@ -1804,10 +1830,11 @@
1830 blob_append_sql(pSql,
1831 "UPDATE config SET value=%Q, mtime=now()"
1832 " WHERE name='walias:%q' AND value<>%Q;\n",
1833 zValue, zOldName, zValue);
1834 }
1835 db_protect_pop();
1836 }
1837
1838 /*
1839 ** WEBPAGE: waliassetup
1840 **
1841
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315315
/* Check for requests to delete the user */
316316
if( P("delete") && cgi_csrf_safe(1) ){
317317
int n;
318318
if( P("verifydelete") ){
319319
/* Verified delete user request */
320
+ db_unprotect(PROTECT_USER);
320321
db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
322
+ db_protect_pop();
321323
moderation_disapprove_for_missing_users();
322324
admin_log("Deleted user [%s] (uid %d).",
323325
PD("login","???")/*safe-for-%s*/, uid);
324326
cgi_redirect(cgi_referer("setup_ulist"));
325327
return;
@@ -401,15 +403,17 @@
401403
@ [Bummer]</a></p>
402404
style_footer();
403405
return;
404406
}
405407
login_verify_csrf_secret();
408
+ db_unprotect(PROTECT_USER);
406409
db_multi_exec(
407410
"REPLACE INTO user(uid,login,info,pw,cap,mtime) "
408411
"VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
409412
uid, zLogin, P("info"), zPw, zCap
410413
);
414
+ db_protect_pop();
411415
setup_incr_cfgcnt();
412416
admin_log( "Updated user [%q] with capabilities [%q].",
413417
zLogin, zCap );
414418
if( atoi(PD("all","0"))>0 ){
415419
Blob sql;
@@ -432,11 +436,13 @@
432436
" mtime=now()"
433437
" WHERE login=%Q;",
434438
zLogin, P("pw"), zLogin, P("info"), zCap,
435439
zOldLogin
436440
);
441
+ db_unprotect(PROTECT_USER);
437442
login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
443
+ db_protect_pop();
438444
blob_reset(&sql);
439445
admin_log( "Updated user [%q] in all login groups "
440446
"with capabilities [%q].",
441447
zLogin, zCap );
442448
if( zErr ){
443449
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315 /* Check for requests to delete the user */
316 if( P("delete") && cgi_csrf_safe(1) ){
317 int n;
318 if( P("verifydelete") ){
319 /* Verified delete user request */
 
320 db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
 
321 moderation_disapprove_for_missing_users();
322 admin_log("Deleted user [%s] (uid %d).",
323 PD("login","???")/*safe-for-%s*/, uid);
324 cgi_redirect(cgi_referer("setup_ulist"));
325 return;
@@ -401,15 +403,17 @@
401 @ [Bummer]</a></p>
402 style_footer();
403 return;
404 }
405 login_verify_csrf_secret();
 
406 db_multi_exec(
407 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
408 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
409 uid, zLogin, P("info"), zPw, zCap
410 );
 
411 setup_incr_cfgcnt();
412 admin_log( "Updated user [%q] with capabilities [%q].",
413 zLogin, zCap );
414 if( atoi(PD("all","0"))>0 ){
415 Blob sql;
@@ -432,11 +436,13 @@
432 " mtime=now()"
433 " WHERE login=%Q;",
434 zLogin, P("pw"), zLogin, P("info"), zCap,
435 zOldLogin
436 );
 
437 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
 
438 blob_reset(&sql);
439 admin_log( "Updated user [%q] in all login groups "
440 "with capabilities [%q].",
441 zLogin, zCap );
442 if( zErr ){
443
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315 /* Check for requests to delete the user */
316 if( P("delete") && cgi_csrf_safe(1) ){
317 int n;
318 if( P("verifydelete") ){
319 /* Verified delete user request */
320 db_unprotect(PROTECT_USER);
321 db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
322 db_protect_pop();
323 moderation_disapprove_for_missing_users();
324 admin_log("Deleted user [%s] (uid %d).",
325 PD("login","???")/*safe-for-%s*/, uid);
326 cgi_redirect(cgi_referer("setup_ulist"));
327 return;
@@ -401,15 +403,17 @@
403 @ [Bummer]</a></p>
404 style_footer();
405 return;
406 }
407 login_verify_csrf_secret();
408 db_unprotect(PROTECT_USER);
409 db_multi_exec(
410 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
411 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
412 uid, zLogin, P("info"), zPw, zCap
413 );
414 db_protect_pop();
415 setup_incr_cfgcnt();
416 admin_log( "Updated user [%q] with capabilities [%q].",
417 zLogin, zCap );
418 if( atoi(PD("all","0"))>0 ){
419 Blob sql;
@@ -432,11 +436,13 @@
436 " mtime=now()"
437 " WHERE login=%Q;",
438 zLogin, P("pw"), zLogin, P("info"), zCap,
439 zOldLogin
440 );
441 db_unprotect(PROTECT_USER);
442 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
443 db_protect_pop();
444 blob_reset(&sql);
445 admin_log( "Updated user [%q] in all login groups "
446 "with capabilities [%q].",
447 zLogin, zCap );
448 if( zErr ){
449
+14
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360360
zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361361
z = builtin_text(zLabel);
362362
fossil_free(zLabel);
363363
}
364364
}
365
+ db_unprotect(PROTECT_CONFIG);
365366
blob_appendf(&val,
366367
"REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
367368
azSkinFile[i], z
368369
);
370
+ db_protect_pop();
369371
}
370372
return blob_str(&val);
371373
}
372374
373375
/*
@@ -402,14 +404,16 @@
402404
login_insert_csrf_secret();
403405
@ </div></form>
404406
style_footer();
405407
return 1;
406408
}
409
+ db_unprotect(PROTECT_CONFIG);
407410
db_multi_exec(
408411
"UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
409412
zNewName, zOldName
410413
);
414
+ db_protect_pop();
411415
return 0;
412416
}
413417
414418
/*
415419
** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
440444
login_insert_csrf_secret();
441445
@ </div></form>
442446
style_footer();
443447
return 1;
444448
}
449
+ db_unprotect(PROTECT_CONFIG);
445450
db_multi_exec(
446451
"INSERT OR IGNORE INTO config(name, value, mtime)"
447452
"VALUES('skin:%q',%Q,now())",
448453
zNewName, zCurrent
449454
);
455
+ db_protect_pop();
450456
return 0;
451457
}
452458
453459
/*
454460
** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
491497
style_footer();
492498
db_end_transaction(1);
493499
return;
494500
}
495501
if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
502
+ db_unprotect(PROTECT_CONFIG);
496503
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
504
+ db_protect_pop();
497505
}
498506
if( P("draftdel")!=0 ){
499507
const char *zDraft = P("name");
500508
if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
509
+ db_unprotect(PROTECT_CONFIG);
501510
db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
511
+ db_protect_pop();
502512
}
503513
}
504514
if( skinRename() || skinSave(zCurrent) ){
505515
db_end_transaction(0);
506516
return;
@@ -521,15 +531,17 @@
521531
}
522532
if( !seen ){
523533
seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
524534
" AND value=%Q", zCurrent);
525535
if( !seen ){
536
+ db_unprotect(PROTECT_CONFIG);
526537
db_multi_exec(
527538
"INSERT INTO config(name,value,mtime) VALUES("
528539
" strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
529540
" %Q,now())", zCurrent
530541
);
542
+ db_protect_pop();
531543
}
532544
}
533545
seen = 0;
534546
for(i=0; i<count(aBuiltinSkin); i++){
535547
if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
867879
if( !seen ){
868880
seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
869881
" AND value=%Q", zCurrent);
870882
}
871883
if( !seen ){
884
+ db_unprotect(PROTECT_CONFIG);
872885
db_multi_exec(
873886
"INSERT INTO config(name,value,mtime) VALUES("
874887
" strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
875888
" %Q,now())", zCurrent
876889
);
890
+ db_protect_pop();
877891
}
878892
879893
/* Publish draft iSkin */
880894
for(i=0; i<count(azSkinFile); i++){
881895
char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
882896
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360 zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361 z = builtin_text(zLabel);
362 fossil_free(zLabel);
363 }
364 }
 
365 blob_appendf(&val,
366 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
367 azSkinFile[i], z
368 );
 
369 }
370 return blob_str(&val);
371 }
372
373 /*
@@ -402,14 +404,16 @@
402 login_insert_csrf_secret();
403 @ </div></form>
404 style_footer();
405 return 1;
406 }
 
407 db_multi_exec(
408 "UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
409 zNewName, zOldName
410 );
 
411 return 0;
412 }
413
414 /*
415 ** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
440 login_insert_csrf_secret();
441 @ </div></form>
442 style_footer();
443 return 1;
444 }
 
445 db_multi_exec(
446 "INSERT OR IGNORE INTO config(name, value, mtime)"
447 "VALUES('skin:%q',%Q,now())",
448 zNewName, zCurrent
449 );
 
450 return 0;
451 }
452
453 /*
454 ** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
491 style_footer();
492 db_end_transaction(1);
493 return;
494 }
495 if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
 
496 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
497 }
498 if( P("draftdel")!=0 ){
499 const char *zDraft = P("name");
500 if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
 
501 db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
 
502 }
503 }
504 if( skinRename() || skinSave(zCurrent) ){
505 db_end_transaction(0);
506 return;
@@ -521,15 +531,17 @@
521 }
522 if( !seen ){
523 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
524 " AND value=%Q", zCurrent);
525 if( !seen ){
 
526 db_multi_exec(
527 "INSERT INTO config(name,value,mtime) VALUES("
528 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
529 " %Q,now())", zCurrent
530 );
 
531 }
532 }
533 seen = 0;
534 for(i=0; i<count(aBuiltinSkin); i++){
535 if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
867 if( !seen ){
868 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
869 " AND value=%Q", zCurrent);
870 }
871 if( !seen ){
 
872 db_multi_exec(
873 "INSERT INTO config(name,value,mtime) VALUES("
874 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
875 " %Q,now())", zCurrent
876 );
 
877 }
878
879 /* Publish draft iSkin */
880 for(i=0; i<count(azSkinFile); i++){
881 char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
882
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360 zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361 z = builtin_text(zLabel);
362 fossil_free(zLabel);
363 }
364 }
365 db_unprotect(PROTECT_CONFIG);
366 blob_appendf(&val,
367 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
368 azSkinFile[i], z
369 );
370 db_protect_pop();
371 }
372 return blob_str(&val);
373 }
374
375 /*
@@ -402,14 +404,16 @@
404 login_insert_csrf_secret();
405 @ </div></form>
406 style_footer();
407 return 1;
408 }
409 db_unprotect(PROTECT_CONFIG);
410 db_multi_exec(
411 "UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
412 zNewName, zOldName
413 );
414 db_protect_pop();
415 return 0;
416 }
417
418 /*
419 ** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
444 login_insert_csrf_secret();
445 @ </div></form>
446 style_footer();
447 return 1;
448 }
449 db_unprotect(PROTECT_CONFIG);
450 db_multi_exec(
451 "INSERT OR IGNORE INTO config(name, value, mtime)"
452 "VALUES('skin:%q',%Q,now())",
453 zNewName, zCurrent
454 );
455 db_protect_pop();
456 return 0;
457 }
458
459 /*
460 ** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
497 style_footer();
498 db_end_transaction(1);
499 return;
500 }
501 if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
502 db_unprotect(PROTECT_CONFIG);
503 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
504 db_protect_pop();
505 }
506 if( P("draftdel")!=0 ){
507 const char *zDraft = P("name");
508 if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
509 db_unprotect(PROTECT_CONFIG);
510 db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
511 db_protect_pop();
512 }
513 }
514 if( skinRename() || skinSave(zCurrent) ){
515 db_end_transaction(0);
516 return;
@@ -521,15 +531,17 @@
531 }
532 if( !seen ){
533 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
534 " AND value=%Q", zCurrent);
535 if( !seen ){
536 db_unprotect(PROTECT_CONFIG);
537 db_multi_exec(
538 "INSERT INTO config(name,value,mtime) VALUES("
539 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
540 " %Q,now())", zCurrent
541 );
542 db_protect_pop();
543 }
544 }
545 seen = 0;
546 for(i=0; i<count(aBuiltinSkin); i++){
547 if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
879 if( !seen ){
880 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
881 " AND value=%Q", zCurrent);
882 }
883 if( !seen ){
884 db_unprotect(PROTECT_CONFIG);
885 db_multi_exec(
886 "INSERT INTO config(name,value,mtime) VALUES("
887 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
888 " %Q,now())", zCurrent
889 );
890 db_protect_pop();
891 }
892
893 /* Publish draft iSkin */
894 for(i=0; i<count(azSkinFile); i++){
895 char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
896
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -3,11 +3,11 @@
33
hexadecimal digits (as is the case for captchas generated by the
44
[../captcha.c module](../captcha.c)) then these WAV files can be
55
concatenated together to generate an audio reading of the captcha, which
66
enables visually impaired users to complete the captcha.
77
8
-Each of the WAV files uses 8000 samples per second, 8 bytes per sample
8
+Each of the WAV files uses 8000 samples per second, 8 bits per sample
99
and are 6000 samples in length.
1010
1111
The recordings are made by Philip Bennefall and are of his own voice.
1212
Mr. Bennefall is himself blind and uses this system implemented with these
1313
recordings to complete captchas for Fossil.
1414
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -3,11 +3,11 @@
3 hexadecimal digits (as is the case for captchas generated by the
4 [../captcha.c module](../captcha.c)) then these WAV files can be
5 concatenated together to generate an audio reading of the captcha, which
6 enables visually impaired users to complete the captcha.
7
8 Each of the WAV files uses 8000 samples per second, 8 bytes per sample
9 and are 6000 samples in length.
10
11 The recordings are made by Philip Bennefall and are of his own voice.
12 Mr. Bennefall is himself blind and uses this system implemented with these
13 recordings to complete captchas for Fossil.
14
--- src/sounds/README.md
+++ src/sounds/README.md
@@ -3,11 +3,11 @@
3 hexadecimal digits (as is the case for captchas generated by the
4 [../captcha.c module](../captcha.c)) then these WAV files can be
5 concatenated together to generate an audio reading of the captcha, which
6 enables visually impaired users to complete the captcha.
7
8 Each of the WAV files uses 8000 samples per second, 8 bits per sample
9 and are 6000 samples in length.
10
11 The recordings are made by Philip Bennefall and are of his own voice.
12 Mr. Bennefall is himself blind and uses this system implemented with these
13 recordings to complete captchas for Fossil.
14
+40
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153153
sqlcmd_decompress, 0, 0);
154154
sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155155
sqlcmd_gather_artifact_stats, 0, 0);
156156
return SQLITE_OK;
157157
}
158
+
159
+/*
160
+** Undocumented test SQL functions:
161
+**
162
+** db_protect(X)
163
+** db_protect_pop(X)
164
+**
165
+** These invoke the corresponding C routines. Misuse may result in
166
+** an assertion fault.
167
+*/
168
+static void sqlcmd_db_protect(
169
+ sqlite3_context *context,
170
+ int argc,
171
+ sqlite3_value **argv
172
+){
173
+ unsigned mask = 0;
174
+ const char *z = (const char*)sqlite3_value_text(argv[0]);
175
+ if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER;
176
+ if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG;
177
+ if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE;
178
+ if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY;
179
+ if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL;
180
+ db_protect(mask);
181
+}
182
+static void sqlcmd_db_protect_pop(
183
+ sqlite3_context *context,
184
+ int argc,
185
+ sqlite3_value **argv
186
+){
187
+ db_protect_pop();
188
+}
189
+
190
+
191
+
158192
159193
/*
160194
** This is the "automatic extension" initializer that runs right after
161195
** the connection to the repository database is opened. Set up the
162196
** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
193227
}
194228
/* Arrange to trace close operations so that static prepared statements
195229
** will get cleaned up when the shell closes the database connection */
196230
if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
197231
sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
232
+ db_protect_only(PROTECT_NONE);
233
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
234
+ sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
235
+ sqlcmd_db_protect, 0, 0);
236
+ sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
237
+ sqlcmd_db_protect_pop, 0, 0);
198238
return SQLITE_OK;
199239
}
200240
201241
/*
202242
** atexit() handler that cleans up global state modified by this module.
203243
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153 sqlcmd_decompress, 0, 0);
154 sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155 sqlcmd_gather_artifact_stats, 0, 0);
156 return SQLITE_OK;
157 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
159 /*
160 ** This is the "automatic extension" initializer that runs right after
161 ** the connection to the repository database is opened. Set up the
162 ** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
193 }
194 /* Arrange to trace close operations so that static prepared statements
195 ** will get cleaned up when the shell closes the database connection */
196 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
197 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
 
 
 
 
 
 
198 return SQLITE_OK;
199 }
200
201 /*
202 ** atexit() handler that cleans up global state modified by this module.
203
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153 sqlcmd_decompress, 0, 0);
154 sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155 sqlcmd_gather_artifact_stats, 0, 0);
156 return SQLITE_OK;
157 }
158
159 /*
160 ** Undocumented test SQL functions:
161 **
162 ** db_protect(X)
163 ** db_protect_pop(X)
164 **
165 ** These invoke the corresponding C routines. Misuse may result in
166 ** an assertion fault.
167 */
168 static void sqlcmd_db_protect(
169 sqlite3_context *context,
170 int argc,
171 sqlite3_value **argv
172 ){
173 unsigned mask = 0;
174 const char *z = (const char*)sqlite3_value_text(argv[0]);
175 if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER;
176 if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG;
177 if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE;
178 if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY;
179 if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL;
180 db_protect(mask);
181 }
182 static void sqlcmd_db_protect_pop(
183 sqlite3_context *context,
184 int argc,
185 sqlite3_value **argv
186 ){
187 db_protect_pop();
188 }
189
190
191
192
193 /*
194 ** This is the "automatic extension" initializer that runs right after
195 ** the connection to the repository database is opened. Set up the
196 ** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
227 }
228 /* Arrange to trace close operations so that static prepared statements
229 ** will get cleaned up when the shell closes the database connection */
230 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
231 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
232 db_protect_only(PROTECT_NONE);
233 sqlite3_set_authorizer(db, db_top_authorizer, db);
234 sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
235 sqlcmd_db_protect, 0, 0);
236 sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
237 sqlcmd_db_protect_pop, 0, 0);
238 return SQLITE_OK;
239 }
240
241 /*
242 ** atexit() handler that cleans up global state modified by this module.
243
+2 -2
--- src/stat.c
+++ src/stat.c
@@ -153,11 +153,11 @@
153153
fsize = file_size(g.zRepositoryName, ExtFILE);
154154
@ <tr><th>Repository&nbsp;Size:</th><td>%,lld(fsize) bytes</td>
155155
@ </td></tr>
156156
if( !brief ){
157157
@ <tr><th>Number&nbsp;Of&nbsp;Artifacts:</th><td>
158
- n = db_int(0, "SELECT count(*) FROM blob");
158
+ n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
159159
m = db_int(0, "SELECT count(*) FROM delta");
160160
@ %.d(n) (%,d(n-m) fulltext and %,d(m) deltas)
161161
if( g.perm.Write ){
162162
@ <a href='%R/artifact_stats'>Details</a>
163163
}
@@ -165,11 +165,11 @@
165165
if( n>0 ){
166166
int a, b;
167167
Stmt q;
168168
@ <tr><th>Uncompressed&nbsp;Artifact&nbsp;Size:</th><td>
169169
db_prepare(&q, "SELECT total(size), avg(size), max(size)"
170
- " FROM blob WHERE size>0 /*scan*/");
170
+ " FROM blob WHERE content IS NOT NULL /*scan*/");
171171
db_step(&q);
172172
t = db_column_int64(&q, 0);
173173
szAvg = db_column_int(&q, 1);
174174
szMax = db_column_int(&q, 2);
175175
db_finalize(&q);
176176
--- src/stat.c
+++ src/stat.c
@@ -153,11 +153,11 @@
153 fsize = file_size(g.zRepositoryName, ExtFILE);
154 @ <tr><th>Repository&nbsp;Size:</th><td>%,lld(fsize) bytes</td>
155 @ </td></tr>
156 if( !brief ){
157 @ <tr><th>Number&nbsp;Of&nbsp;Artifacts:</th><td>
158 n = db_int(0, "SELECT count(*) FROM blob");
159 m = db_int(0, "SELECT count(*) FROM delta");
160 @ %.d(n) (%,d(n-m) fulltext and %,d(m) deltas)
161 if( g.perm.Write ){
162 @ <a href='%R/artifact_stats'>Details</a>
163 }
@@ -165,11 +165,11 @@
165 if( n>0 ){
166 int a, b;
167 Stmt q;
168 @ <tr><th>Uncompressed&nbsp;Artifact&nbsp;Size:</th><td>
169 db_prepare(&q, "SELECT total(size), avg(size), max(size)"
170 " FROM blob WHERE size>0 /*scan*/");
171 db_step(&q);
172 t = db_column_int64(&q, 0);
173 szAvg = db_column_int(&q, 1);
174 szMax = db_column_int(&q, 2);
175 db_finalize(&q);
176
--- src/stat.c
+++ src/stat.c
@@ -153,11 +153,11 @@
153 fsize = file_size(g.zRepositoryName, ExtFILE);
154 @ <tr><th>Repository&nbsp;Size:</th><td>%,lld(fsize) bytes</td>
155 @ </td></tr>
156 if( !brief ){
157 @ <tr><th>Number&nbsp;Of&nbsp;Artifacts:</th><td>
158 n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
159 m = db_int(0, "SELECT count(*) FROM delta");
160 @ %.d(n) (%,d(n-m) fulltext and %,d(m) deltas)
161 if( g.perm.Write ){
162 @ <a href='%R/artifact_stats'>Details</a>
163 }
@@ -165,11 +165,11 @@
165 if( n>0 ){
166 int a, b;
167 Stmt q;
168 @ <tr><th>Uncompressed&nbsp;Artifact&nbsp;Size:</th><td>
169 db_prepare(&q, "SELECT total(size), avg(size), max(size)"
170 " FROM blob WHERE content IS NOT NULL /*scan*/");
171 db_step(&q);
172 t = db_column_int64(&q, 0);
173 szAvg = db_column_int(&q, 1);
174 szMax = db_column_int(&q, 2);
175 db_finalize(&q);
176
+9 -2
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425425
/* fossil remote off
426426
** Forget the last-sync-URL and its password
427427
*/
428428
if( g.argc!=3 ) usage("off");
429429
remote_delete_default:
430
+ db_unprotect(PROTECT_CONFIG);
430431
db_multi_exec(
431432
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
432433
);
434
+ db_protect_pop();
433435
return;
434436
}
435437
if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
436438
Stmt q;
437439
if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
457459
zName = g.argv[3];
458460
zUrl = g.argv[4];
459461
if( strcmp(zName,"default")==0 ) goto remote_add_default;
460462
url_parse_local(zUrl, URL_PROMPT_PW, &x);
461463
db_begin_write();
464
+ db_unprotect(PROTECT_CONFIG);
462465
db_multi_exec(
463466
"REPLACE INTO config(name, value, mtime)"
464467
" VALUES('sync-url:%q',%Q,now())",
465468
zName, x.canonical
466469
);
@@ -467,21 +470,24 @@
467470
db_multi_exec(
468471
"REPLACE INTO config(name, value, mtime)"
469472
" VALUES('sync-pw:%q',obscure(%Q),now())",
470473
zName, x.passwd
471474
);
475
+ db_protect_pop();
472476
db_commit_transaction();
473477
return;
474478
}
475479
if( strncmp(zArg, "delete", nArg)==0 ){
476480
char *zName;
477481
if( g.argc!=4 ) usage("delete NAME");
478482
zName = g.argv[3];
479483
if( strcmp(zName,"default")==0 ) goto remote_delete_default;
480484
db_begin_write();
485
+ db_unprotect(PROTECT_CONFIG);
481486
db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
482487
db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
488
+ db_protect_pop();
483489
db_commit_transaction();
484490
return;
485491
}
486492
if( sqlite3_strlike("http://%",zArg,0)==0
487493
|| sqlite3_strlike("https://%",zArg,0)==0
@@ -505,20 +511,20 @@
505511
**
506512
** Usage: %fossil backup ?OPTIONS? FILE|DIRECTORY
507513
**
508514
** Make a backup of the repository into the named file or into the named
509515
** directory. This backup is guaranteed to be consistent even if there are
510
-** concurrent chnages taking place on the repository. In other words, it
516
+** concurrent changes taking place on the repository. In other words, it
511517
** is safe to run "fossil backup" on a repository that is in active use.
512518
**
513519
** Only the main repository database is backed up by this command. The
514520
** open checkout file (if any) is not saved. Nor is the global configuration
515521
** database.
516522
**
517523
** Options:
518524
**
519
-** --overwrite OK to overwrite an existing file.
525
+** --overwrite OK to overwrite an existing file
520526
** -R NAME Filename of the repository to backup
521527
*/
522528
void backup_cmd(void){
523529
char *zDest;
524530
int bOverwrite = 0;
@@ -539,7 +545,8 @@
539545
}
540546
}else{
541547
fossil_fatal("backup \"%s\" already exists", zDest);
542548
}
543549
}
550
+ db_unprotect(PROTECT_ALL);
544551
db_multi_exec("VACUUM repository INTO %Q", zDest);
545552
}
546553
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425 /* fossil remote off
426 ** Forget the last-sync-URL and its password
427 */
428 if( g.argc!=3 ) usage("off");
429 remote_delete_default:
 
430 db_multi_exec(
431 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
432 );
 
433 return;
434 }
435 if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
436 Stmt q;
437 if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
457 zName = g.argv[3];
458 zUrl = g.argv[4];
459 if( strcmp(zName,"default")==0 ) goto remote_add_default;
460 url_parse_local(zUrl, URL_PROMPT_PW, &x);
461 db_begin_write();
 
462 db_multi_exec(
463 "REPLACE INTO config(name, value, mtime)"
464 " VALUES('sync-url:%q',%Q,now())",
465 zName, x.canonical
466 );
@@ -467,21 +470,24 @@
467 db_multi_exec(
468 "REPLACE INTO config(name, value, mtime)"
469 " VALUES('sync-pw:%q',obscure(%Q),now())",
470 zName, x.passwd
471 );
 
472 db_commit_transaction();
473 return;
474 }
475 if( strncmp(zArg, "delete", nArg)==0 ){
476 char *zName;
477 if( g.argc!=4 ) usage("delete NAME");
478 zName = g.argv[3];
479 if( strcmp(zName,"default")==0 ) goto remote_delete_default;
480 db_begin_write();
 
481 db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
482 db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
 
483 db_commit_transaction();
484 return;
485 }
486 if( sqlite3_strlike("http://%",zArg,0)==0
487 || sqlite3_strlike("https://%",zArg,0)==0
@@ -505,20 +511,20 @@
505 **
506 ** Usage: %fossil backup ?OPTIONS? FILE|DIRECTORY
507 **
508 ** Make a backup of the repository into the named file or into the named
509 ** directory. This backup is guaranteed to be consistent even if there are
510 ** concurrent chnages taking place on the repository. In other words, it
511 ** is safe to run "fossil backup" on a repository that is in active use.
512 **
513 ** Only the main repository database is backed up by this command. The
514 ** open checkout file (if any) is not saved. Nor is the global configuration
515 ** database.
516 **
517 ** Options:
518 **
519 ** --overwrite OK to overwrite an existing file.
520 ** -R NAME Filename of the repository to backup
521 */
522 void backup_cmd(void){
523 char *zDest;
524 int bOverwrite = 0;
@@ -539,7 +545,8 @@
539 }
540 }else{
541 fossil_fatal("backup \"%s\" already exists", zDest);
542 }
543 }
 
544 db_multi_exec("VACUUM repository INTO %Q", zDest);
545 }
546
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425 /* fossil remote off
426 ** Forget the last-sync-URL and its password
427 */
428 if( g.argc!=3 ) usage("off");
429 remote_delete_default:
430 db_unprotect(PROTECT_CONFIG);
431 db_multi_exec(
432 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
433 );
434 db_protect_pop();
435 return;
436 }
437 if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
438 Stmt q;
439 if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
459 zName = g.argv[3];
460 zUrl = g.argv[4];
461 if( strcmp(zName,"default")==0 ) goto remote_add_default;
462 url_parse_local(zUrl, URL_PROMPT_PW, &x);
463 db_begin_write();
464 db_unprotect(PROTECT_CONFIG);
465 db_multi_exec(
466 "REPLACE INTO config(name, value, mtime)"
467 " VALUES('sync-url:%q',%Q,now())",
468 zName, x.canonical
469 );
@@ -467,21 +470,24 @@
470 db_multi_exec(
471 "REPLACE INTO config(name, value, mtime)"
472 " VALUES('sync-pw:%q',obscure(%Q),now())",
473 zName, x.passwd
474 );
475 db_protect_pop();
476 db_commit_transaction();
477 return;
478 }
479 if( strncmp(zArg, "delete", nArg)==0 ){
480 char *zName;
481 if( g.argc!=4 ) usage("delete NAME");
482 zName = g.argv[3];
483 if( strcmp(zName,"default")==0 ) goto remote_delete_default;
484 db_begin_write();
485 db_unprotect(PROTECT_CONFIG);
486 db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
487 db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
488 db_protect_pop();
489 db_commit_transaction();
490 return;
491 }
492 if( sqlite3_strlike("http://%",zArg,0)==0
493 || sqlite3_strlike("https://%",zArg,0)==0
@@ -505,20 +511,20 @@
511 **
512 ** Usage: %fossil backup ?OPTIONS? FILE|DIRECTORY
513 **
514 ** Make a backup of the repository into the named file or into the named
515 ** directory. This backup is guaranteed to be consistent even if there are
516 ** concurrent changes taking place on the repository. In other words, it
517 ** is safe to run "fossil backup" on a repository that is in active use.
518 **
519 ** Only the main repository database is backed up by this command. The
520 ** open checkout file (if any) is not saved. Nor is the global configuration
521 ** database.
522 **
523 ** Options:
524 **
525 ** --overwrite OK to overwrite an existing file
526 ** -R NAME Filename of the repository to backup
527 */
528 void backup_cmd(void){
529 char *zDest;
530 int bOverwrite = 0;
@@ -539,7 +545,8 @@
545 }
546 }else{
547 fossil_fatal("backup \"%s\" already exists", zDest);
548 }
549 }
550 db_unprotect(PROTECT_ALL);
551 db_multi_exec("VACUUM repository INTO %Q", zDest);
552 }
553
+1 -2
--- src/tkt.c
+++ src/tkt.c
@@ -451,16 +451,15 @@
451451
db_multi_exec(
452452
"DROP TABLE IF EXISTS ticket;"
453453
"DROP TABLE IF EXISTS ticketchng;"
454454
);
455455
zSql = ticket_table_schema();
456
+ db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
456457
if( separateConnection ){
457458
if( db_transaction_nesting_depth() ) db_end_transaction(0);
458
- db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
459459
db_init_database(g.zRepositoryName, zSql, 0);
460460
}else{
461
- db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
462461
db_multi_exec("%s", zSql/*safe-for-%s*/);
463462
}
464463
db_clear_authorizer();
465464
fossil_free(zSql);
466465
}
467466
--- src/tkt.c
+++ src/tkt.c
@@ -451,16 +451,15 @@
451 db_multi_exec(
452 "DROP TABLE IF EXISTS ticket;"
453 "DROP TABLE IF EXISTS ticketchng;"
454 );
455 zSql = ticket_table_schema();
 
456 if( separateConnection ){
457 if( db_transaction_nesting_depth() ) db_end_transaction(0);
458 db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
459 db_init_database(g.zRepositoryName, zSql, 0);
460 }else{
461 db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
462 db_multi_exec("%s", zSql/*safe-for-%s*/);
463 }
464 db_clear_authorizer();
465 fossil_free(zSql);
466 }
467
--- src/tkt.c
+++ src/tkt.c
@@ -451,16 +451,15 @@
451 db_multi_exec(
452 "DROP TABLE IF EXISTS ticket;"
453 "DROP TABLE IF EXISTS ticketchng;"
454 );
455 zSql = ticket_table_schema();
456 db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
457 if( separateConnection ){
458 if( db_transaction_nesting_depth() ) db_end_transaction(0);
 
459 db_init_database(g.zRepositoryName, zSql, 0);
460 }else{
 
461 db_multi_exec("%s", zSql/*safe-for-%s*/);
462 }
463 db_clear_authorizer();
464 fossil_free(zSql);
465 }
466
+29 -18
--- src/translate.c
+++ src/translate.c
@@ -155,19 +155,24 @@
155155
fprintf(out,"\n");
156156
}else{
157157
fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
158158
}
159159
}else{
160
- /* Otherwise (if the last non-whitespace was not '=') then generate
161
- ** a cgi_printf() statement whose format is the text following the '@'.
162
- ** Substrings of the form "%C(...)" (where C is any sequence of
163
- ** characters other than \000 and '(') will put "%C" in the
164
- ** format and add the "(...)" as an argument to the cgi_printf call.
160
+ /* Otherwise (if the last non-whitespace was not '=') then generate a
161
+ ** cgi_printf() statement whose format is the text following the '@'.
162
+ ** Substrings of the form "%C(...)" (where C is any sequence of characters
163
+ ** other than \000 and '(') will put "%C" in the format and add the
164
+ ** "(...)" as an argument to the cgi_printf call. Each '*' character
165
+ ** present in C (max two) causes one more "(...)" sequence to be consumed.
166
+ ** For example, "%*.*d(4)(2)(1)" converts to "%*.*d" with arguments "4",
167
+ ** "2", and "1", which will be used as the field width, precision, and
168
+ ** value, respectively, producing a final formatted result of " 01".
165169
*/
166170
const char *zNewline = "\\n";
167171
int indent;
168172
int nC;
173
+ int nParam;
169174
char c;
170175
i++;
171176
if( isspace(zLine[i]) ){ i++; }
172177
indent = i;
173178
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
@@ -177,25 +182,31 @@
177182
break;
178183
}
179184
if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
180185
zOut[j++] = zLine[i];
181186
if( zLine[i]!='%' || zLine[i+1]=='%' || zLine[i+1]==0 ) continue;
182
- for(nC=1; zLine[i+nC] && zLine[i+nC]!='('; nC++){}
187
+ nParam=1;
188
+ for(nC=1; zLine[i+nC] && zLine[i+nC]!='('; nC++){
189
+ if( zLine[i+nC]=='*' && nParam < 3 ) nParam++;
190
+ }
183191
if( zLine[i+nC]!='(' || !isalpha(zLine[i+nC-1]) ) continue;
184192
while( --nC ) zOut[j++] = zLine[++i];
185
- zArg[nArg++] = ',';
186
- k = 0; i++;
187
- while( (c = zLine[i])!=0 ){
188
- zArg[nArg++] = c;
189
- if( c==')' ){
190
- k--;
191
- if( k==0 ) break;
192
- }else if( c=='(' ){
193
- k++;
194
- }
195
- i++;
196
- }
193
+ do{
194
+ zArg[nArg++] = ',';
195
+ k = 0; i++;
196
+ if( zLine[i]!='(' ) break;
197
+ while( (c = zLine[i])!=0 ){
198
+ zArg[nArg++] = c;
199
+ if( c==')' ){
200
+ k--;
201
+ if( k==0 ) break;
202
+ }else if( c=='(' ){
203
+ k++;
204
+ }
205
+ i++;
206
+ }
207
+ }while( --nParam );
197208
}
198209
zOut[j] = 0;
199210
if( !inPrint ){
200211
fprintf(out,"%*scgi_printf(\"%s%s\"",indent-2,"", zOut, zNewline);
201212
inPrint = 1;
202213
--- src/translate.c
+++ src/translate.c
@@ -155,19 +155,24 @@
155 fprintf(out,"\n");
156 }else{
157 fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
158 }
159 }else{
160 /* Otherwise (if the last non-whitespace was not '=') then generate
161 ** a cgi_printf() statement whose format is the text following the '@'.
162 ** Substrings of the form "%C(...)" (where C is any sequence of
163 ** characters other than \000 and '(') will put "%C" in the
164 ** format and add the "(...)" as an argument to the cgi_printf call.
 
 
 
 
165 */
166 const char *zNewline = "\\n";
167 int indent;
168 int nC;
 
169 char c;
170 i++;
171 if( isspace(zLine[i]) ){ i++; }
172 indent = i;
173 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
@@ -177,25 +182,31 @@
177 break;
178 }
179 if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
180 zOut[j++] = zLine[i];
181 if( zLine[i]!='%' || zLine[i+1]=='%' || zLine[i+1]==0 ) continue;
182 for(nC=1; zLine[i+nC] && zLine[i+nC]!='('; nC++){}
 
 
 
183 if( zLine[i+nC]!='(' || !isalpha(zLine[i+nC-1]) ) continue;
184 while( --nC ) zOut[j++] = zLine[++i];
185 zArg[nArg++] = ',';
186 k = 0; i++;
187 while( (c = zLine[i])!=0 ){
188 zArg[nArg++] = c;
189 if( c==')' ){
190 k--;
191 if( k==0 ) break;
192 }else if( c=='(' ){
193 k++;
194 }
195 i++;
196 }
 
 
 
197 }
198 zOut[j] = 0;
199 if( !inPrint ){
200 fprintf(out,"%*scgi_printf(\"%s%s\"",indent-2,"", zOut, zNewline);
201 inPrint = 1;
202
--- src/translate.c
+++ src/translate.c
@@ -155,19 +155,24 @@
155 fprintf(out,"\n");
156 }else{
157 fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
158 }
159 }else{
160 /* Otherwise (if the last non-whitespace was not '=') then generate a
161 ** cgi_printf() statement whose format is the text following the '@'.
162 ** Substrings of the form "%C(...)" (where C is any sequence of characters
163 ** other than \000 and '(') will put "%C" in the format and add the
164 ** "(...)" as an argument to the cgi_printf call. Each '*' character
165 ** present in C (max two) causes one more "(...)" sequence to be consumed.
166 ** For example, "%*.*d(4)(2)(1)" converts to "%*.*d" with arguments "4",
167 ** "2", and "1", which will be used as the field width, precision, and
168 ** value, respectively, producing a final formatted result of " 01".
169 */
170 const char *zNewline = "\\n";
171 int indent;
172 int nC;
173 int nParam;
174 char c;
175 i++;
176 if( isspace(zLine[i]) ){ i++; }
177 indent = i;
178 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
@@ -177,25 +182,31 @@
182 break;
183 }
184 if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
185 zOut[j++] = zLine[i];
186 if( zLine[i]!='%' || zLine[i+1]=='%' || zLine[i+1]==0 ) continue;
187 nParam=1;
188 for(nC=1; zLine[i+nC] && zLine[i+nC]!='('; nC++){
189 if( zLine[i+nC]=='*' && nParam < 3 ) nParam++;
190 }
191 if( zLine[i+nC]!='(' || !isalpha(zLine[i+nC-1]) ) continue;
192 while( --nC ) zOut[j++] = zLine[++i];
193 do{
194 zArg[nArg++] = ',';
195 k = 0; i++;
196 if( zLine[i]!='(' ) break;
197 while( (c = zLine[i])!=0 ){
198 zArg[nArg++] = c;
199 if( c==')' ){
200 k--;
201 if( k==0 ) break;
202 }else if( c=='(' ){
203 k++;
204 }
205 i++;
206 }
207 }while( --nParam );
208 }
209 zOut[j] = 0;
210 if( !inPrint ){
211 fprintf(out,"%*scgi_printf(\"%s%s\"",indent-2,"", zOut, zNewline);
212 inPrint = 1;
213
+3 -3
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
5050
int isHttps; /* True if a "https:" url */
5151
int isSsh; /* True if an "ssh:" url */
5252
int isAlias; /* Input URL was an alias */
5353
char *name; /* Hostname for http: or filename for file: */
5454
char *hostname; /* The HOST: parameter on http headers */
55
- const char *protocol; /* "http" or "https" or "ssh" */
55
+ const char *protocol; /* "http" or "https" or "ssh" or "file" */
5656
int port; /* TCP port number for http: or https: */
5757
int dfltPort; /* The default port for the given protocol */
5858
char *path; /* Pathname for http: */
5959
char *user; /* User id for http: */
6060
char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
7676
** as follows:
7777
**
7878
** isFile True if FILE:
7979
** isHttps True if HTTPS:
8080
** isSsh True if SSH:
81
-** protocol "http" or "https" or "file"
81
+** protocol "http" or "https" or "file" or "ssh"
8282
** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
8383
** port TCP port number for HTTP or HTTPS.
8484
** dfltPort Default TCP port number (80 or 443).
8585
** path Path name for HTTP or HTTPS.
8686
** user Userid.
@@ -305,11 +305,11 @@
305305
** form last-sync-pw.
306306
**
307307
** g.url.isFile True if FILE:
308308
** g.url.isHttps True if HTTPS:
309309
** g.url.isSsh True if SSH:
310
-** g.url.protocol "http" or "https" or "file"
310
+** g.url.protocol "http" or "https" or "file" or "ssh"
311311
** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312312
** g.url.port TCP port number for HTTP or HTTPS.
313313
** g.url.dfltPort Default TCP port number (80 or 443).
314314
** g.url.path Path name for HTTP or HTTPS.
315315
** g.url.user Userid.
316316
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
50 int isHttps; /* True if a "https:" url */
51 int isSsh; /* True if an "ssh:" url */
52 int isAlias; /* Input URL was an alias */
53 char *name; /* Hostname for http: or filename for file: */
54 char *hostname; /* The HOST: parameter on http headers */
55 const char *protocol; /* "http" or "https" or "ssh" */
56 int port; /* TCP port number for http: or https: */
57 int dfltPort; /* The default port for the given protocol */
58 char *path; /* Pathname for http: */
59 char *user; /* User id for http: */
60 char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
76 ** as follows:
77 **
78 ** isFile True if FILE:
79 ** isHttps True if HTTPS:
80 ** isSsh True if SSH:
81 ** protocol "http" or "https" or "file"
82 ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
83 ** port TCP port number for HTTP or HTTPS.
84 ** dfltPort Default TCP port number (80 or 443).
85 ** path Path name for HTTP or HTTPS.
86 ** user Userid.
@@ -305,11 +305,11 @@
305 ** form last-sync-pw.
306 **
307 ** g.url.isFile True if FILE:
308 ** g.url.isHttps True if HTTPS:
309 ** g.url.isSsh True if SSH:
310 ** g.url.protocol "http" or "https" or "file"
311 ** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312 ** g.url.port TCP port number for HTTP or HTTPS.
313 ** g.url.dfltPort Default TCP port number (80 or 443).
314 ** g.url.path Path name for HTTP or HTTPS.
315 ** g.url.user Userid.
316
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
50 int isHttps; /* True if a "https:" url */
51 int isSsh; /* True if an "ssh:" url */
52 int isAlias; /* Input URL was an alias */
53 char *name; /* Hostname for http: or filename for file: */
54 char *hostname; /* The HOST: parameter on http headers */
55 const char *protocol; /* "http" or "https" or "ssh" or "file" */
56 int port; /* TCP port number for http: or https: */
57 int dfltPort; /* The default port for the given protocol */
58 char *path; /* Pathname for http: */
59 char *user; /* User id for http: */
60 char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
76 ** as follows:
77 **
78 ** isFile True if FILE:
79 ** isHttps True if HTTPS:
80 ** isSsh True if SSH:
81 ** protocol "http" or "https" or "file" or "ssh"
82 ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
83 ** port TCP port number for HTTP or HTTPS.
84 ** dfltPort Default TCP port number (80 or 443).
85 ** path Path name for HTTP or HTTPS.
86 ** user Userid.
@@ -305,11 +305,11 @@
305 ** form last-sync-pw.
306 **
307 ** g.url.isFile True if FILE:
308 ** g.url.isHttps True if HTTPS:
309 ** g.url.isSsh True if SSH:
310 ** g.url.protocol "http" or "https" or "file" or "ssh"
311 ** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312 ** g.url.port TCP port number for HTTP or HTTPS.
313 ** g.url.dfltPort Default TCP port number (80 or 443).
314 ** g.url.path Path name for HTTP or HTTPS.
315 ** g.url.user Userid.
316
+5
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432432
}
433433
if( blob_size(&pw)==0 ){
434434
fossil_print("password unchanged\n");
435435
}else{
436436
char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
437
+ db_unprotect(PROTECT_USER);
437438
db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
438439
zSecret, uid);
440
+ db_protect_pop();
439441
free(zSecret);
440442
}
441443
}else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
442444
int uid;
443445
if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
446448
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
447449
if( uid==0 ){
448450
fossil_fatal("no such user: %s", g.argv[3]);
449451
}
450452
if( g.argc==5 ){
453
+ db_unprotect(PROTECT_USER);
451454
db_multi_exec(
452455
"UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
453456
g.argv[4], uid
454457
);
458
+ db_protect_pop();
455459
}
456460
fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
457461
}else{
458462
fossil_fatal("user subcommand should be one of: "
459463
"capabilities default list new password");
@@ -573,10 +577,11 @@
573577
void user_hash_passwords_cmd(void){
574578
if( g.argc!=3 ) usage("REPOSITORY");
575579
db_open_repository(g.argv[2]);
576580
sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
577581
sha1_shared_secret_sql_function, 0, 0);
582
+ db_unprotect(PROTECT_ALL);
578583
db_multi_exec(
579584
"UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
580585
" WHERE length(pw)>0 AND length(pw)!=40"
581586
);
582587
}
583588
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432 }
433 if( blob_size(&pw)==0 ){
434 fossil_print("password unchanged\n");
435 }else{
436 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
 
437 db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
438 zSecret, uid);
 
439 free(zSecret);
440 }
441 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
442 int uid;
443 if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
446 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
447 if( uid==0 ){
448 fossil_fatal("no such user: %s", g.argv[3]);
449 }
450 if( g.argc==5 ){
 
451 db_multi_exec(
452 "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
453 g.argv[4], uid
454 );
 
455 }
456 fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
457 }else{
458 fossil_fatal("user subcommand should be one of: "
459 "capabilities default list new password");
@@ -573,10 +577,11 @@
573 void user_hash_passwords_cmd(void){
574 if( g.argc!=3 ) usage("REPOSITORY");
575 db_open_repository(g.argv[2]);
576 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
577 sha1_shared_secret_sql_function, 0, 0);
 
578 db_multi_exec(
579 "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
580 " WHERE length(pw)>0 AND length(pw)!=40"
581 );
582 }
583
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432 }
433 if( blob_size(&pw)==0 ){
434 fossil_print("password unchanged\n");
435 }else{
436 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
437 db_unprotect(PROTECT_USER);
438 db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
439 zSecret, uid);
440 db_protect_pop();
441 free(zSecret);
442 }
443 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
444 int uid;
445 if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
448 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
449 if( uid==0 ){
450 fossil_fatal("no such user: %s", g.argv[3]);
451 }
452 if( g.argc==5 ){
453 db_unprotect(PROTECT_USER);
454 db_multi_exec(
455 "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
456 g.argv[4], uid
457 );
458 db_protect_pop();
459 }
460 fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
461 }else{
462 fossil_fatal("user subcommand should be one of: "
463 "capabilities default list new password");
@@ -573,10 +577,11 @@
577 void user_hash_passwords_cmd(void){
578 if( g.argc!=3 ) usage("REPOSITORY");
579 db_open_repository(g.argv[2]);
580 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
581 sha1_shared_secret_sql_function, 0, 0);
582 db_unprotect(PROTECT_ALL);
583 db_multi_exec(
584 "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
585 " WHERE length(pw)>0 AND length(pw)!=40"
586 );
587 }
588
+11 -1
--- src/util.c
+++ src/util.c
@@ -691,11 +691,11 @@
691691
int i;
692692
char z[60];
693693
694694
/* Source characters for the password. Omit characters like "0", "O",
695695
** "1" and "I" that might be easily confused */
696
- static const char zAlphabet[] =
696
+ static const char zAlphabet[] =
697697
/* 0 1 2 3 4 5 */
698698
/* 123456789 123456789 123456789 123456789 123456789 123456 */
699699
"23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
700700
701701
if( N<8 ) N = 8;
@@ -728,5 +728,15 @@
728728
if( g.argc>=3 ){
729729
N = atoi(g.argv[2]);
730730
}
731731
fossil_print("%s\n", fossil_random_password(N));
732732
}
733
+
734
+/*
735
+** Return the number of decimal digits in a nonnegative integer. This is useful
736
+** when formatting text.
737
+*/
738
+int fossil_num_digits(int n){
739
+ return n< 10 ? 1 : n< 100 ? 2 : n< 1000 ? 3
740
+ : n< 10000 ? 4 : n< 100000 ? 5 : n< 1000000 ? 6
741
+ : n<10000000 ? 7 : n<100000000 ? 8 : n<1000000000 ? 9 : 10;
742
+}
733743
--- src/util.c
+++ src/util.c
@@ -691,11 +691,11 @@
691 int i;
692 char z[60];
693
694 /* Source characters for the password. Omit characters like "0", "O",
695 ** "1" and "I" that might be easily confused */
696 static const char zAlphabet[] =
697 /* 0 1 2 3 4 5 */
698 /* 123456789 123456789 123456789 123456789 123456789 123456 */
699 "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
700
701 if( N<8 ) N = 8;
@@ -728,5 +728,15 @@
728 if( g.argc>=3 ){
729 N = atoi(g.argv[2]);
730 }
731 fossil_print("%s\n", fossil_random_password(N));
732 }
 
 
 
 
 
 
 
 
 
 
733
--- src/util.c
+++ src/util.c
@@ -691,11 +691,11 @@
691 int i;
692 char z[60];
693
694 /* Source characters for the password. Omit characters like "0", "O",
695 ** "1" and "I" that might be easily confused */
696 static const char zAlphabet[] =
697 /* 0 1 2 3 4 5 */
698 /* 123456789 123456789 123456789 123456789 123456789 123456 */
699 "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
700
701 if( N<8 ) N = 8;
@@ -728,5 +728,15 @@
728 if( g.argc>=3 ){
729 N = atoi(g.argv[2]);
730 }
731 fossil_print("%s\n", fossil_random_password(N));
732 }
733
734 /*
735 ** Return the number of decimal digits in a nonnegative integer. This is useful
736 ** when formatting text.
737 */
738 int fossil_num_digits(int n){
739 return n< 10 ? 1 : n< 100 ? 2 : n< 1000 ? 3
740 : n< 10000 ? 4 : n< 100000 ? 5 : n< 1000000 ? 6
741 : n<10000000 ? 7 : n<100000000 ? 8 : n<1000000000 ? 9 : 10;
742 }
743
+6 -1
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220220
if( fTxt ){
221221
style_submenu_element("Formatted", "%R/md_rules");
222222
}else{
223223
style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224224
}
225
+ style_submenu_element("Wiki", "%R/wiki_rules");
225226
blob_init(&x, builtin_text("markdown.md"), -1);
226227
blob_materialize(&x);
228
+ interwiki_append_map_table(&x);
227229
safe_html_context(DOCSRC_TRUSTED);
228230
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
229231
blob_reset(&x);
230232
style_footer();
231233
}
@@ -242,12 +244,14 @@
242244
if( fTxt ){
243245
style_submenu_element("Formatted", "%R/wiki_rules");
244246
}else{
245247
style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
246248
}
249
+ style_submenu_element("Markdown","%R/md_rules");
247250
blob_init(&x, builtin_text("wiki.wiki"), -1);
248251
blob_materialize(&x);
252
+ interwiki_append_map_table(&x);
249253
safe_html_context(DOCSRC_TRUSTED);
250254
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
251255
blob_reset(&x);
252256
style_footer();
253257
}
@@ -978,11 +982,12 @@
978982
Stmt q = empty_Stmt;
979983
int n = 0;
980984
db_begin_transaction();
981985
db_prepare(&q, "SELECT"
982986
" substr(tagname,6) AS name"
983
- " FROM tag WHERE tagname GLOB 'wiki-*'"
987
+ " FROM tag JOIN tagxref USING('tagid')"
988
+ " WHERE tagname GLOB 'wiki-*'"
984989
" UNION SELECT 'Sandbox' AS name"
985990
" ORDER BY name COLLATE NOCASE");
986991
CX("[");
987992
while( SQLITE_ROW==db_step(&q) ){
988993
char const * zName = db_column_text(&q,0);
989994
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220 if( fTxt ){
221 style_submenu_element("Formatted", "%R/md_rules");
222 }else{
223 style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224 }
 
225 blob_init(&x, builtin_text("markdown.md"), -1);
226 blob_materialize(&x);
 
227 safe_html_context(DOCSRC_TRUSTED);
228 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
229 blob_reset(&x);
230 style_footer();
231 }
@@ -242,12 +244,14 @@
242 if( fTxt ){
243 style_submenu_element("Formatted", "%R/wiki_rules");
244 }else{
245 style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
246 }
 
247 blob_init(&x, builtin_text("wiki.wiki"), -1);
248 blob_materialize(&x);
 
249 safe_html_context(DOCSRC_TRUSTED);
250 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
251 blob_reset(&x);
252 style_footer();
253 }
@@ -978,11 +982,12 @@
978 Stmt q = empty_Stmt;
979 int n = 0;
980 db_begin_transaction();
981 db_prepare(&q, "SELECT"
982 " substr(tagname,6) AS name"
983 " FROM tag WHERE tagname GLOB 'wiki-*'"
 
984 " UNION SELECT 'Sandbox' AS name"
985 " ORDER BY name COLLATE NOCASE");
986 CX("[");
987 while( SQLITE_ROW==db_step(&q) ){
988 char const * zName = db_column_text(&q,0);
989
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220 if( fTxt ){
221 style_submenu_element("Formatted", "%R/md_rules");
222 }else{
223 style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224 }
225 style_submenu_element("Wiki", "%R/wiki_rules");
226 blob_init(&x, builtin_text("markdown.md"), -1);
227 blob_materialize(&x);
228 interwiki_append_map_table(&x);
229 safe_html_context(DOCSRC_TRUSTED);
230 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
231 blob_reset(&x);
232 style_footer();
233 }
@@ -242,12 +244,14 @@
244 if( fTxt ){
245 style_submenu_element("Formatted", "%R/wiki_rules");
246 }else{
247 style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
248 }
249 style_submenu_element("Markdown","%R/md_rules");
250 blob_init(&x, builtin_text("wiki.wiki"), -1);
251 blob_materialize(&x);
252 interwiki_append_map_table(&x);
253 safe_html_context(DOCSRC_TRUSTED);
254 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
255 blob_reset(&x);
256 style_footer();
257 }
@@ -978,11 +982,12 @@
982 Stmt q = empty_Stmt;
983 int n = 0;
984 db_begin_transaction();
985 db_prepare(&q, "SELECT"
986 " substr(tagname,6) AS name"
987 " FROM tag JOIN tagxref USING('tagid')"
988 " WHERE tagname GLOB 'wiki-*'"
989 " UNION SELECT 'Sandbox' AS name"
990 " ORDER BY name COLLATE NOCASE");
991 CX("[");
992 while( SQLITE_ROW==db_step(&q) ){
993 char const * zName = db_column_text(&q,0);
994
+6 -1
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220220
if( fTxt ){
221221
style_submenu_element("Formatted", "%R/md_rules");
222222
}else{
223223
style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224224
}
225
+ style_submenu_element("Wiki", "%R/wiki_rules");
225226
blob_init(&x, builtin_text("markdown.md"), -1);
226227
blob_materialize(&x);
228
+ interwiki_append_map_table(&x);
227229
safe_html_context(DOCSRC_TRUSTED);
228230
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
229231
blob_reset(&x);
230232
style_footer();
231233
}
@@ -242,12 +244,14 @@
242244
if( fTxt ){
243245
style_submenu_element("Formatted", "%R/wiki_rules");
244246
}else{
245247
style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
246248
}
249
+ style_submenu_element("Markdown","%R/md_rules");
247250
blob_init(&x, builtin_text("wiki.wiki"), -1);
248251
blob_materialize(&x);
252
+ interwiki_append_map_table(&x);
249253
safe_html_context(DOCSRC_TRUSTED);
250254
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
251255
blob_reset(&x);
252256
style_footer();
253257
}
@@ -978,11 +982,12 @@
978982
Stmt q = empty_Stmt;
979983
int n = 0;
980984
db_begin_transaction();
981985
db_prepare(&q, "SELECT"
982986
" substr(tagname,6) AS name"
983
- " FROM tag WHERE tagname GLOB 'wiki-*'"
987
+ " FROM tag JOIN tagxref USING('tagid')"
988
+ " WHERE tagname GLOB 'wiki-*'"
984989
" UNION SELECT 'Sandbox' AS name"
985990
" ORDER BY name COLLATE NOCASE");
986991
CX("[");
987992
while( SQLITE_ROW==db_step(&q) ){
988993
char const * zName = db_column_text(&q,0);
989994
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220 if( fTxt ){
221 style_submenu_element("Formatted", "%R/md_rules");
222 }else{
223 style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224 }
 
225 blob_init(&x, builtin_text("markdown.md"), -1);
226 blob_materialize(&x);
 
227 safe_html_context(DOCSRC_TRUSTED);
228 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
229 blob_reset(&x);
230 style_footer();
231 }
@@ -242,12 +244,14 @@
242 if( fTxt ){
243 style_submenu_element("Formatted", "%R/wiki_rules");
244 }else{
245 style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
246 }
 
247 blob_init(&x, builtin_text("wiki.wiki"), -1);
248 blob_materialize(&x);
 
249 safe_html_context(DOCSRC_TRUSTED);
250 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
251 blob_reset(&x);
252 style_footer();
253 }
@@ -978,11 +982,12 @@
978 Stmt q = empty_Stmt;
979 int n = 0;
980 db_begin_transaction();
981 db_prepare(&q, "SELECT"
982 " substr(tagname,6) AS name"
983 " FROM tag WHERE tagname GLOB 'wiki-*'"
 
984 " UNION SELECT 'Sandbox' AS name"
985 " ORDER BY name COLLATE NOCASE");
986 CX("[");
987 while( SQLITE_ROW==db_step(&q) ){
988 char const * zName = db_column_text(&q,0);
989
--- src/wiki.c
+++ src/wiki.c
@@ -220,12 +220,14 @@
220 if( fTxt ){
221 style_submenu_element("Formatted", "%R/md_rules");
222 }else{
223 style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
224 }
225 style_submenu_element("Wiki", "%R/wiki_rules");
226 blob_init(&x, builtin_text("markdown.md"), -1);
227 blob_materialize(&x);
228 interwiki_append_map_table(&x);
229 safe_html_context(DOCSRC_TRUSTED);
230 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
231 blob_reset(&x);
232 style_footer();
233 }
@@ -242,12 +244,14 @@
244 if( fTxt ){
245 style_submenu_element("Formatted", "%R/wiki_rules");
246 }else{
247 style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
248 }
249 style_submenu_element("Markdown","%R/md_rules");
250 blob_init(&x, builtin_text("wiki.wiki"), -1);
251 blob_materialize(&x);
252 interwiki_append_map_table(&x);
253 safe_html_context(DOCSRC_TRUSTED);
254 wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
255 blob_reset(&x);
256 style_footer();
257 }
@@ -978,11 +982,12 @@
982 Stmt q = empty_Stmt;
983 int n = 0;
984 db_begin_transaction();
985 db_prepare(&q, "SELECT"
986 " substr(tagname,6) AS name"
987 " FROM tag JOIN tagxref USING('tagid')"
988 " WHERE tagname GLOB 'wiki-*'"
989 " UNION SELECT 'Sandbox' AS name"
990 " ORDER BY name COLLATE NOCASE");
991 CX("[");
992 while( SQLITE_ROW==db_step(&q) ){
993 char const * zName = db_column_text(&q,0);
994
+11 -3
--- src/wiki.wiki
+++ src/wiki.wiki
@@ -4,11 +4,12 @@
44
# Bullets are "*" surrounded by two spaces at the beginning of a line
55
# Enumeration items are "#" or a digit and a "." surrounded by two
66
spaces at the beginning of a line
77
# Indented paragraphs begin with a tab or two spaces
88
# Hyperlinks are contained within square brackets:
9
- <nowiki>"[target]" or "[target|label]"</nowiki>
9
+ <nowiki>"<b>[</b><i>target</i><b>]</b>"
10
+ or "<b>[</b><i>target</i><b>|</b><i>label</i><b>]</b>"</nowiki>
1011
# Most ordinary HTML works
1112
# &lt;verbatim&gt; and &lt;nowiki&gt;
1213
1314
We call the first five rules above the "wiki" formatting rules.
1415
The last two rules are the HTML formatting rules.
@@ -42,11 +43,13 @@
4243
Use HTML for deeper indentation.
4344
4445
5. <b>Hyperlinks.</b>
4546
Text within square brackets <nowiki>("[...]")</nowiki> becomes a
4647
hyperlink. The target can be a wiki page name, the artifact ID of
47
- a check-in or ticket, the name of an image, or a URL.
48
+ a check-in or ticket, the name of an image, a URL, or an
49
+ [#intermap|interwiki link] of the form
50
+ "<i>Tag</i><b>:</b><i>PageName</i>".
4851
By default, the target is displayed as the text of the hyperlink.
4952
But you can specify alternative text after the target name
5053
separated by a "|" character.
5154
You can also link to internal anchor names using
5255
<nowiki>[#anchor-name],</nowiki> providing you have added the necessary
@@ -54,12 +57,14 @@
5457
5558
6. <b>HTML.</b>
5659
The following standard HTML elements may be used:
5760
&lt;a&gt; &lt;address&gt; &lt;article&gt; &lt;aside&gt; &lt;b&gt;
5861
&lt;big&gt; &lt;blockquote&gt; &lt;br&gt; &lt;center&gt; &lt;cite&gt;
59
- &lt;code&gt; &lt;col&gt; &lt;colgroup&gt; &lt;dd&gt; &lt;dfn&gt;
62
+ &lt;code&gt; &lt;col&gt; &lt;colgroup&gt; &lt;dd&gt;
63
+ &lt;del&gt; &lt;dfn&gt;
6064
&lt;div&gt; &lt;dl&gt; &lt;dt&gt; &lt;em&gt; &lt;font&gt; &lt;footer&gt;
65
+ &lt;ins&gt;
6166
&lt;h1&gt; &lt;h2&gt; &lt;h3&gt; &lt;h4&gt; &lt;h5&gt; &lt;h6&gt;
6267
&lt;header&gt; &lt;hr&gt; &lt;i&gt; &lt;img&gt; &lt;kbd&gt; &lt;li&gt;
6368
&lt;nav&gt; &lt;nobr&gt; &lt;nowiki&gt; &lt;ol&gt; &lt;p&gt; &lt;pre&gt;
6469
&lt;s&gt; &lt;samp&gt; &lt;section&gt; &lt;small&gt; &lt;span&gt;
6570
&lt;strike&gt; &lt;strong&gt; &lt;sub&gt; &lt;sup&gt; &lt;table&gt;
@@ -74,5 +79,8 @@
7479
7. <b>Special Markup.</b>
7580
The &lt;nowiki&gt; tag disables all wiki formatting rules through
7681
the matching &lt;/nowiki&gt; element. The &lt;verbatim&gt; tag works
7782
like &lt;pre&gt; with the addition that it also disables all wiki
7883
and HTML markup through the matching &lt;/verbatim&gt;.
84
+
85
+<a name="intermap"></a>
86
+<h2>Interwiki Tag Map</h2>
7987
--- src/wiki.wiki
+++ src/wiki.wiki
@@ -4,11 +4,12 @@
4 # Bullets are "*" surrounded by two spaces at the beginning of a line
5 # Enumeration items are "#" or a digit and a "." surrounded by two
6 spaces at the beginning of a line
7 # Indented paragraphs begin with a tab or two spaces
8 # Hyperlinks are contained within square brackets:
9 <nowiki>"[target]" or "[target|label]"</nowiki>
 
10 # Most ordinary HTML works
11 # &lt;verbatim&gt; and &lt;nowiki&gt;
12
13 We call the first five rules above the "wiki" formatting rules.
14 The last two rules are the HTML formatting rules.
@@ -42,11 +43,13 @@
42 Use HTML for deeper indentation.
43
44 5. <b>Hyperlinks.</b>
45 Text within square brackets <nowiki>("[...]")</nowiki> becomes a
46 hyperlink. The target can be a wiki page name, the artifact ID of
47 a check-in or ticket, the name of an image, or a URL.
 
 
48 By default, the target is displayed as the text of the hyperlink.
49 But you can specify alternative text after the target name
50 separated by a "|" character.
51 You can also link to internal anchor names using
52 <nowiki>[#anchor-name],</nowiki> providing you have added the necessary
@@ -54,12 +57,14 @@
54
55 6. <b>HTML.</b>
56 The following standard HTML elements may be used:
57 &lt;a&gt; &lt;address&gt; &lt;article&gt; &lt;aside&gt; &lt;b&gt;
58 &lt;big&gt; &lt;blockquote&gt; &lt;br&gt; &lt;center&gt; &lt;cite&gt;
59 &lt;code&gt; &lt;col&gt; &lt;colgroup&gt; &lt;dd&gt; &lt;dfn&gt;
 
60 &lt;div&gt; &lt;dl&gt; &lt;dt&gt; &lt;em&gt; &lt;font&gt; &lt;footer&gt;
 
61 &lt;h1&gt; &lt;h2&gt; &lt;h3&gt; &lt;h4&gt; &lt;h5&gt; &lt;h6&gt;
62 &lt;header&gt; &lt;hr&gt; &lt;i&gt; &lt;img&gt; &lt;kbd&gt; &lt;li&gt;
63 &lt;nav&gt; &lt;nobr&gt; &lt;nowiki&gt; &lt;ol&gt; &lt;p&gt; &lt;pre&gt;
64 &lt;s&gt; &lt;samp&gt; &lt;section&gt; &lt;small&gt; &lt;span&gt;
65 &lt;strike&gt; &lt;strong&gt; &lt;sub&gt; &lt;sup&gt; &lt;table&gt;
@@ -74,5 +79,8 @@
74 7. <b>Special Markup.</b>
75 The &lt;nowiki&gt; tag disables all wiki formatting rules through
76 the matching &lt;/nowiki&gt; element. The &lt;verbatim&gt; tag works
77 like &lt;pre&gt; with the addition that it also disables all wiki
78 and HTML markup through the matching &lt;/verbatim&gt;.
 
 
 
79
--- src/wiki.wiki
+++ src/wiki.wiki
@@ -4,11 +4,12 @@
4 # Bullets are "*" surrounded by two spaces at the beginning of a line
5 # Enumeration items are "#" or a digit and a "." surrounded by two
6 spaces at the beginning of a line
7 # Indented paragraphs begin with a tab or two spaces
8 # Hyperlinks are contained within square brackets:
9 <nowiki>"<b>[</b><i>target</i><b>]</b>"
10 or "<b>[</b><i>target</i><b>|</b><i>label</i><b>]</b>"</nowiki>
11 # Most ordinary HTML works
12 # &lt;verbatim&gt; and &lt;nowiki&gt;
13
14 We call the first five rules above the "wiki" formatting rules.
15 The last two rules are the HTML formatting rules.
@@ -42,11 +43,13 @@
43 Use HTML for deeper indentation.
44
45 5. <b>Hyperlinks.</b>
46 Text within square brackets <nowiki>("[...]")</nowiki> becomes a
47 hyperlink. The target can be a wiki page name, the artifact ID of
48 a check-in or ticket, the name of an image, a URL, or an
49 [#intermap|interwiki link] of the form
50 "<i>Tag</i><b>:</b><i>PageName</i>".
51 By default, the target is displayed as the text of the hyperlink.
52 But you can specify alternative text after the target name
53 separated by a "|" character.
54 You can also link to internal anchor names using
55 <nowiki>[#anchor-name],</nowiki> providing you have added the necessary
@@ -54,12 +57,14 @@
57
58 6. <b>HTML.</b>
59 The following standard HTML elements may be used:
60 &lt;a&gt; &lt;address&gt; &lt;article&gt; &lt;aside&gt; &lt;b&gt;
61 &lt;big&gt; &lt;blockquote&gt; &lt;br&gt; &lt;center&gt; &lt;cite&gt;
62 &lt;code&gt; &lt;col&gt; &lt;colgroup&gt; &lt;dd&gt;
63 &lt;del&gt; &lt;dfn&gt;
64 &lt;div&gt; &lt;dl&gt; &lt;dt&gt; &lt;em&gt; &lt;font&gt; &lt;footer&gt;
65 &lt;ins&gt;
66 &lt;h1&gt; &lt;h2&gt; &lt;h3&gt; &lt;h4&gt; &lt;h5&gt; &lt;h6&gt;
67 &lt;header&gt; &lt;hr&gt; &lt;i&gt; &lt;img&gt; &lt;kbd&gt; &lt;li&gt;
68 &lt;nav&gt; &lt;nobr&gt; &lt;nowiki&gt; &lt;ol&gt; &lt;p&gt; &lt;pre&gt;
69 &lt;s&gt; &lt;samp&gt; &lt;section&gt; &lt;small&gt; &lt;span&gt;
70 &lt;strike&gt; &lt;strong&gt; &lt;sub&gt; &lt;sup&gt; &lt;table&gt;
@@ -74,5 +79,8 @@
79 7. <b>Special Markup.</b>
80 The &lt;nowiki&gt; tag disables all wiki formatting rules through
81 the matching &lt;/nowiki&gt; element. The &lt;verbatim&gt; tag works
82 like &lt;pre&gt; with the addition that it also disables all wiki
83 and HTML markup through the matching &lt;/verbatim&gt;.
84
85 <a name="intermap"></a>
86 <h2>Interwiki Tag Map</h2>
87
+58 -48
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -188,57 +188,59 @@
188188
#define MARKUP_CITE 10
189189
#define MARKUP_CODE 11
190190
#define MARKUP_COL 12
191191
#define MARKUP_COLGROUP 13
192192
#define MARKUP_DD 14
193
-#define MARKUP_DFN 15
194
-#define MARKUP_DIV 16
195
-#define MARKUP_DL 17
196
-#define MARKUP_DT 18
197
-#define MARKUP_EM 19
198
-#define MARKUP_FONT 20
199
-#define MARKUP_HTML5_FOOTER 21
200
-#define MARKUP_H1 22
201
-#define MARKUP_H2 23
202
-#define MARKUP_H3 24
203
-#define MARKUP_H4 25
204
-#define MARKUP_H5 26
205
-#define MARKUP_H6 27
206
-#define MARKUP_HTML5_HEADER 28
207
-#define MARKUP_HR 29
208
-#define MARKUP_I 30
209
-#define MARKUP_IMG 31
210
-#define MARKUP_KBD 32
211
-#define MARKUP_LI 33
212
-#define MARKUP_HTML5_NAV 34
213
-#define MARKUP_NOBR 35
214
-#define MARKUP_NOWIKI 36
215
-#define MARKUP_OL 37
216
-#define MARKUP_P 38
217
-#define MARKUP_PRE 39
218
-#define MARKUP_S 40
219
-#define MARKUP_SAMP 41
220
-#define MARKUP_HTML5_SECTION 42
221
-#define MARKUP_SMALL 43
222
-#define MARKUP_SPAN 44
223
-#define MARKUP_STRIKE 45
224
-#define MARKUP_STRONG 46
225
-#define MARKUP_SUB 47
226
-#define MARKUP_SUP 48
227
-#define MARKUP_TABLE 49
228
-#define MARKUP_TBODY 50
229
-#define MARKUP_TD 51
230
-#define MARKUP_TFOOT 52
231
-#define MARKUP_TH 53
232
-#define MARKUP_THEAD 54
233
-#define MARKUP_TITLE 55
234
-#define MARKUP_TR 56
235
-#define MARKUP_TT 57
236
-#define MARKUP_U 58
237
-#define MARKUP_UL 59
238
-#define MARKUP_VAR 60
239
-#define MARKUP_VERBATIM 61
193
+#define MARKUP_DEL 15
194
+#define MARKUP_DFN 16
195
+#define MARKUP_DIV 17
196
+#define MARKUP_DL 18
197
+#define MARKUP_DT 19
198
+#define MARKUP_EM 20
199
+#define MARKUP_FONT 21
200
+#define MARKUP_HTML5_FOOTER 22
201
+#define MARKUP_H1 23
202
+#define MARKUP_H2 24
203
+#define MARKUP_H3 25
204
+#define MARKUP_H4 26
205
+#define MARKUP_H5 27
206
+#define MARKUP_H6 28
207
+#define MARKUP_HTML5_HEADER 29
208
+#define MARKUP_HR 30
209
+#define MARKUP_I 31
210
+#define MARKUP_IMG 32
211
+#define MARKUP_INS 33
212
+#define MARKUP_KBD 34
213
+#define MARKUP_LI 35
214
+#define MARKUP_HTML5_NAV 36
215
+#define MARKUP_NOBR 37
216
+#define MARKUP_NOWIKI 38
217
+#define MARKUP_OL 39
218
+#define MARKUP_P 40
219
+#define MARKUP_PRE 41
220
+#define MARKUP_S 42
221
+#define MARKUP_SAMP 43
222
+#define MARKUP_HTML5_SECTION 44
223
+#define MARKUP_SMALL 45
224
+#define MARKUP_SPAN 46
225
+#define MARKUP_STRIKE 47
226
+#define MARKUP_STRONG 48
227
+#define MARKUP_SUB 49
228
+#define MARKUP_SUP 50
229
+#define MARKUP_TABLE 51
230
+#define MARKUP_TBODY 52
231
+#define MARKUP_TD 53
232
+#define MARKUP_TFOOT 54
233
+#define MARKUP_TH 55
234
+#define MARKUP_THEAD 56
235
+#define MARKUP_TITLE 57
236
+#define MARKUP_TR 58
237
+#define MARKUP_TT 59
238
+#define MARKUP_U 60
239
+#define MARKUP_UL 61
240
+#define MARKUP_VAR 62
241
+#define MARKUP_VERBATIM 63
240242
241243
/*
242244
** The various markup is divided into the following types:
243245
*/
244246
#define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */
@@ -290,10 +292,11 @@
290292
{ "col", MARKUP_COL, MUTYPE_SINGLE,
291293
AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE },
292294
{ "colgroup", MARKUP_COLGROUP, MUTYPE_BLOCK,
293295
AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE},
294296
{ "dd", MARKUP_DD, MUTYPE_LI, AMSK_STYLE },
297
+ { "del", MARKUP_DEL, MUTYPE_FONT, AMSK_STYLE },
295298
{ "dfn", MARKUP_DFN, MUTYPE_FONT, AMSK_STYLE },
296299
{ "div", MARKUP_DIV, MUTYPE_BLOCK,
297300
AMSK_ID|AMSK_CLASS|AMSK_STYLE },
298301
{ "dl", MARKUP_DL, MUTYPE_LIST,
299302
AMSK_COMPACT|AMSK_STYLE },
@@ -325,10 +328,11 @@
325328
AMSK_STYLE|AMSK_CLASS },
326329
{ "i", MARKUP_I, MUTYPE_FONT, AMSK_STYLE },
327330
{ "img", MARKUP_IMG, MUTYPE_SINGLE,
328331
AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
329332
AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE },
333
+ { "ins", MARKUP_INS, MUTYPE_FONT, AMSK_STYLE },
330334
{ "kbd", MARKUP_KBD, MUTYPE_FONT, AMSK_STYLE },
331335
{ "li", MARKUP_LI, MUTYPE_LI,
332336
AMSK_TYPE|AMSK_VALUE|AMSK_STYLE },
333337
{ "nav", MARKUP_HTML5_NAV, MUTYPE_BLOCK,
334338
AMSK_ID|AMSK_CLASS|AMSK_STYLE },
@@ -1226,10 +1230,12 @@
12261230
**
12271231
** [WikiPageName]
12281232
** [wiki:WikiPageName]
12291233
**
12301234
** [2010-02-27 07:13]
1235
+**
1236
+** [InterMap:Link] -> Interwiki link
12311237
*/
12321238
void wiki_resolve_hyperlink(
12331239
Blob *pOut, /* Write the HTML output here */
12341240
int mFlags, /* Rendering option flags */
12351241
const char *zTarget, /* Hyperlink target; text within [...] */
@@ -1240,10 +1246,11 @@
12401246
){
12411247
const char *zTerm = "</a>";
12421248
const char *z;
12431249
char *zExtra = 0;
12441250
const char *zExtraNS = 0;
1251
+ char *zRemote = 0;
12451252
12461253
if( zTitle ){
12471254
zExtra = mprintf(" title='%h'", zTitle);
12481255
zExtraNS = zExtra+1;
12491256
}
@@ -1299,10 +1306,13 @@
12991306
blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
13001307
zTerm = "]</a>";
13011308
}else{
13021309
zTerm = "";
13031310
}
1311
+ }else if( (zRemote = interwiki_url(zTarget))!=0 ){
1312
+ blob_appendf(pOut, "<a href=\"%z\"%s>", zRemote, zExtra);
1313
+ zTerm = "</a>";
13041314
}else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
13051315
/* The link is to a valid wiki page name */
13061316
const char *zOverride = wiki_is_overridden(zTarget);
13071317
if( zOverride ){
13081318
blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
@@ -1510,11 +1520,11 @@
15101520
z[j] = 0;
15111521
}
15121522
}
15131523
z[i] = 0;
15141524
if( zDisplay==0 ){
1515
- zDisplay = zTarget;
1525
+ zDisplay = zTarget + interwiki_removable_prefix(zTarget);
15161526
}else{
15171527
while( fossil_isspace(*zDisplay) ) zDisplay++;
15181528
}
15191529
wiki_resolve_hyperlink(p->pOut, p->state,
15201530
zTarget, zClose, sizeof(zClose), zOrig, 0);
15211531
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -188,57 +188,59 @@
188 #define MARKUP_CITE 10
189 #define MARKUP_CODE 11
190 #define MARKUP_COL 12
191 #define MARKUP_COLGROUP 13
192 #define MARKUP_DD 14
193 #define MARKUP_DFN 15
194 #define MARKUP_DIV 16
195 #define MARKUP_DL 17
196 #define MARKUP_DT 18
197 #define MARKUP_EM 19
198 #define MARKUP_FONT 20
199 #define MARKUP_HTML5_FOOTER 21
200 #define MARKUP_H1 22
201 #define MARKUP_H2 23
202 #define MARKUP_H3 24
203 #define MARKUP_H4 25
204 #define MARKUP_H5 26
205 #define MARKUP_H6 27
206 #define MARKUP_HTML5_HEADER 28
207 #define MARKUP_HR 29
208 #define MARKUP_I 30
209 #define MARKUP_IMG 31
210 #define MARKUP_KBD 32
211 #define MARKUP_LI 33
212 #define MARKUP_HTML5_NAV 34
213 #define MARKUP_NOBR 35
214 #define MARKUP_NOWIKI 36
215 #define MARKUP_OL 37
216 #define MARKUP_P 38
217 #define MARKUP_PRE 39
218 #define MARKUP_S 40
219 #define MARKUP_SAMP 41
220 #define MARKUP_HTML5_SECTION 42
221 #define MARKUP_SMALL 43
222 #define MARKUP_SPAN 44
223 #define MARKUP_STRIKE 45
224 #define MARKUP_STRONG 46
225 #define MARKUP_SUB 47
226 #define MARKUP_SUP 48
227 #define MARKUP_TABLE 49
228 #define MARKUP_TBODY 50
229 #define MARKUP_TD 51
230 #define MARKUP_TFOOT 52
231 #define MARKUP_TH 53
232 #define MARKUP_THEAD 54
233 #define MARKUP_TITLE 55
234 #define MARKUP_TR 56
235 #define MARKUP_TT 57
236 #define MARKUP_U 58
237 #define MARKUP_UL 59
238 #define MARKUP_VAR 60
239 #define MARKUP_VERBATIM 61
 
 
240
241 /*
242 ** The various markup is divided into the following types:
243 */
244 #define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */
@@ -290,10 +292,11 @@
290 { "col", MARKUP_COL, MUTYPE_SINGLE,
291 AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE },
292 { "colgroup", MARKUP_COLGROUP, MUTYPE_BLOCK,
293 AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE},
294 { "dd", MARKUP_DD, MUTYPE_LI, AMSK_STYLE },
 
295 { "dfn", MARKUP_DFN, MUTYPE_FONT, AMSK_STYLE },
296 { "div", MARKUP_DIV, MUTYPE_BLOCK,
297 AMSK_ID|AMSK_CLASS|AMSK_STYLE },
298 { "dl", MARKUP_DL, MUTYPE_LIST,
299 AMSK_COMPACT|AMSK_STYLE },
@@ -325,10 +328,11 @@
325 AMSK_STYLE|AMSK_CLASS },
326 { "i", MARKUP_I, MUTYPE_FONT, AMSK_STYLE },
327 { "img", MARKUP_IMG, MUTYPE_SINGLE,
328 AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
329 AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE },
 
330 { "kbd", MARKUP_KBD, MUTYPE_FONT, AMSK_STYLE },
331 { "li", MARKUP_LI, MUTYPE_LI,
332 AMSK_TYPE|AMSK_VALUE|AMSK_STYLE },
333 { "nav", MARKUP_HTML5_NAV, MUTYPE_BLOCK,
334 AMSK_ID|AMSK_CLASS|AMSK_STYLE },
@@ -1226,10 +1230,12 @@
1226 **
1227 ** [WikiPageName]
1228 ** [wiki:WikiPageName]
1229 **
1230 ** [2010-02-27 07:13]
 
 
1231 */
1232 void wiki_resolve_hyperlink(
1233 Blob *pOut, /* Write the HTML output here */
1234 int mFlags, /* Rendering option flags */
1235 const char *zTarget, /* Hyperlink target; text within [...] */
@@ -1240,10 +1246,11 @@
1240 ){
1241 const char *zTerm = "</a>";
1242 const char *z;
1243 char *zExtra = 0;
1244 const char *zExtraNS = 0;
 
1245
1246 if( zTitle ){
1247 zExtra = mprintf(" title='%h'", zTitle);
1248 zExtraNS = zExtra+1;
1249 }
@@ -1299,10 +1306,13 @@
1299 blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
1300 zTerm = "]</a>";
1301 }else{
1302 zTerm = "";
1303 }
 
 
 
1304 }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
1305 /* The link is to a valid wiki page name */
1306 const char *zOverride = wiki_is_overridden(zTarget);
1307 if( zOverride ){
1308 blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
@@ -1510,11 +1520,11 @@
1510 z[j] = 0;
1511 }
1512 }
1513 z[i] = 0;
1514 if( zDisplay==0 ){
1515 zDisplay = zTarget;
1516 }else{
1517 while( fossil_isspace(*zDisplay) ) zDisplay++;
1518 }
1519 wiki_resolve_hyperlink(p->pOut, p->state,
1520 zTarget, zClose, sizeof(zClose), zOrig, 0);
1521
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -188,57 +188,59 @@
188 #define MARKUP_CITE 10
189 #define MARKUP_CODE 11
190 #define MARKUP_COL 12
191 #define MARKUP_COLGROUP 13
192 #define MARKUP_DD 14
193 #define MARKUP_DEL 15
194 #define MARKUP_DFN 16
195 #define MARKUP_DIV 17
196 #define MARKUP_DL 18
197 #define MARKUP_DT 19
198 #define MARKUP_EM 20
199 #define MARKUP_FONT 21
200 #define MARKUP_HTML5_FOOTER 22
201 #define MARKUP_H1 23
202 #define MARKUP_H2 24
203 #define MARKUP_H3 25
204 #define MARKUP_H4 26
205 #define MARKUP_H5 27
206 #define MARKUP_H6 28
207 #define MARKUP_HTML5_HEADER 29
208 #define MARKUP_HR 30
209 #define MARKUP_I 31
210 #define MARKUP_IMG 32
211 #define MARKUP_INS 33
212 #define MARKUP_KBD 34
213 #define MARKUP_LI 35
214 #define MARKUP_HTML5_NAV 36
215 #define MARKUP_NOBR 37
216 #define MARKUP_NOWIKI 38
217 #define MARKUP_OL 39
218 #define MARKUP_P 40
219 #define MARKUP_PRE 41
220 #define MARKUP_S 42
221 #define MARKUP_SAMP 43
222 #define MARKUP_HTML5_SECTION 44
223 #define MARKUP_SMALL 45
224 #define MARKUP_SPAN 46
225 #define MARKUP_STRIKE 47
226 #define MARKUP_STRONG 48
227 #define MARKUP_SUB 49
228 #define MARKUP_SUP 50
229 #define MARKUP_TABLE 51
230 #define MARKUP_TBODY 52
231 #define MARKUP_TD 53
232 #define MARKUP_TFOOT 54
233 #define MARKUP_TH 55
234 #define MARKUP_THEAD 56
235 #define MARKUP_TITLE 57
236 #define MARKUP_TR 58
237 #define MARKUP_TT 59
238 #define MARKUP_U 60
239 #define MARKUP_UL 61
240 #define MARKUP_VAR 62
241 #define MARKUP_VERBATIM 63
242
243 /*
244 ** The various markup is divided into the following types:
245 */
246 #define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */
@@ -290,10 +292,11 @@
292 { "col", MARKUP_COL, MUTYPE_SINGLE,
293 AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE },
294 { "colgroup", MARKUP_COLGROUP, MUTYPE_BLOCK,
295 AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE},
296 { "dd", MARKUP_DD, MUTYPE_LI, AMSK_STYLE },
297 { "del", MARKUP_DEL, MUTYPE_FONT, AMSK_STYLE },
298 { "dfn", MARKUP_DFN, MUTYPE_FONT, AMSK_STYLE },
299 { "div", MARKUP_DIV, MUTYPE_BLOCK,
300 AMSK_ID|AMSK_CLASS|AMSK_STYLE },
301 { "dl", MARKUP_DL, MUTYPE_LIST,
302 AMSK_COMPACT|AMSK_STYLE },
@@ -325,10 +328,11 @@
328 AMSK_STYLE|AMSK_CLASS },
329 { "i", MARKUP_I, MUTYPE_FONT, AMSK_STYLE },
330 { "img", MARKUP_IMG, MUTYPE_SINGLE,
331 AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
332 AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE },
333 { "ins", MARKUP_INS, MUTYPE_FONT, AMSK_STYLE },
334 { "kbd", MARKUP_KBD, MUTYPE_FONT, AMSK_STYLE },
335 { "li", MARKUP_LI, MUTYPE_LI,
336 AMSK_TYPE|AMSK_VALUE|AMSK_STYLE },
337 { "nav", MARKUP_HTML5_NAV, MUTYPE_BLOCK,
338 AMSK_ID|AMSK_CLASS|AMSK_STYLE },
@@ -1226,10 +1230,12 @@
1230 **
1231 ** [WikiPageName]
1232 ** [wiki:WikiPageName]
1233 **
1234 ** [2010-02-27 07:13]
1235 **
1236 ** [InterMap:Link] -> Interwiki link
1237 */
1238 void wiki_resolve_hyperlink(
1239 Blob *pOut, /* Write the HTML output here */
1240 int mFlags, /* Rendering option flags */
1241 const char *zTarget, /* Hyperlink target; text within [...] */
@@ -1240,10 +1246,11 @@
1246 ){
1247 const char *zTerm = "</a>";
1248 const char *z;
1249 char *zExtra = 0;
1250 const char *zExtraNS = 0;
1251 char *zRemote = 0;
1252
1253 if( zTitle ){
1254 zExtra = mprintf(" title='%h'", zTitle);
1255 zExtraNS = zExtra+1;
1256 }
@@ -1299,10 +1306,13 @@
1306 blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
1307 zTerm = "]</a>";
1308 }else{
1309 zTerm = "";
1310 }
1311 }else if( (zRemote = interwiki_url(zTarget))!=0 ){
1312 blob_appendf(pOut, "<a href=\"%z\"%s>", zRemote, zExtra);
1313 zTerm = "</a>";
1314 }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
1315 /* The link is to a valid wiki page name */
1316 const char *zOverride = wiki_is_overridden(zTarget);
1317 if( zOverride ){
1318 blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
@@ -1510,11 +1520,11 @@
1520 z[j] = 0;
1521 }
1522 }
1523 z[i] = 0;
1524 if( zDisplay==0 ){
1525 zDisplay = zTarget + interwiki_removable_prefix(zTarget);
1526 }else{
1527 while( fossil_isspace(*zDisplay) ) zDisplay++;
1528 }
1529 wiki_resolve_hyperlink(p->pOut, p->state,
1530 zTarget, zClose, sizeof(zClose), zOrig, 0);
1531
+6
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
16571657
int x = db_column_int(&q,3);
16581658
const char *zName = db_column_text(&q,4);
16591659
if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
16601660
/* check-in locks expire after maxAge seconds, or when the
16611661
** check-in is no longer a leaf */
1662
+ db_unprotect(PROTECT_CONFIG);
16621663
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1664
+ db_protect_pop();
16631665
continue;
16641666
}
16651667
if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
16661668
const char *zClientId = db_column_text(&q, 2);
16671669
const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
16721674
seenFault = 1;
16731675
}
16741676
}
16751677
db_finalize(&q);
16761678
if( !seenFault ){
1679
+ db_unprotect(PROTECT_CONFIG);
16771680
db_multi_exec(
16781681
"REPLACE INTO config(name,value,mtime)"
16791682
"VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
16801683
blob_str(&xfer.aToken[2]), g.zLogin,
16811684
blob_str(&xfer.aToken[3])
16821685
);
1686
+ db_protect_pop();
16831687
}
16841688
if( db_get_boolean("forbid-delta-manifests",0) ){
16851689
@ pragma avoid-delta-manifests
16861690
}
16871691
}
@@ -1694,16 +1698,18 @@
16941698
*/
16951699
if( blob_eq(&xfer.aToken[1], "ci-unlock")
16961700
&& xfer.nToken==3
16971701
&& blob_is_hname(&xfer.aToken[2])
16981702
){
1703
+ db_unprotect(PROTECT_CONFIG);
16991704
db_multi_exec(
17001705
"DELETE FROM config"
17011706
" WHERE name GLOB 'ci-lock-*'"
17021707
" AND json_extract(value,'$.clientid')=%Q",
17031708
blob_str(&xfer.aToken[2])
17041709
);
1710
+ db_protect_pop();
17051711
}
17061712
17071713
}else
17081714
17091715
/* Unknown message
17101716
17111717
ADDED test/reserved-names.test
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
1657 int x = db_column_int(&q,3);
1658 const char *zName = db_column_text(&q,4);
1659 if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
1660 /* check-in locks expire after maxAge seconds, or when the
1661 ** check-in is no longer a leaf */
 
1662 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
1663 continue;
1664 }
1665 if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
1666 const char *zClientId = db_column_text(&q, 2);
1667 const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
1672 seenFault = 1;
1673 }
1674 }
1675 db_finalize(&q);
1676 if( !seenFault ){
 
1677 db_multi_exec(
1678 "REPLACE INTO config(name,value,mtime)"
1679 "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
1680 blob_str(&xfer.aToken[2]), g.zLogin,
1681 blob_str(&xfer.aToken[3])
1682 );
 
1683 }
1684 if( db_get_boolean("forbid-delta-manifests",0) ){
1685 @ pragma avoid-delta-manifests
1686 }
1687 }
@@ -1694,16 +1698,18 @@
1694 */
1695 if( blob_eq(&xfer.aToken[1], "ci-unlock")
1696 && xfer.nToken==3
1697 && blob_is_hname(&xfer.aToken[2])
1698 ){
 
1699 db_multi_exec(
1700 "DELETE FROM config"
1701 " WHERE name GLOB 'ci-lock-*'"
1702 " AND json_extract(value,'$.clientid')=%Q",
1703 blob_str(&xfer.aToken[2])
1704 );
 
1705 }
1706
1707 }else
1708
1709 /* Unknown message
1710
1711 DDED test/reserved-names.test
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
1657 int x = db_column_int(&q,3);
1658 const char *zName = db_column_text(&q,4);
1659 if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
1660 /* check-in locks expire after maxAge seconds, or when the
1661 ** check-in is no longer a leaf */
1662 db_unprotect(PROTECT_CONFIG);
1663 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1664 db_protect_pop();
1665 continue;
1666 }
1667 if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
1668 const char *zClientId = db_column_text(&q, 2);
1669 const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
1674 seenFault = 1;
1675 }
1676 }
1677 db_finalize(&q);
1678 if( !seenFault ){
1679 db_unprotect(PROTECT_CONFIG);
1680 db_multi_exec(
1681 "REPLACE INTO config(name,value,mtime)"
1682 "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
1683 blob_str(&xfer.aToken[2]), g.zLogin,
1684 blob_str(&xfer.aToken[3])
1685 );
1686 db_protect_pop();
1687 }
1688 if( db_get_boolean("forbid-delta-manifests",0) ){
1689 @ pragma avoid-delta-manifests
1690 }
1691 }
@@ -1694,16 +1698,18 @@
1698 */
1699 if( blob_eq(&xfer.aToken[1], "ci-unlock")
1700 && xfer.nToken==3
1701 && blob_is_hname(&xfer.aToken[2])
1702 ){
1703 db_unprotect(PROTECT_CONFIG);
1704 db_multi_exec(
1705 "DELETE FROM config"
1706 " WHERE name GLOB 'ci-lock-*'"
1707 " AND json_extract(value,'$.clientid')=%Q",
1708 blob_str(&xfer.aToken[2])
1709 );
1710 db_protect_pop();
1711 }
1712
1713 }else
1714
1715 /* Unknown message
1716
1717 DDED test/reserved-names.test
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
1
+#
2
+# Copyright (c) 2020 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
+# Tests for reserved names.
19
+#
20
+
21
+test_setup
22
+
23
+###############################################################################
24
+
25
+set reserved_names_tests [list \
26
+ {0 {}} \
27
+ {0 a.fslckout} \
28
+ {1 .fslckout} \
29
+ {1 .FSlckOUT} \
30
+ {2 a/.fslckout} \
31
+ {0 .fslckout/b} \
32
+ {0 fslckout} \
33
+ {0 .fslckoutx} \
34
+ {1 _FOSSIL_} \
35
+ {0 _FOSSIL} \
36
+ {0 FOSSIL_} \
37
+ {0 FOSSIL_} \
38
+ {0 a_FOSSIL_} \
39
+ {0 _FOSSIL__} \
40
+ {0 __FOSSIL__} \
41
+ {0 __FOssIL__} \
42
+ {0 _FOSSIL_/a} \
43
+ {2 a/_FOSSIL_} \
44
+ {2 _FOSSIL_/c/.fslckout} \
45
+ {2 _FOSSIL_/c/.fslckout/_FOSSIL_} \
46
+ {0 _FOSSIL_/c/.fslckout/._FOSSIL_t} \
47
+ {0 _FOSSIL_/c/.fslckout/t._FOSSIL_} \
48
+ {0 a} \
49
+ {0 a/b} \
50
+ {0 a/b/c} \
51
+ {0 a/b/c/} \
52
+ {0 a/_FOSSIL/} \
53
+ {0 a/fslckout/} \
54
+ {0 a/_fslckout/} \
55
+ {0 _FOSSIL-wal} \
56
+ {0 _FOSSIL-shm} \
57
+ {0 _FOSSIL-journal} \
58
+ {0 _FOSSIL_-wal/a} \
59
+ {0 _FOSSIL_-shm/a} \
60
+ {0 _FOSSIL_-journal/a} \
61
+ {1 _FOSSIL_-wal} \
62
+ {1 _FOSSIL_-shm} \
63
+ {1 _FOSSIL_-journal} \
64
+ {2 a/_FOSSIL_-wal} \
65
+ {2 a/_FOSSIL_-shm} \
66
+ {2 a/_FOSSIL_-journal} \
67
+ {0 .fslckout-wal/a} \
68
+ {0 .fslckout-shm/a} \
69
+ {0 .fslckout-journal/a} \
70
+ {1 .fslckout-wal} \
71
+ {1 .fslckout-shm} \
72
+ {1 .fslckout-journal} \
73
+ {2 a/.fslckout-wal} \
74
+ {2 a/.fslckout-shm} \
75
+ {2 a/.fslckout-journal} \
76
+]
77
+
78
+###############################################################################
79
+
80
+set testNo 0
81
+
82
+foreach reserved_names_test $reserved_names_tests {
83
+ incr testNo
84
+
85
+ set reserved_result [lindex $reserved_names_test 0]
86
+ set reserved_name [lindex $reserved_names_test 1]
87
+
88
+ fossil test-is-reserved-name $reserved_name
89
+
90
+ test reserved-result-$testNo {
91
+ [lindex [normalize_result] 0] eq $reserved_result
92
+ }
93
+
94
+ test reserved-name-$testNo {
95
+ [lindex [normalize_result] 1] eq $reserved_name
96
+ }
97
+
98
+ fossil test-is-reserved-name [string toupper $reserved_name]
99
+
100
+ test reserved-result-upper-$testNo {
101
+ [lindex [normalize_result] 0] eq $reserved_result
102
+ }
103
+
104
+ test reserved-name-upper-$testNo {
105
+ [lindex [normalize_result] 1] eq [string toupper $reserved_name]
106
+ }
107
+
108
+ fossil test-is-reserved-name [string tolower $reserved_name]
109
+
110
+ test reserved-result-lower-$testNo {
111
+ [lindex [normalize_result] 0] eq $reserved_result
112
+ }
113
+
114
+ test reserved-name-lower-$testNo {
115
+ [lindex [normalize_result] 1] eq [string tolower $reserved_name]
116
+ }
117
+}
118
+
119
+###############################################################################
120
+
121
+test_cleanup
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
1 #
2 # Copyright (c) 2020 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 # Tests for reserved names.
19 #
20
21 test_setup
22
23 ###############################################################################
24
25 set reserved_names_tests [list \
26 {0 {}} \
27 {0 a.fslckout} \
28 {1 .fslckout} \
29 {1 .FSlckOUT} \
30 {2 a/.fslckout} \
31 {0 .fslckout/b} \
32 {0 fslckout} \
33 {0 .fslckoutx} \
34 {1 _FOSSIL_} \
35 {0 _FOSSIL} \
36 {0 FOSSIL_} \
37 {0 FOSSIL_} \
38 {0 a_FOSSIL_} \
39 {0 _FOSSIL__} \
40 {0 __FOSSIL__} \
41 {0 __FOssIL__} \
42 {0 _FOSSIL_/a} \
43 {2 a/_FOSSIL_} \
44 {2 _FOSSIL_/c/.fslckout} \
45 {2 _FOSSIL_/c/.fslckout/_FOSSIL_} \
46 {0 _FOSSIL_/c/.fslckout/._FOSSIL_t} \
47 {0 _FOSSIL_/c/.fslckout/t._FOSSIL_} \
48 {0 a} \
49 {0 a/b} \
50 {0 a/b/c} \
51 {0 a/b/c/} \
52 {0 a/_FOSSIL/} \
53 {0 a/fslckout/} \
54 {0 a/_fslckout/} \
55 {0 _FOSSIL-wal} \
56 {0 _FOSSIL-shm} \
57 {0 _FOSSIL-journal} \
58 {0 _FOSSIL_-wal/a} \
59 {0 _FOSSIL_-shm/a} \
60 {0 _FOSSIL_-journal/a} \
61 {1 _FOSSIL_-wal} \
62 {1 _FOSSIL_-shm} \
63 {1 _FOSSIL_-journal} \
64 {2 a/_FOSSIL_-wal} \
65 {2 a/_FOSSIL_-shm} \
66 {2 a/_FOSSIL_-journal} \
67 {0 .fslckout-wal/a} \
68 {0 .fslckout-shm/a} \
69 {0 .fslckout-journal/a} \
70 {1 .fslckout-wal} \
71 {1 .fslckout-shm} \
72 {1 .fslckout-journal} \
73 {2 a/.fslckout-wal} \
74 {2 a/.fslckout-shm} \
75 {2 a/.fslckout-journal} \
76 ]
77
78 ###############################################################################
79
80 set testNo 0
81
82 foreach reserved_names_test $reserved_names_tests {
83 incr testNo
84
85 set reserved_result [lindex $reserved_names_test 0]
86 set reserved_name [lindex $reserved_names_test 1]
87
88 fossil test-is-reserved-name $reserved_name
89
90 test reserved-result-$testNo {
91 [lindex [normalize_result] 0] eq $reserved_result
92 }
93
94 test reserved-name-$testNo {
95 [lindex [normalize_result] 1] eq $reserved_name
96 }
97
98 fossil test-is-reserved-name [string toupper $reserved_name]
99
100 test reserved-result-upper-$testNo {
101 [lindex [normalize_result] 0] eq $reserved_result
102 }
103
104 test reserved-name-upper-$testNo {
105 [lindex [normalize_result] 1] eq [string toupper $reserved_name]
106 }
107
108 fossil test-is-reserved-name [string tolower $reserved_name]
109
110 test reserved-result-lower-$testNo {
111 [lindex [normalize_result] 0] eq $reserved_result
112 }
113
114 test reserved-name-lower-$testNo {
115 [lindex [normalize_result] 1] eq [string tolower $reserved_name]
116 }
117 }
118
119 ###############################################################################
120
121 test_cleanup
+10 -4
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -28,13 +28,13 @@
2828
2929
SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0
3030
3131
SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
3232
33
-SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
33
+SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
3434
35
-OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
35
+OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
3636
3737
3838
RC=$(DMDIR)\bin\rcc
3939
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
4040
@@ -49,11 +49,11 @@
4949
5050
$(OBJDIR)\fossil.res: $B\win\fossil.rc
5151
$(RC) $(RCFLAGS) -o$@ $**
5252
5353
$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
54
- +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
54
+ +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
5555
+echo fossil >> $@
5656
+echo fossil >> $@
5757
+echo $(LIBS) >> $@
5858
+echo. >> $@
5959
+echo fossil >> $@
@@ -475,10 +475,16 @@
475475
$(OBJDIR)\info$O : info_.c info.h
476476
$(TCC) -o$@ -c info_.c
477477
478478
info_.c : $(SRCDIR)\info.c
479479
+translate$E $** > $@
480
+
481
+$(OBJDIR)\interwiki$O : interwiki_.c interwiki.h
482
+ $(TCC) -o$@ -c interwiki_.c
483
+
484
+interwiki_.c : $(SRCDIR)\interwiki.c
485
+ +translate$E $** > $@
480486
481487
$(OBJDIR)\json$O : json_.c json.h
482488
$(TCC) -o$@ -c json_.c
483489
484490
json_.c : $(SRCDIR)\json.c
@@ -981,7 +987,7 @@
981987
982988
zip_.c : $(SRCDIR)\zip.c
983989
+translate$E $** > $@
984990
985991
headers: makeheaders$E page_index.h builtin_data.h VERSION.h
986
- +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
992
+ +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
987993
@copy /Y nul: headers
988994
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -28,13 +28,13 @@
28
29 SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0
30
31 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
32
33 SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
34
35 OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
36
37
38 RC=$(DMDIR)\bin\rcc
39 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
40
@@ -49,11 +49,11 @@
49
50 $(OBJDIR)\fossil.res: $B\win\fossil.rc
51 $(RC) $(RCFLAGS) -o$@ $**
52
53 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
54 +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
55 +echo fossil >> $@
56 +echo fossil >> $@
57 +echo $(LIBS) >> $@
58 +echo. >> $@
59 +echo fossil >> $@
@@ -475,10 +475,16 @@
475 $(OBJDIR)\info$O : info_.c info.h
476 $(TCC) -o$@ -c info_.c
477
478 info_.c : $(SRCDIR)\info.c
479 +translate$E $** > $@
 
 
 
 
 
 
480
481 $(OBJDIR)\json$O : json_.c json.h
482 $(TCC) -o$@ -c json_.c
483
484 json_.c : $(SRCDIR)\json.c
@@ -981,7 +987,7 @@
981
982 zip_.c : $(SRCDIR)\zip.c
983 +translate$E $** > $@
984
985 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
986 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
987 @copy /Y nul: headers
988
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -28,13 +28,13 @@
28
29 SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0
30
31 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
32
33 SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
34
35 OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
36
37
38 RC=$(DMDIR)\bin\rcc
39 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
40
@@ -49,11 +49,11 @@
49
50 $(OBJDIR)\fossil.res: $B\win\fossil.rc
51 $(RC) $(RCFLAGS) -o$@ $**
52
53 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
54 +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
55 +echo fossil >> $@
56 +echo fossil >> $@
57 +echo $(LIBS) >> $@
58 +echo. >> $@
59 +echo fossil >> $@
@@ -475,10 +475,16 @@
475 $(OBJDIR)\info$O : info_.c info.h
476 $(TCC) -o$@ -c info_.c
477
478 info_.c : $(SRCDIR)\info.c
479 +translate$E $** > $@
480
481 $(OBJDIR)\interwiki$O : interwiki_.c interwiki.h
482 $(TCC) -o$@ -c interwiki_.c
483
484 interwiki_.c : $(SRCDIR)\interwiki.c
485 +translate$E $** > $@
486
487 $(OBJDIR)\json$O : json_.c json.h
488 $(TCC) -o$@ -c json_.c
489
490 json_.c : $(SRCDIR)\json.c
@@ -981,7 +987,7 @@
987
988 zip_.c : $(SRCDIR)\zip.c
989 +translate$E $** > $@
990
991 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
992 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
993 @copy /Y nul: headers
994
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485485
$(SRCDIR)/http_socket.c \
486486
$(SRCDIR)/http_ssl.c \
487487
$(SRCDIR)/http_transport.c \
488488
$(SRCDIR)/import.c \
489489
$(SRCDIR)/info.c \
490
+ $(SRCDIR)/interwiki.c \
490491
$(SRCDIR)/json.c \
491492
$(SRCDIR)/json_artifact.c \
492493
$(SRCDIR)/json_branch.c \
493494
$(SRCDIR)/json_config.c \
494495
$(SRCDIR)/json_diff.c \
@@ -635,15 +636,18 @@
635636
$(SRCDIR)/default.css \
636637
$(SRCDIR)/diff.tcl \
637638
$(SRCDIR)/forum.js \
638639
$(SRCDIR)/fossil.bootstrap.js \
639640
$(SRCDIR)/fossil.confirmer.js \
641
+ $(SRCDIR)/fossil.copybutton.js \
640642
$(SRCDIR)/fossil.dom.js \
641643
$(SRCDIR)/fossil.fetch.js \
644
+ $(SRCDIR)/fossil.numbered-lines.js \
642645
$(SRCDIR)/fossil.page.fileedit.js \
643646
$(SRCDIR)/fossil.page.forumpost.js \
644647
$(SRCDIR)/fossil.page.wikiedit.js \
648
+ $(SRCDIR)/fossil.popupwidget.js \
645649
$(SRCDIR)/fossil.storage.js \
646650
$(SRCDIR)/fossil.tabs.js \
647651
$(SRCDIR)/graph.js \
648652
$(SRCDIR)/href.js \
649653
$(SRCDIR)/login.js \
@@ -734,10 +738,11 @@
734738
$(OBJDIR)/http_socket_.c \
735739
$(OBJDIR)/http_ssl_.c \
736740
$(OBJDIR)/http_transport_.c \
737741
$(OBJDIR)/import_.c \
738742
$(OBJDIR)/info_.c \
743
+ $(OBJDIR)/interwiki_.c \
739744
$(OBJDIR)/json_.c \
740745
$(OBJDIR)/json_artifact_.c \
741746
$(OBJDIR)/json_branch_.c \
742747
$(OBJDIR)/json_config_.c \
743748
$(OBJDIR)/json_diff_.c \
@@ -879,10 +884,11 @@
879884
$(OBJDIR)/http_socket.o \
880885
$(OBJDIR)/http_ssl.o \
881886
$(OBJDIR)/http_transport.o \
882887
$(OBJDIR)/import.o \
883888
$(OBJDIR)/info.o \
889
+ $(OBJDIR)/interwiki.o \
884890
$(OBJDIR)/json.o \
885891
$(OBJDIR)/json_artifact.o \
886892
$(OBJDIR)/json_branch.o \
887893
$(OBJDIR)/json_config.o \
888894
$(OBJDIR)/json_diff.o \
@@ -1239,10 +1245,11 @@
12391245
$(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
12401246
$(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
12411247
$(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
12421248
$(OBJDIR)/import_.c:$(OBJDIR)/import.h \
12431249
$(OBJDIR)/info_.c:$(OBJDIR)/info.h \
1250
+ $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
12441251
$(OBJDIR)/json_.c:$(OBJDIR)/json.h \
12451252
$(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
12461253
$(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
12471254
$(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
12481255
$(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1803,10 +1810,18 @@
18031810
18041811
$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
18051812
$(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
18061813
18071814
$(OBJDIR)/info.h: $(OBJDIR)/headers
1815
+
1816
+$(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(TRANSLATE)
1817
+ $(TRANSLATE) $(SRCDIR)/interwiki.c >$@
1818
+
1819
+$(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1820
+ $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1821
+
1822
+$(OBJDIR)/interwiki.h: $(OBJDIR)/headers
18081823
18091824
$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE)
18101825
$(TRANSLATE) $(SRCDIR)/json.c >$@
18111826
18121827
$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
18131828
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485 $(SRCDIR)/http_socket.c \
486 $(SRCDIR)/http_ssl.c \
487 $(SRCDIR)/http_transport.c \
488 $(SRCDIR)/import.c \
489 $(SRCDIR)/info.c \
 
490 $(SRCDIR)/json.c \
491 $(SRCDIR)/json_artifact.c \
492 $(SRCDIR)/json_branch.c \
493 $(SRCDIR)/json_config.c \
494 $(SRCDIR)/json_diff.c \
@@ -635,15 +636,18 @@
635 $(SRCDIR)/default.css \
636 $(SRCDIR)/diff.tcl \
637 $(SRCDIR)/forum.js \
638 $(SRCDIR)/fossil.bootstrap.js \
639 $(SRCDIR)/fossil.confirmer.js \
 
640 $(SRCDIR)/fossil.dom.js \
641 $(SRCDIR)/fossil.fetch.js \
 
642 $(SRCDIR)/fossil.page.fileedit.js \
643 $(SRCDIR)/fossil.page.forumpost.js \
644 $(SRCDIR)/fossil.page.wikiedit.js \
 
645 $(SRCDIR)/fossil.storage.js \
646 $(SRCDIR)/fossil.tabs.js \
647 $(SRCDIR)/graph.js \
648 $(SRCDIR)/href.js \
649 $(SRCDIR)/login.js \
@@ -734,10 +738,11 @@
734 $(OBJDIR)/http_socket_.c \
735 $(OBJDIR)/http_ssl_.c \
736 $(OBJDIR)/http_transport_.c \
737 $(OBJDIR)/import_.c \
738 $(OBJDIR)/info_.c \
 
739 $(OBJDIR)/json_.c \
740 $(OBJDIR)/json_artifact_.c \
741 $(OBJDIR)/json_branch_.c \
742 $(OBJDIR)/json_config_.c \
743 $(OBJDIR)/json_diff_.c \
@@ -879,10 +884,11 @@
879 $(OBJDIR)/http_socket.o \
880 $(OBJDIR)/http_ssl.o \
881 $(OBJDIR)/http_transport.o \
882 $(OBJDIR)/import.o \
883 $(OBJDIR)/info.o \
 
884 $(OBJDIR)/json.o \
885 $(OBJDIR)/json_artifact.o \
886 $(OBJDIR)/json_branch.o \
887 $(OBJDIR)/json_config.o \
888 $(OBJDIR)/json_diff.o \
@@ -1239,10 +1245,11 @@
1239 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
1240 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
1241 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
1242 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
1243 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
 
1244 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
1245 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
1246 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
1247 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
1248 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1803,10 +1810,18 @@
1803
1804 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1805 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1806
1807 $(OBJDIR)/info.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1808
1809 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE)
1810 $(TRANSLATE) $(SRCDIR)/json.c >$@
1811
1812 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1813
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485 $(SRCDIR)/http_socket.c \
486 $(SRCDIR)/http_ssl.c \
487 $(SRCDIR)/http_transport.c \
488 $(SRCDIR)/import.c \
489 $(SRCDIR)/info.c \
490 $(SRCDIR)/interwiki.c \
491 $(SRCDIR)/json.c \
492 $(SRCDIR)/json_artifact.c \
493 $(SRCDIR)/json_branch.c \
494 $(SRCDIR)/json_config.c \
495 $(SRCDIR)/json_diff.c \
@@ -635,15 +636,18 @@
636 $(SRCDIR)/default.css \
637 $(SRCDIR)/diff.tcl \
638 $(SRCDIR)/forum.js \
639 $(SRCDIR)/fossil.bootstrap.js \
640 $(SRCDIR)/fossil.confirmer.js \
641 $(SRCDIR)/fossil.copybutton.js \
642 $(SRCDIR)/fossil.dom.js \
643 $(SRCDIR)/fossil.fetch.js \
644 $(SRCDIR)/fossil.numbered-lines.js \
645 $(SRCDIR)/fossil.page.fileedit.js \
646 $(SRCDIR)/fossil.page.forumpost.js \
647 $(SRCDIR)/fossil.page.wikiedit.js \
648 $(SRCDIR)/fossil.popupwidget.js \
649 $(SRCDIR)/fossil.storage.js \
650 $(SRCDIR)/fossil.tabs.js \
651 $(SRCDIR)/graph.js \
652 $(SRCDIR)/href.js \
653 $(SRCDIR)/login.js \
@@ -734,10 +738,11 @@
738 $(OBJDIR)/http_socket_.c \
739 $(OBJDIR)/http_ssl_.c \
740 $(OBJDIR)/http_transport_.c \
741 $(OBJDIR)/import_.c \
742 $(OBJDIR)/info_.c \
743 $(OBJDIR)/interwiki_.c \
744 $(OBJDIR)/json_.c \
745 $(OBJDIR)/json_artifact_.c \
746 $(OBJDIR)/json_branch_.c \
747 $(OBJDIR)/json_config_.c \
748 $(OBJDIR)/json_diff_.c \
@@ -879,10 +884,11 @@
884 $(OBJDIR)/http_socket.o \
885 $(OBJDIR)/http_ssl.o \
886 $(OBJDIR)/http_transport.o \
887 $(OBJDIR)/import.o \
888 $(OBJDIR)/info.o \
889 $(OBJDIR)/interwiki.o \
890 $(OBJDIR)/json.o \
891 $(OBJDIR)/json_artifact.o \
892 $(OBJDIR)/json_branch.o \
893 $(OBJDIR)/json_config.o \
894 $(OBJDIR)/json_diff.o \
@@ -1239,10 +1245,11 @@
1245 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
1246 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
1247 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
1248 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
1249 $(OBJDIR)/info_.c:$(OBJDIR)/info.h \
1250 $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \
1251 $(OBJDIR)/json_.c:$(OBJDIR)/json.h \
1252 $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \
1253 $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \
1254 $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \
1255 $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \
@@ -1803,10 +1810,18 @@
1810
1811 $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h
1812 $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c
1813
1814 $(OBJDIR)/info.h: $(OBJDIR)/headers
1815
1816 $(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(TRANSLATE)
1817 $(TRANSLATE) $(SRCDIR)/interwiki.c >$@
1818
1819 $(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h
1820 $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c
1821
1822 $(OBJDIR)/interwiki.h: $(OBJDIR)/headers
1823
1824 $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE)
1825 $(TRANSLATE) $(SRCDIR)/json.c >$@
1826
1827 $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h
1828
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -478,10 +478,11 @@
478478
$(SRCDIR)/fuzz.c \
479479
$(SRCDIR)/glob.c \
480480
$(SRCDIR)/graph.c \
481481
$(SRCDIR)/gzip.c \
482482
$(SRCDIR)/hname.c \
483
+ $(SRCDIR)/hook.c \
483484
$(SRCDIR)/http.c \
484485
$(SRCDIR)/http_socket.c \
485486
$(SRCDIR)/http_ssl.c \
486487
$(SRCDIR)/http_transport.c \
487488
$(SRCDIR)/import.c \
@@ -565,11 +566,10 @@
565566
$(SRCDIR)/webmail.c \
566567
$(SRCDIR)/wiki.c \
567568
$(SRCDIR)/wikiformat.c \
568569
$(SRCDIR)/winfile.c \
569570
$(SRCDIR)/winhttp.c \
570
- $(SRCDIR)/wysiwyg.c \
571571
$(SRCDIR)/xfer.c \
572572
$(SRCDIR)/xfersetup.c \
573573
$(SRCDIR)/zip.c
574574
575575
EXTRA_FILES = \
@@ -635,13 +635,18 @@
635635
$(SRCDIR)/default.css \
636636
$(SRCDIR)/diff.tcl \
637637
$(SRCDIR)/forum.js \
638638
$(SRCDIR)/fossil.bootstrap.js \
639639
$(SRCDIR)/fossil.confirmer.js \
640
+ $(SRCDIR)/fossil.copybutton.js \
640641
$(SRCDIR)/fossil.dom.js \
641642
$(SRCDIR)/fossil.fetch.js \
643
+ $(SRCDIR)/fossil.numbered-lines.js \
642644
$(SRCDIR)/fossil.page.fileedit.js \
645
+ $(SRCDIR)/fossil.page.forumpost.js \
646
+ $(SRCDIR)/fossil.page.wikiedit.js \
647
+ $(SRCDIR)/fossil.popupwidget.js \
643648
$(SRCDIR)/fossil.storage.js \
644649
$(SRCDIR)/fossil.tabs.js \
645650
$(SRCDIR)/graph.js \
646651
$(SRCDIR)/href.js \
647652
$(SRCDIR)/login.js \
@@ -667,10 +672,11 @@
667672
$(SRCDIR)/sounds/d.wav \
668673
$(SRCDIR)/sounds/e.wav \
669674
$(SRCDIR)/sounds/f.wav \
670675
$(SRCDIR)/style.admin_log.css \
671676
$(SRCDIR)/style.fileedit.css \
677
+ $(SRCDIR)/style.wikiedit.css \
672678
$(SRCDIR)/tree.js \
673679
$(SRCDIR)/useredit.js \
674680
$(SRCDIR)/wiki.wiki
675681
676682
TRANS_SRC = \
@@ -724,10 +730,11 @@
724730
$(OBJDIR)/fuzz_.c \
725731
$(OBJDIR)/glob_.c \
726732
$(OBJDIR)/graph_.c \
727733
$(OBJDIR)/gzip_.c \
728734
$(OBJDIR)/hname_.c \
735
+ $(OBJDIR)/hook_.c \
729736
$(OBJDIR)/http_.c \
730737
$(OBJDIR)/http_socket_.c \
731738
$(OBJDIR)/http_ssl_.c \
732739
$(OBJDIR)/http_transport_.c \
733740
$(OBJDIR)/import_.c \
@@ -811,11 +818,10 @@
811818
$(OBJDIR)/webmail_.c \
812819
$(OBJDIR)/wiki_.c \
813820
$(OBJDIR)/wikiformat_.c \
814821
$(OBJDIR)/winfile_.c \
815822
$(OBJDIR)/winhttp_.c \
816
- $(OBJDIR)/wysiwyg_.c \
817823
$(OBJDIR)/xfer_.c \
818824
$(OBJDIR)/xfersetup_.c \
819825
$(OBJDIR)/zip_.c
820826
821827
OBJ = \
@@ -869,10 +875,11 @@
869875
$(OBJDIR)/fuzz.o \
870876
$(OBJDIR)/glob.o \
871877
$(OBJDIR)/graph.o \
872878
$(OBJDIR)/gzip.o \
873879
$(OBJDIR)/hname.o \
880
+ $(OBJDIR)/hook.o \
874881
$(OBJDIR)/http.o \
875882
$(OBJDIR)/http_socket.o \
876883
$(OBJDIR)/http_ssl.o \
877884
$(OBJDIR)/http_transport.o \
878885
$(OBJDIR)/import.o \
@@ -956,11 +963,10 @@
956963
$(OBJDIR)/webmail.o \
957964
$(OBJDIR)/wiki.o \
958965
$(OBJDIR)/wikiformat.o \
959966
$(OBJDIR)/winfile.o \
960967
$(OBJDIR)/winhttp.o \
961
- $(OBJDIR)/wysiwyg.o \
962968
$(OBJDIR)/xfer.o \
963969
$(OBJDIR)/xfersetup.o \
964970
$(OBJDIR)/zip.o
965971
966972
APPNAME = fossil.exe
@@ -1057,13 +1063,16 @@
10571063
# build is done from, i.e. the checkout belongs to. Do not sync/push
10581064
# the repository after running the tests.
10591065
test: $(OBJDIR) $(APPNAME)
10601066
$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
10611067
1062
-$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION)
1068
+$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(OBJDIR)/phony.h
10631069
$(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@
10641070
1071
+$(OBJDIR)/phony.h:
1072
+ # Force rebuild of VERSION.h every time "make" is run
1073
+
10651074
# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
10661075
# to 1. If it is set to 1, then there is no need to build or link
10671076
# the sqlite3.o object. Instead, the system SQLite will be linked
10681077
# using -lsqlite3.
10691078
SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
@@ -1226,10 +1235,11 @@
12261235
$(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \
12271236
$(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \
12281237
$(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \
12291238
$(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \
12301239
$(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \
1240
+ $(OBJDIR)/hook_.c:$(OBJDIR)/hook.h \
12311241
$(OBJDIR)/http_.c:$(OBJDIR)/http.h \
12321242
$(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
12331243
$(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
12341244
$(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
12351245
$(OBJDIR)/import_.c:$(OBJDIR)/import.h \
@@ -1313,11 +1323,10 @@
13131323
$(OBJDIR)/webmail_.c:$(OBJDIR)/webmail.h \
13141324
$(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \
13151325
$(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \
13161326
$(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \
13171327
$(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \
1318
- $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \
13191328
$(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \
13201329
$(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \
13211330
$(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \
13221331
$(SRCDIR)/sqlite3.h \
13231332
$(SRCDIR)/th.h \
@@ -1741,10 +1750,18 @@
17411750
17421751
$(OBJDIR)/hname.o: $(OBJDIR)/hname_.c $(OBJDIR)/hname.h $(SRCDIR)/config.h
17431752
$(XTCC) -o $(OBJDIR)/hname.o -c $(OBJDIR)/hname_.c
17441753
17451754
$(OBJDIR)/hname.h: $(OBJDIR)/headers
1755
+
1756
+$(OBJDIR)/hook_.c: $(SRCDIR)/hook.c $(TRANSLATE)
1757
+ $(TRANSLATE) $(SRCDIR)/hook.c >$@
1758
+
1759
+$(OBJDIR)/hook.o: $(OBJDIR)/hook_.c $(OBJDIR)/hook.h $(SRCDIR)/config.h
1760
+ $(XTCC) -o $(OBJDIR)/hook.o -c $(OBJDIR)/hook_.c
1761
+
1762
+$(OBJDIR)/hook.h: $(OBJDIR)/headers
17461763
17471764
$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE)
17481765
$(TRANSLATE) $(SRCDIR)/http.c >$@
17491766
17501767
$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h
@@ -2438,18 +2455,10 @@
24382455
$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h
24392456
$(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c
24402457
24412458
$(OBJDIR)/winhttp.h: $(OBJDIR)/headers
24422459
2443
-$(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(TRANSLATE)
2444
- $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$@
2445
-
2446
-$(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h
2447
- $(XTCC) -o $(OBJDIR)/wysiwyg.o -c $(OBJDIR)/wysiwyg_.c
2448
-
2449
-$(OBJDIR)/wysiwyg.h: $(OBJDIR)/headers
2450
-
24512460
$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE)
24522461
$(TRANSLATE) $(SRCDIR)/xfer.c >$@
24532462
24542463
$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h
24552464
$(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c
24562465
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -478,10 +478,11 @@
478 $(SRCDIR)/fuzz.c \
479 $(SRCDIR)/glob.c \
480 $(SRCDIR)/graph.c \
481 $(SRCDIR)/gzip.c \
482 $(SRCDIR)/hname.c \
 
483 $(SRCDIR)/http.c \
484 $(SRCDIR)/http_socket.c \
485 $(SRCDIR)/http_ssl.c \
486 $(SRCDIR)/http_transport.c \
487 $(SRCDIR)/import.c \
@@ -565,11 +566,10 @@
565 $(SRCDIR)/webmail.c \
566 $(SRCDIR)/wiki.c \
567 $(SRCDIR)/wikiformat.c \
568 $(SRCDIR)/winfile.c \
569 $(SRCDIR)/winhttp.c \
570 $(SRCDIR)/wysiwyg.c \
571 $(SRCDIR)/xfer.c \
572 $(SRCDIR)/xfersetup.c \
573 $(SRCDIR)/zip.c
574
575 EXTRA_FILES = \
@@ -635,13 +635,18 @@
635 $(SRCDIR)/default.css \
636 $(SRCDIR)/diff.tcl \
637 $(SRCDIR)/forum.js \
638 $(SRCDIR)/fossil.bootstrap.js \
639 $(SRCDIR)/fossil.confirmer.js \
 
640 $(SRCDIR)/fossil.dom.js \
641 $(SRCDIR)/fossil.fetch.js \
 
642 $(SRCDIR)/fossil.page.fileedit.js \
 
 
 
643 $(SRCDIR)/fossil.storage.js \
644 $(SRCDIR)/fossil.tabs.js \
645 $(SRCDIR)/graph.js \
646 $(SRCDIR)/href.js \
647 $(SRCDIR)/login.js \
@@ -667,10 +672,11 @@
667 $(SRCDIR)/sounds/d.wav \
668 $(SRCDIR)/sounds/e.wav \
669 $(SRCDIR)/sounds/f.wav \
670 $(SRCDIR)/style.admin_log.css \
671 $(SRCDIR)/style.fileedit.css \
 
672 $(SRCDIR)/tree.js \
673 $(SRCDIR)/useredit.js \
674 $(SRCDIR)/wiki.wiki
675
676 TRANS_SRC = \
@@ -724,10 +730,11 @@
724 $(OBJDIR)/fuzz_.c \
725 $(OBJDIR)/glob_.c \
726 $(OBJDIR)/graph_.c \
727 $(OBJDIR)/gzip_.c \
728 $(OBJDIR)/hname_.c \
 
729 $(OBJDIR)/http_.c \
730 $(OBJDIR)/http_socket_.c \
731 $(OBJDIR)/http_ssl_.c \
732 $(OBJDIR)/http_transport_.c \
733 $(OBJDIR)/import_.c \
@@ -811,11 +818,10 @@
811 $(OBJDIR)/webmail_.c \
812 $(OBJDIR)/wiki_.c \
813 $(OBJDIR)/wikiformat_.c \
814 $(OBJDIR)/winfile_.c \
815 $(OBJDIR)/winhttp_.c \
816 $(OBJDIR)/wysiwyg_.c \
817 $(OBJDIR)/xfer_.c \
818 $(OBJDIR)/xfersetup_.c \
819 $(OBJDIR)/zip_.c
820
821 OBJ = \
@@ -869,10 +875,11 @@
869 $(OBJDIR)/fuzz.o \
870 $(OBJDIR)/glob.o \
871 $(OBJDIR)/graph.o \
872 $(OBJDIR)/gzip.o \
873 $(OBJDIR)/hname.o \
 
874 $(OBJDIR)/http.o \
875 $(OBJDIR)/http_socket.o \
876 $(OBJDIR)/http_ssl.o \
877 $(OBJDIR)/http_transport.o \
878 $(OBJDIR)/import.o \
@@ -956,11 +963,10 @@
956 $(OBJDIR)/webmail.o \
957 $(OBJDIR)/wiki.o \
958 $(OBJDIR)/wikiformat.o \
959 $(OBJDIR)/winfile.o \
960 $(OBJDIR)/winhttp.o \
961 $(OBJDIR)/wysiwyg.o \
962 $(OBJDIR)/xfer.o \
963 $(OBJDIR)/xfersetup.o \
964 $(OBJDIR)/zip.o
965
966 APPNAME = fossil.exe
@@ -1057,13 +1063,16 @@
1057 # build is done from, i.e. the checkout belongs to. Do not sync/push
1058 # the repository after running the tests.
1059 test: $(OBJDIR) $(APPNAME)
1060 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
1061
1062 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION)
1063 $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@
1064
 
 
 
1065 # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
1066 # to 1. If it is set to 1, then there is no need to build or link
1067 # the sqlite3.o object. Instead, the system SQLite will be linked
1068 # using -lsqlite3.
1069 SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
@@ -1226,10 +1235,11 @@
1226 $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \
1227 $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \
1228 $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \
1229 $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \
1230 $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \
 
1231 $(OBJDIR)/http_.c:$(OBJDIR)/http.h \
1232 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
1233 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
1234 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
1235 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
@@ -1313,11 +1323,10 @@
1313 $(OBJDIR)/webmail_.c:$(OBJDIR)/webmail.h \
1314 $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \
1315 $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \
1316 $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \
1317 $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \
1318 $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \
1319 $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \
1320 $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \
1321 $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \
1322 $(SRCDIR)/sqlite3.h \
1323 $(SRCDIR)/th.h \
@@ -1741,10 +1750,18 @@
1741
1742 $(OBJDIR)/hname.o: $(OBJDIR)/hname_.c $(OBJDIR)/hname.h $(SRCDIR)/config.h
1743 $(XTCC) -o $(OBJDIR)/hname.o -c $(OBJDIR)/hname_.c
1744
1745 $(OBJDIR)/hname.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1746
1747 $(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE)
1748 $(TRANSLATE) $(SRCDIR)/http.c >$@
1749
1750 $(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h
@@ -2438,18 +2455,10 @@
2438 $(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h
2439 $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c
2440
2441 $(OBJDIR)/winhttp.h: $(OBJDIR)/headers
2442
2443 $(OBJDIR)/wysiwyg_.c: $(SRCDIR)/wysiwyg.c $(TRANSLATE)
2444 $(TRANSLATE) $(SRCDIR)/wysiwyg.c >$@
2445
2446 $(OBJDIR)/wysiwyg.o: $(OBJDIR)/wysiwyg_.c $(OBJDIR)/wysiwyg.h $(SRCDIR)/config.h
2447 $(XTCC) -o $(OBJDIR)/wysiwyg.o -c $(OBJDIR)/wysiwyg_.c
2448
2449 $(OBJDIR)/wysiwyg.h: $(OBJDIR)/headers
2450
2451 $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE)
2452 $(TRANSLATE) $(SRCDIR)/xfer.c >$@
2453
2454 $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h
2455 $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c
2456
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -478,10 +478,11 @@
478 $(SRCDIR)/fuzz.c \
479 $(SRCDIR)/glob.c \
480 $(SRCDIR)/graph.c \
481 $(SRCDIR)/gzip.c \
482 $(SRCDIR)/hname.c \
483 $(SRCDIR)/hook.c \
484 $(SRCDIR)/http.c \
485 $(SRCDIR)/http_socket.c \
486 $(SRCDIR)/http_ssl.c \
487 $(SRCDIR)/http_transport.c \
488 $(SRCDIR)/import.c \
@@ -565,11 +566,10 @@
566 $(SRCDIR)/webmail.c \
567 $(SRCDIR)/wiki.c \
568 $(SRCDIR)/wikiformat.c \
569 $(SRCDIR)/winfile.c \
570 $(SRCDIR)/winhttp.c \
 
571 $(SRCDIR)/xfer.c \
572 $(SRCDIR)/xfersetup.c \
573 $(SRCDIR)/zip.c
574
575 EXTRA_FILES = \
@@ -635,13 +635,18 @@
635 $(SRCDIR)/default.css \
636 $(SRCDIR)/diff.tcl \
637 $(SRCDIR)/forum.js \
638 $(SRCDIR)/fossil.bootstrap.js \
639 $(SRCDIR)/fossil.confirmer.js \
640 $(SRCDIR)/fossil.copybutton.js \
641 $(SRCDIR)/fossil.dom.js \
642 $(SRCDIR)/fossil.fetch.js \
643 $(SRCDIR)/fossil.numbered-lines.js \
644 $(SRCDIR)/fossil.page.fileedit.js \
645 $(SRCDIR)/fossil.page.forumpost.js \
646 $(SRCDIR)/fossil.page.wikiedit.js \
647 $(SRCDIR)/fossil.popupwidget.js \
648 $(SRCDIR)/fossil.storage.js \
649 $(SRCDIR)/fossil.tabs.js \
650 $(SRCDIR)/graph.js \
651 $(SRCDIR)/href.js \
652 $(SRCDIR)/login.js \
@@ -667,10 +672,11 @@
672 $(SRCDIR)/sounds/d.wav \
673 $(SRCDIR)/sounds/e.wav \
674 $(SRCDIR)/sounds/f.wav \
675 $(SRCDIR)/style.admin_log.css \
676 $(SRCDIR)/style.fileedit.css \
677 $(SRCDIR)/style.wikiedit.css \
678 $(SRCDIR)/tree.js \
679 $(SRCDIR)/useredit.js \
680 $(SRCDIR)/wiki.wiki
681
682 TRANS_SRC = \
@@ -724,10 +730,11 @@
730 $(OBJDIR)/fuzz_.c \
731 $(OBJDIR)/glob_.c \
732 $(OBJDIR)/graph_.c \
733 $(OBJDIR)/gzip_.c \
734 $(OBJDIR)/hname_.c \
735 $(OBJDIR)/hook_.c \
736 $(OBJDIR)/http_.c \
737 $(OBJDIR)/http_socket_.c \
738 $(OBJDIR)/http_ssl_.c \
739 $(OBJDIR)/http_transport_.c \
740 $(OBJDIR)/import_.c \
@@ -811,11 +818,10 @@
818 $(OBJDIR)/webmail_.c \
819 $(OBJDIR)/wiki_.c \
820 $(OBJDIR)/wikiformat_.c \
821 $(OBJDIR)/winfile_.c \
822 $(OBJDIR)/winhttp_.c \
 
823 $(OBJDIR)/xfer_.c \
824 $(OBJDIR)/xfersetup_.c \
825 $(OBJDIR)/zip_.c
826
827 OBJ = \
@@ -869,10 +875,11 @@
875 $(OBJDIR)/fuzz.o \
876 $(OBJDIR)/glob.o \
877 $(OBJDIR)/graph.o \
878 $(OBJDIR)/gzip.o \
879 $(OBJDIR)/hname.o \
880 $(OBJDIR)/hook.o \
881 $(OBJDIR)/http.o \
882 $(OBJDIR)/http_socket.o \
883 $(OBJDIR)/http_ssl.o \
884 $(OBJDIR)/http_transport.o \
885 $(OBJDIR)/import.o \
@@ -956,11 +963,10 @@
963 $(OBJDIR)/webmail.o \
964 $(OBJDIR)/wiki.o \
965 $(OBJDIR)/wikiformat.o \
966 $(OBJDIR)/winfile.o \
967 $(OBJDIR)/winhttp.o \
 
968 $(OBJDIR)/xfer.o \
969 $(OBJDIR)/xfersetup.o \
970 $(OBJDIR)/zip.o
971
972 APPNAME = fossil.exe
@@ -1057,13 +1063,16 @@
1063 # build is done from, i.e. the checkout belongs to. Do not sync/push
1064 # the repository after running the tests.
1065 test: $(OBJDIR) $(APPNAME)
1066 $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)
1067
1068 $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(OBJDIR)/phony.h
1069 $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@
1070
1071 $(OBJDIR)/phony.h:
1072 # Force rebuild of VERSION.h every time "make" is run
1073
1074 # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set
1075 # to 1. If it is set to 1, then there is no need to build or link
1076 # the sqlite3.o object. Instead, the system SQLite will be linked
1077 # using -lsqlite3.
1078 SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
@@ -1226,10 +1235,11 @@
1235 $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \
1236 $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \
1237 $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \
1238 $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \
1239 $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \
1240 $(OBJDIR)/hook_.c:$(OBJDIR)/hook.h \
1241 $(OBJDIR)/http_.c:$(OBJDIR)/http.h \
1242 $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \
1243 $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \
1244 $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \
1245 $(OBJDIR)/import_.c:$(OBJDIR)/import.h \
@@ -1313,11 +1323,10 @@
1323 $(OBJDIR)/webmail_.c:$(OBJDIR)/webmail.h \
1324 $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \
1325 $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \
1326 $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \
1327 $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \
 
1328 $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \
1329 $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \
1330 $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \
1331 $(SRCDIR)/sqlite3.h \
1332 $(SRCDIR)/th.h \
@@ -1741,10 +1750,18 @@
1750
1751 $(OBJDIR)/hname.o: $(OBJDIR)/hname_.c $(OBJDIR)/hname.h $(SRCDIR)/config.h
1752 $(XTCC) -o $(OBJDIR)/hname.o -c $(OBJDIR)/hname_.c
1753
1754 $(OBJDIR)/hname.h: $(OBJDIR)/headers
1755
1756 $(OBJDIR)/hook_.c: $(SRCDIR)/hook.c $(TRANSLATE)
1757 $(TRANSLATE) $(SRCDIR)/hook.c >$@
1758
1759 $(OBJDIR)/hook.o: $(OBJDIR)/hook_.c $(OBJDIR)/hook.h $(SRCDIR)/config.h
1760 $(XTCC) -o $(OBJDIR)/hook.o -c $(OBJDIR)/hook_.c
1761
1762 $(OBJDIR)/hook.h: $(OBJDIR)/headers
1763
1764 $(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE)
1765 $(TRANSLATE) $(SRCDIR)/http.c >$@
1766
1767 $(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h
@@ -2438,18 +2455,10 @@
2455 $(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h
2456 $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c
2457
2458 $(OBJDIR)/winhttp.h: $(OBJDIR)/headers
2459
 
 
 
 
 
 
 
 
2460 $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE)
2461 $(TRANSLATE) $(SRCDIR)/xfer.c >$@
2462
2463 $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h
2464 $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c
2465
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -407,10 +407,11 @@
407407
"$(OX)\http_socket_.c" \
408408
"$(OX)\http_ssl_.c" \
409409
"$(OX)\http_transport_.c" \
410410
"$(OX)\import_.c" \
411411
"$(OX)\info_.c" \
412
+ "$(OX)\interwiki_.c" \
412413
"$(OX)\json_.c" \
413414
"$(OX)\json_artifact_.c" \
414415
"$(OX)\json_branch_.c" \
415416
"$(OX)\json_config_.c" \
416417
"$(OX)\json_diff_.c" \
@@ -556,15 +557,18 @@
556557
"$(SRCDIR)\default.css" \
557558
"$(SRCDIR)\diff.tcl" \
558559
"$(SRCDIR)\forum.js" \
559560
"$(SRCDIR)\fossil.bootstrap.js" \
560561
"$(SRCDIR)\fossil.confirmer.js" \
562
+ "$(SRCDIR)\fossil.copybutton.js" \
561563
"$(SRCDIR)\fossil.dom.js" \
562564
"$(SRCDIR)\fossil.fetch.js" \
565
+ "$(SRCDIR)\fossil.numbered-lines.js" \
563566
"$(SRCDIR)\fossil.page.fileedit.js" \
564567
"$(SRCDIR)\fossil.page.forumpost.js" \
565568
"$(SRCDIR)\fossil.page.wikiedit.js" \
569
+ "$(SRCDIR)\fossil.popupwidget.js" \
566570
"$(SRCDIR)\fossil.storage.js" \
567571
"$(SRCDIR)\fossil.tabs.js" \
568572
"$(SRCDIR)\graph.js" \
569573
"$(SRCDIR)\href.js" \
570574
"$(SRCDIR)\login.js" \
@@ -655,10 +659,11 @@
655659
"$(OX)\http_socket$O" \
656660
"$(OX)\http_ssl$O" \
657661
"$(OX)\http_transport$O" \
658662
"$(OX)\import$O" \
659663
"$(OX)\info$O" \
664
+ "$(OX)\interwiki$O" \
660665
"$(OX)\json$O" \
661666
"$(OX)\json_artifact$O" \
662667
"$(OX)\json_branch$O" \
663668
"$(OX)\json_config$O" \
664669
"$(OX)\json_diff$O" \
@@ -881,10 +886,11 @@
881886
echo "$(OX)\http_socket.obj" >> $@
882887
echo "$(OX)\http_ssl.obj" >> $@
883888
echo "$(OX)\http_transport.obj" >> $@
884889
echo "$(OX)\import.obj" >> $@
885890
echo "$(OX)\info.obj" >> $@
891
+ echo "$(OX)\interwiki.obj" >> $@
886892
echo "$(OX)\json.obj" >> $@
887893
echo "$(OX)\json_artifact.obj" >> $@
888894
echo "$(OX)\json_branch.obj" >> $@
889895
echo "$(OX)\json_config.obj" >> $@
890896
echo "$(OX)\json_diff.obj" >> $@
@@ -1150,15 +1156,18 @@
11501156
echo "$(SRCDIR)\default.css" >> $@
11511157
echo "$(SRCDIR)\diff.tcl" >> $@
11521158
echo "$(SRCDIR)\forum.js" >> $@
11531159
echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
11541160
echo "$(SRCDIR)\fossil.confirmer.js" >> $@
1161
+ echo "$(SRCDIR)\fossil.copybutton.js" >> $@
11551162
echo "$(SRCDIR)\fossil.dom.js" >> $@
11561163
echo "$(SRCDIR)\fossil.fetch.js" >> $@
1164
+ echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
11571165
echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
11581166
echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
11591167
echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
1168
+ echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
11601169
echo "$(SRCDIR)\fossil.storage.js" >> $@
11611170
echo "$(SRCDIR)\fossil.tabs.js" >> $@
11621171
echo "$(SRCDIR)\graph.js" >> $@
11631172
echo "$(SRCDIR)\href.js" >> $@
11641173
echo "$(SRCDIR)\login.js" >> $@
@@ -1542,10 +1551,16 @@
15421551
"$(OX)\info$O" : "$(OX)\info_.c" "$(OX)\info.h"
15431552
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\info_.c"
15441553
15451554
"$(OX)\info_.c" : "$(SRCDIR)\info.c"
15461555
"$(OBJDIR)\translate$E" $** > $@
1556
+
1557
+"$(OX)\interwiki$O" : "$(OX)\interwiki_.c" "$(OX)\interwiki.h"
1558
+ $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\interwiki_.c"
1559
+
1560
+"$(OX)\interwiki_.c" : "$(SRCDIR)\interwiki.c"
1561
+ "$(OBJDIR)\translate$E" $** > $@
15471562
15481563
"$(OX)\json$O" : "$(OX)\json_.c" "$(OX)\json.h"
15491564
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_.c"
15501565
15511566
"$(OX)\json_.c" : "$(SRCDIR)\json.c"
@@ -2110,10 +2125,11 @@
21102125
"$(OX)\http_socket_.c":"$(OX)\http_socket.h" \
21112126
"$(OX)\http_ssl_.c":"$(OX)\http_ssl.h" \
21122127
"$(OX)\http_transport_.c":"$(OX)\http_transport.h" \
21132128
"$(OX)\import_.c":"$(OX)\import.h" \
21142129
"$(OX)\info_.c":"$(OX)\info.h" \
2130
+ "$(OX)\interwiki_.c":"$(OX)\interwiki.h" \
21152131
"$(OX)\json_.c":"$(OX)\json.h" \
21162132
"$(OX)\json_artifact_.c":"$(OX)\json_artifact.h" \
21172133
"$(OX)\json_branch_.c":"$(OX)\json_branch.h" \
21182134
"$(OX)\json_config_.c":"$(OX)\json_config.h" \
21192135
"$(OX)\json_diff_.c":"$(OX)\json_diff.h" \
21202136
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -407,10 +407,11 @@
407 "$(OX)\http_socket_.c" \
408 "$(OX)\http_ssl_.c" \
409 "$(OX)\http_transport_.c" \
410 "$(OX)\import_.c" \
411 "$(OX)\info_.c" \
 
412 "$(OX)\json_.c" \
413 "$(OX)\json_artifact_.c" \
414 "$(OX)\json_branch_.c" \
415 "$(OX)\json_config_.c" \
416 "$(OX)\json_diff_.c" \
@@ -556,15 +557,18 @@
556 "$(SRCDIR)\default.css" \
557 "$(SRCDIR)\diff.tcl" \
558 "$(SRCDIR)\forum.js" \
559 "$(SRCDIR)\fossil.bootstrap.js" \
560 "$(SRCDIR)\fossil.confirmer.js" \
 
561 "$(SRCDIR)\fossil.dom.js" \
562 "$(SRCDIR)\fossil.fetch.js" \
 
563 "$(SRCDIR)\fossil.page.fileedit.js" \
564 "$(SRCDIR)\fossil.page.forumpost.js" \
565 "$(SRCDIR)\fossil.page.wikiedit.js" \
 
566 "$(SRCDIR)\fossil.storage.js" \
567 "$(SRCDIR)\fossil.tabs.js" \
568 "$(SRCDIR)\graph.js" \
569 "$(SRCDIR)\href.js" \
570 "$(SRCDIR)\login.js" \
@@ -655,10 +659,11 @@
655 "$(OX)\http_socket$O" \
656 "$(OX)\http_ssl$O" \
657 "$(OX)\http_transport$O" \
658 "$(OX)\import$O" \
659 "$(OX)\info$O" \
 
660 "$(OX)\json$O" \
661 "$(OX)\json_artifact$O" \
662 "$(OX)\json_branch$O" \
663 "$(OX)\json_config$O" \
664 "$(OX)\json_diff$O" \
@@ -881,10 +886,11 @@
881 echo "$(OX)\http_socket.obj" >> $@
882 echo "$(OX)\http_ssl.obj" >> $@
883 echo "$(OX)\http_transport.obj" >> $@
884 echo "$(OX)\import.obj" >> $@
885 echo "$(OX)\info.obj" >> $@
 
886 echo "$(OX)\json.obj" >> $@
887 echo "$(OX)\json_artifact.obj" >> $@
888 echo "$(OX)\json_branch.obj" >> $@
889 echo "$(OX)\json_config.obj" >> $@
890 echo "$(OX)\json_diff.obj" >> $@
@@ -1150,15 +1156,18 @@
1150 echo "$(SRCDIR)\default.css" >> $@
1151 echo "$(SRCDIR)\diff.tcl" >> $@
1152 echo "$(SRCDIR)\forum.js" >> $@
1153 echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
1154 echo "$(SRCDIR)\fossil.confirmer.js" >> $@
 
1155 echo "$(SRCDIR)\fossil.dom.js" >> $@
1156 echo "$(SRCDIR)\fossil.fetch.js" >> $@
 
1157 echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
1158 echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
1159 echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
 
1160 echo "$(SRCDIR)\fossil.storage.js" >> $@
1161 echo "$(SRCDIR)\fossil.tabs.js" >> $@
1162 echo "$(SRCDIR)\graph.js" >> $@
1163 echo "$(SRCDIR)\href.js" >> $@
1164 echo "$(SRCDIR)\login.js" >> $@
@@ -1542,10 +1551,16 @@
1542 "$(OX)\info$O" : "$(OX)\info_.c" "$(OX)\info.h"
1543 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\info_.c"
1544
1545 "$(OX)\info_.c" : "$(SRCDIR)\info.c"
1546 "$(OBJDIR)\translate$E" $** > $@
 
 
 
 
 
 
1547
1548 "$(OX)\json$O" : "$(OX)\json_.c" "$(OX)\json.h"
1549 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_.c"
1550
1551 "$(OX)\json_.c" : "$(SRCDIR)\json.c"
@@ -2110,10 +2125,11 @@
2110 "$(OX)\http_socket_.c":"$(OX)\http_socket.h" \
2111 "$(OX)\http_ssl_.c":"$(OX)\http_ssl.h" \
2112 "$(OX)\http_transport_.c":"$(OX)\http_transport.h" \
2113 "$(OX)\import_.c":"$(OX)\import.h" \
2114 "$(OX)\info_.c":"$(OX)\info.h" \
 
2115 "$(OX)\json_.c":"$(OX)\json.h" \
2116 "$(OX)\json_artifact_.c":"$(OX)\json_artifact.h" \
2117 "$(OX)\json_branch_.c":"$(OX)\json_branch.h" \
2118 "$(OX)\json_config_.c":"$(OX)\json_config.h" \
2119 "$(OX)\json_diff_.c":"$(OX)\json_diff.h" \
2120
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -407,10 +407,11 @@
407 "$(OX)\http_socket_.c" \
408 "$(OX)\http_ssl_.c" \
409 "$(OX)\http_transport_.c" \
410 "$(OX)\import_.c" \
411 "$(OX)\info_.c" \
412 "$(OX)\interwiki_.c" \
413 "$(OX)\json_.c" \
414 "$(OX)\json_artifact_.c" \
415 "$(OX)\json_branch_.c" \
416 "$(OX)\json_config_.c" \
417 "$(OX)\json_diff_.c" \
@@ -556,15 +557,18 @@
557 "$(SRCDIR)\default.css" \
558 "$(SRCDIR)\diff.tcl" \
559 "$(SRCDIR)\forum.js" \
560 "$(SRCDIR)\fossil.bootstrap.js" \
561 "$(SRCDIR)\fossil.confirmer.js" \
562 "$(SRCDIR)\fossil.copybutton.js" \
563 "$(SRCDIR)\fossil.dom.js" \
564 "$(SRCDIR)\fossil.fetch.js" \
565 "$(SRCDIR)\fossil.numbered-lines.js" \
566 "$(SRCDIR)\fossil.page.fileedit.js" \
567 "$(SRCDIR)\fossil.page.forumpost.js" \
568 "$(SRCDIR)\fossil.page.wikiedit.js" \
569 "$(SRCDIR)\fossil.popupwidget.js" \
570 "$(SRCDIR)\fossil.storage.js" \
571 "$(SRCDIR)\fossil.tabs.js" \
572 "$(SRCDIR)\graph.js" \
573 "$(SRCDIR)\href.js" \
574 "$(SRCDIR)\login.js" \
@@ -655,10 +659,11 @@
659 "$(OX)\http_socket$O" \
660 "$(OX)\http_ssl$O" \
661 "$(OX)\http_transport$O" \
662 "$(OX)\import$O" \
663 "$(OX)\info$O" \
664 "$(OX)\interwiki$O" \
665 "$(OX)\json$O" \
666 "$(OX)\json_artifact$O" \
667 "$(OX)\json_branch$O" \
668 "$(OX)\json_config$O" \
669 "$(OX)\json_diff$O" \
@@ -881,10 +886,11 @@
886 echo "$(OX)\http_socket.obj" >> $@
887 echo "$(OX)\http_ssl.obj" >> $@
888 echo "$(OX)\http_transport.obj" >> $@
889 echo "$(OX)\import.obj" >> $@
890 echo "$(OX)\info.obj" >> $@
891 echo "$(OX)\interwiki.obj" >> $@
892 echo "$(OX)\json.obj" >> $@
893 echo "$(OX)\json_artifact.obj" >> $@
894 echo "$(OX)\json_branch.obj" >> $@
895 echo "$(OX)\json_config.obj" >> $@
896 echo "$(OX)\json_diff.obj" >> $@
@@ -1150,15 +1156,18 @@
1156 echo "$(SRCDIR)\default.css" >> $@
1157 echo "$(SRCDIR)\diff.tcl" >> $@
1158 echo "$(SRCDIR)\forum.js" >> $@
1159 echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
1160 echo "$(SRCDIR)\fossil.confirmer.js" >> $@
1161 echo "$(SRCDIR)\fossil.copybutton.js" >> $@
1162 echo "$(SRCDIR)\fossil.dom.js" >> $@
1163 echo "$(SRCDIR)\fossil.fetch.js" >> $@
1164 echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
1165 echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
1166 echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
1167 echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
1168 echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
1169 echo "$(SRCDIR)\fossil.storage.js" >> $@
1170 echo "$(SRCDIR)\fossil.tabs.js" >> $@
1171 echo "$(SRCDIR)\graph.js" >> $@
1172 echo "$(SRCDIR)\href.js" >> $@
1173 echo "$(SRCDIR)\login.js" >> $@
@@ -1542,10 +1551,16 @@
1551 "$(OX)\info$O" : "$(OX)\info_.c" "$(OX)\info.h"
1552 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\info_.c"
1553
1554 "$(OX)\info_.c" : "$(SRCDIR)\info.c"
1555 "$(OBJDIR)\translate$E" $** > $@
1556
1557 "$(OX)\interwiki$O" : "$(OX)\interwiki_.c" "$(OX)\interwiki.h"
1558 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\interwiki_.c"
1559
1560 "$(OX)\interwiki_.c" : "$(SRCDIR)\interwiki.c"
1561 "$(OBJDIR)\translate$E" $** > $@
1562
1563 "$(OX)\json$O" : "$(OX)\json_.c" "$(OX)\json.h"
1564 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_.c"
1565
1566 "$(OX)\json_.c" : "$(SRCDIR)\json.c"
@@ -2110,10 +2125,11 @@
2125 "$(OX)\http_socket_.c":"$(OX)\http_socket.h" \
2126 "$(OX)\http_ssl_.c":"$(OX)\http_ssl.h" \
2127 "$(OX)\http_transport_.c":"$(OX)\http_transport.h" \
2128 "$(OX)\import_.c":"$(OX)\import.h" \
2129 "$(OX)\info_.c":"$(OX)\info.h" \
2130 "$(OX)\interwiki_.c":"$(OX)\interwiki.h" \
2131 "$(OX)\json_.c":"$(OX)\json.h" \
2132 "$(OX)\json_artifact_.c":"$(OX)\json_artifact.h" \
2133 "$(OX)\json_branch_.c":"$(OX)\json_branch.h" \
2134 "$(OX)\json_config_.c":"$(OX)\json_config.h" \
2135 "$(OX)\json_diff_.c":"$(OX)\json_diff.h" \
2136
+13 -11
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,21 +1,18 @@
11
<title>Change Log</title>
22
3
-<a name='v2_12_1'></a>
4
-<h2>Changes for Version 2.12.1 (2020-08-19)</h2>
5
-
6
- * Fix in the new [/help?cmd=/wikiedit|/wikiedit] and
7
- [/help?cmd=/fileedit|/fileedit] pages for a mistake which allowed
8
- both to leak edits across all repositories on the same "origin"
9
- (in short, the same domain, port number, and browser profile
10
- instance). Full details are in
11
- [https://fossil-scm.org/forum/forumpost/33dd754eaf|the support
12
- forum]. This does not affect any versions prior to 2.12.
3
+<a name='v2_13'></a>
4
+<h2>Changes for Version 2.13 (pending)</h2>
5
+
6
+ * Added support for [./interwiki.md|interwiki links].
7
+ * Enable &lt;del&gt; and &lt;ins&gt; markup in wiki.
8
+ * Improvements to the Forum threading display.
139
1410
<a name='v2_12'></a>
15
-<h2>Changes for Version 2.12 (pending)</h2>
11
+<h2>Changes for Version 2.12.1 (2020-08-20)</h2>
1612
13
+ * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz.
1714
* Security fix in the "[/help?cmd=git|fossil git export]" command.
1815
The same fix is also backported to version 2.10.1 and 2.11.1.
1916
New "safety-net" features were added to prevent similar problems
2017
in the future.
2118
* Enhancements to the graph display for cases when there are
@@ -48,10 +45,11 @@
4845
* Added the experimental "[/help?cmd=hook|fossil hook]" command for
4946
managing "hook scripts" that run before checkin or after a push.
5047
* Enhance the [/help?cmd=revert|fossil revert] command so that it
5148
is able to revert all files beneath a directory.
5249
* Add the [/help?cmd=bisect|fossil bisect skip] command.
50
+ * Add the [/help?cmd=backup|fossil backup] command.
5351
* Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked
5452
check-ins in between the innermost "good" and "bad" check-ins.
5553
* Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]",
5654
"[/help?cmd=rm|fossil rm]", and
5755
"[/help?cmd=addremove|fossil addremove]" commands.
@@ -90,10 +88,12 @@
9088
* Countless documentation enhancements.
9189
9290
<a name='v2_11'></a>
9391
<h2>Changes for Version 2.11 (2020-05-25)</h2>
9492
93
+ * (2.11.2): Backport security fixes from 2.12.1
94
+ * (2.11.1): Backport security fix for the "fossil git export" command.
9595
* Support [/md_rules|Markdown] in the default ticket configuration.
9696
* Timestamp strings in [./checkin_names.wiki|object names]
9797
can now omit punctation. So, for example, "202004181942" and
9898
"2020-04-18 19:42" mean the same thing.
9999
* Enhance backlink processing so that it works with Markdown-formatted
@@ -179,10 +179,12 @@
179179
* Many minor enhancements to existing features.
180180
181181
<a name='v2_10'></a>
182182
<h2>Changes for Version 2.10 (2019-10-04)</h2>
183183
184
+ * (2.10.2): backport security fixes from 2.12.1
185
+ * (2.10.1): backport security fix for the "fossil git export" command.
184186
* Added support for [./serverext.wiki|CGI-based Server Extensions].
185187
* Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
186188
add style to repository list pages.
187189
* Enhance the hierarchical display of Forum threads to do less
188190
indentation and to provide links back to the previous message
189191
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,21 +1,18 @@
1 <title>Change Log</title>
2
3 <a name='v2_12_1'></a>
4 <h2>Changes for Version 2.12.1 (2020-08-19)</h2>
5
6 * Fix in the new [/help?cmd=/wikiedit|/wikiedit] and
7 [/help?cmd=/fileedit|/fileedit] pages for a mistake which allowed
8 both to leak edits across all repositories on the same "origin"
9 (in short, the same domain, port number, and browser profile
10 instance). Full details are in
11 [https://fossil-scm.org/forum/forumpost/33dd754eaf|the support
12 forum]. This does not affect any versions prior to 2.12.
13
14 <a name='v2_12'></a>
15 <h2>Changes for Version 2.12 (pending)</h2>
16
 
17 * Security fix in the "[/help?cmd=git|fossil git export]" command.
18 The same fix is also backported to version 2.10.1 and 2.11.1.
19 New "safety-net" features were added to prevent similar problems
20 in the future.
21 * Enhancements to the graph display for cases when there are
@@ -48,10 +45,11 @@
48 * Added the experimental "[/help?cmd=hook|fossil hook]" command for
49 managing "hook scripts" that run before checkin or after a push.
50 * Enhance the [/help?cmd=revert|fossil revert] command so that it
51 is able to revert all files beneath a directory.
52 * Add the [/help?cmd=bisect|fossil bisect skip] command.
 
53 * Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked
54 check-ins in between the innermost "good" and "bad" check-ins.
55 * Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]",
56 "[/help?cmd=rm|fossil rm]", and
57 "[/help?cmd=addremove|fossil addremove]" commands.
@@ -90,10 +88,12 @@
90 * Countless documentation enhancements.
91
92 <a name='v2_11'></a>
93 <h2>Changes for Version 2.11 (2020-05-25)</h2>
94
 
 
95 * Support [/md_rules|Markdown] in the default ticket configuration.
96 * Timestamp strings in [./checkin_names.wiki|object names]
97 can now omit punctation. So, for example, "202004181942" and
98 "2020-04-18 19:42" mean the same thing.
99 * Enhance backlink processing so that it works with Markdown-formatted
@@ -179,10 +179,12 @@
179 * Many minor enhancements to existing features.
180
181 <a name='v2_10'></a>
182 <h2>Changes for Version 2.10 (2019-10-04)</h2>
183
 
 
184 * Added support for [./serverext.wiki|CGI-based Server Extensions].
185 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
186 add style to repository list pages.
187 * Enhance the hierarchical display of Forum threads to do less
188 indentation and to provide links back to the previous message
189
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,21 +1,18 @@
1 <title>Change Log</title>
2
3 <a name='v2_13'></a>
4 <h2>Changes for Version 2.13 (pending)</h2>
5
6 * Added support for [./interwiki.md|interwiki links].
7 * Enable &lt;del&gt; and &lt;ins&gt; markup in wiki.
8 * Improvements to the Forum threading display.
 
 
 
 
9
10 <a name='v2_12'></a>
11 <h2>Changes for Version 2.12.1 (2020-08-20)</h2>
12
13 * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz.
14 * Security fix in the "[/help?cmd=git|fossil git export]" command.
15 The same fix is also backported to version 2.10.1 and 2.11.1.
16 New "safety-net" features were added to prevent similar problems
17 in the future.
18 * Enhancements to the graph display for cases when there are
@@ -48,10 +45,11 @@
45 * Added the experimental "[/help?cmd=hook|fossil hook]" command for
46 managing "hook scripts" that run before checkin or after a push.
47 * Enhance the [/help?cmd=revert|fossil revert] command so that it
48 is able to revert all files beneath a directory.
49 * Add the [/help?cmd=bisect|fossil bisect skip] command.
50 * Add the [/help?cmd=backup|fossil backup] command.
51 * Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked
52 check-ins in between the innermost "good" and "bad" check-ins.
53 * Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]",
54 "[/help?cmd=rm|fossil rm]", and
55 "[/help?cmd=addremove|fossil addremove]" commands.
@@ -90,10 +88,12 @@
88 * Countless documentation enhancements.
89
90 <a name='v2_11'></a>
91 <h2>Changes for Version 2.11 (2020-05-25)</h2>
92
93 * (2.11.2): Backport security fixes from 2.12.1
94 * (2.11.1): Backport security fix for the "fossil git export" command.
95 * Support [/md_rules|Markdown] in the default ticket configuration.
96 * Timestamp strings in [./checkin_names.wiki|object names]
97 can now omit punctation. So, for example, "202004181942" and
98 "2020-04-18 19:42" mean the same thing.
99 * Enhance backlink processing so that it works with Markdown-formatted
@@ -179,10 +179,12 @@
179 * Many minor enhancements to existing features.
180
181 <a name='v2_10'></a>
182 <h2>Changes for Version 2.10 (2019-10-04)</h2>
183
184 * (2.10.2): backport security fixes from 2.12.1
185 * (2.10.1): backport security fix for the "fossil git export" command.
186 * Added support for [./serverext.wiki|CGI-based Server Extensions].
187 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
188 add style to repository list pages.
189 * Enhance the hierarchical display of Forum threads to do less
190 indentation and to provide links back to the previous message
191
+1 -1
--- www/chroot.md
+++ www/chroot.md
@@ -37,6 +37,6 @@
3737
[bld]: https://www.fossil-scm.org/fossil/doc/trunk/www/build.wiki
3838
[cj]: https://en.wikipedia.org/wiki/Chroot
3939
[fls]: ./loadmgmt.md
4040
[mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb
4141
[srv]: ./server/
42
-[obsd]: ./server/openbsd/httpd.md#chroot
42
+[obsd]: ./server/openbsd/fastcgi.md#chroot
4343
--- www/chroot.md
+++ www/chroot.md
@@ -37,6 +37,6 @@
37 [bld]: https://www.fossil-scm.org/fossil/doc/trunk/www/build.wiki
38 [cj]: https://en.wikipedia.org/wiki/Chroot
39 [fls]: ./loadmgmt.md
40 [mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb
41 [srv]: ./server/
42 [obsd]: ./server/openbsd/httpd.md#chroot
43
--- www/chroot.md
+++ www/chroot.md
@@ -37,6 +37,6 @@
37 [bld]: https://www.fossil-scm.org/fossil/doc/trunk/www/build.wiki
38 [cj]: https://en.wikipedia.org/wiki/Chroot
39 [fls]: ./loadmgmt.md
40 [mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb
41 [srv]: ./server/
42 [obsd]: ./server/openbsd/fastcgi.md#chroot
43
--- www/customskin.md
+++ www/customskin.md
@@ -68,12 +68,13 @@
6868
<tr><td style='background-color:lightblue;text-align:center;'>Content Footer</td></tr>
6969
<tr><td style='background-color:lightgreen;text-align:center;'>
7070
Fossil-Generated HTML Footer</td></tr>
7171
</tbody></table></blockquote>
7272
73
-The green parts are generated by Fossil. The blue parts are things that
74
-you, the administrator, get to modify in order to customize the skin.
73
+The green parts are *usually* generated by Fossil. The blue parts
74
+are things that you, the administrator, get to modify in order to
75
+customize the skin.
7576
7677
Fossil *usually* (but not always - [see below](#override))
7778
generates the initial HTML Header section of a page. The
7879
generated HTML Header will look something like this:
7980
@@ -85,31 +86,33 @@
8586
<title>....</title>
8687
<link rel="stylesheet" href="..." type="text/css" />
8788
</head>
8889
<body>
8990
90
-In most cases, it is best to leave the Fossil-generated HTML Header alone.
91
-The configurable part of the skin begins with the Content Header section which
92
-should followign the following template:
91
+In most cases, it is best to leave the Fossil-generated HTML Header
92
+alone. (One exception is when the administrator needs to include links
93
+to additional CSS files.) The configurable part of the skin begins
94
+with the Content Header section which should follow this template:
9395
9496
<div class="header">
9597
... top banner and menu bar ...
9698
</div>
9799
98
-Note that `<div class="header">` and `</div>` tags must be included in the
99
-Content Header text of the skin. In other words, you the administrator need
100
-to supply that text as part of your skin customization.
100
+Note that `<div class="header">` and `</div>` tags must be included in
101
+the Content Header text of the skin. In other words, you, the
102
+administrator, need to supply that text as part of your skin
103
+customization.
101104
102105
The Fossil-generated Content section immediately follows the Content Header.
103106
The Content section will looks like this:
104107
105108
<div class="content">
106109
... Fossil-generated content here ...
107110
</div>
108111
109112
After the Content is the custom Content Footer section which should
110
-following this template:
113
+follow this template:
111114
112115
<div class="footer">
113116
... skin-specific stuff here ...
114117
</div>
115118
<script nonce="$nonce">
116119
--- www/customskin.md
+++ www/customskin.md
@@ -68,12 +68,13 @@
68 <tr><td style='background-color:lightblue;text-align:center;'>Content Footer</td></tr>
69 <tr><td style='background-color:lightgreen;text-align:center;'>
70 Fossil-Generated HTML Footer</td></tr>
71 </tbody></table></blockquote>
72
73 The green parts are generated by Fossil. The blue parts are things that
74 you, the administrator, get to modify in order to customize the skin.
 
75
76 Fossil *usually* (but not always - [see below](#override))
77 generates the initial HTML Header section of a page. The
78 generated HTML Header will look something like this:
79
@@ -85,31 +86,33 @@
85 <title>....</title>
86 <link rel="stylesheet" href="..." type="text/css" />
87 </head>
88 <body>
89
90 In most cases, it is best to leave the Fossil-generated HTML Header alone.
91 The configurable part of the skin begins with the Content Header section which
92 should followign the following template:
 
93
94 <div class="header">
95 ... top banner and menu bar ...
96 </div>
97
98 Note that `<div class="header">` and `</div>` tags must be included in the
99 Content Header text of the skin. In other words, you the administrator need
100 to supply that text as part of your skin customization.
 
101
102 The Fossil-generated Content section immediately follows the Content Header.
103 The Content section will looks like this:
104
105 <div class="content">
106 ... Fossil-generated content here ...
107 </div>
108
109 After the Content is the custom Content Footer section which should
110 following this template:
111
112 <div class="footer">
113 ... skin-specific stuff here ...
114 </div>
115 <script nonce="$nonce">
116
--- www/customskin.md
+++ www/customskin.md
@@ -68,12 +68,13 @@
68 <tr><td style='background-color:lightblue;text-align:center;'>Content Footer</td></tr>
69 <tr><td style='background-color:lightgreen;text-align:center;'>
70 Fossil-Generated HTML Footer</td></tr>
71 </tbody></table></blockquote>
72
73 The green parts are *usually* generated by Fossil. The blue parts
74 are things that you, the administrator, get to modify in order to
75 customize the skin.
76
77 Fossil *usually* (but not always - [see below](#override))
78 generates the initial HTML Header section of a page. The
79 generated HTML Header will look something like this:
80
@@ -85,31 +86,33 @@
86 <title>....</title>
87 <link rel="stylesheet" href="..." type="text/css" />
88 </head>
89 <body>
90
91 In most cases, it is best to leave the Fossil-generated HTML Header
92 alone. (One exception is when the administrator needs to include links
93 to additional CSS files.) The configurable part of the skin begins
94 with the Content Header section which should follow this template:
95
96 <div class="header">
97 ... top banner and menu bar ...
98 </div>
99
100 Note that `<div class="header">` and `</div>` tags must be included in
101 the Content Header text of the skin. In other words, you, the
102 administrator, need to supply that text as part of your skin
103 customization.
104
105 The Fossil-generated Content section immediately follows the Content Header.
106 The Content section will looks like this:
107
108 <div class="content">
109 ... Fossil-generated content here ...
110 </div>
111
112 After the Content is the custom Content Footer section which should
113 follow this template:
114
115 <div class="footer">
116 ... skin-specific stuff here ...
117 </div>
118 <script nonce="$nonce">
119
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -563,11 +563,11 @@
563563
subject for that thread. The argument to the <b>H</b> card is a string
564564
in the same format as a comment string in a <b>C</b> card.
565565
All follow-up posts have an <b>I</b> card that
566566
indicates which prior post in the same thread the current forum
567567
post is replying to, and a <b>G</b> card specifying the root post for
568
-the entire thread. The argument to G and <b>I</b> cards is the
568
+the entire thread. The argument to <b>G</b> and <b>I</b> cards is the
569569
artifact hash for the prior forum post to which the card refers.
570570
571571
In theory, it is sufficient for follow-up posts to have only an
572572
<b>I</b> card, since the <b>G</b> card value could be computed by following a
573573
chain of <b>I</b> cards. However, the <b>G</b> card is required in order to
574574
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -563,11 +563,11 @@
563 subject for that thread. The argument to the <b>H</b> card is a string
564 in the same format as a comment string in a <b>C</b> card.
565 All follow-up posts have an <b>I</b> card that
566 indicates which prior post in the same thread the current forum
567 post is replying to, and a <b>G</b> card specifying the root post for
568 the entire thread. The argument to G and <b>I</b> cards is the
569 artifact hash for the prior forum post to which the card refers.
570
571 In theory, it is sufficient for follow-up posts to have only an
572 <b>I</b> card, since the <b>G</b> card value could be computed by following a
573 chain of <b>I</b> cards. However, the <b>G</b> card is required in order to
574
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -563,11 +563,11 @@
563 subject for that thread. The argument to the <b>H</b> card is a string
564 in the same format as a comment string in a <b>C</b> card.
565 All follow-up posts have an <b>I</b> card that
566 indicates which prior post in the same thread the current forum
567 post is replying to, and a <b>G</b> card specifying the root post for
568 the entire thread. The argument to <b>G</b> and <b>I</b> cards is the
569 artifact hash for the prior forum post to which the card refers.
570
571 In theory, it is sufficient for follow-up posts to have only an
572 <b>I</b> card, since the <b>G</b> card value could be computed by following a
573 chain of <b>I</b> cards. However, the <b>G</b> card is required in order to
574
+2 -2
--- www/index.wiki
+++ www/index.wiki
@@ -84,14 +84,14 @@
8484
the repository are consistent prior to each commit.
8585
8686
8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
8787
8888
<hr>
89
-<h3>[/timeline?t=release|Latest Release]: 2.12.1 (2020-08-19)</h3>
89
+<h3>[https://www.fossil-scm.org/forum/info/a05ae3ce7760daf6|Latest Release]: 2.12.1 (2020-08-20)</h3>
9090
9191
* [/uv/download.html|Download]
92
- * [./changes.wiki#v2_12_1|Change Summary]
92
+ * [./changes.wiki#v2_12|Change Summary]
9393
9494
<hr>
9595
<h3>Quick Start</h3>
9696
9797
1. [/uv/download.html|Download] or install using a package manager or
9898
9999
ADDED www/interwiki.md
--- www/index.wiki
+++ www/index.wiki
@@ -84,14 +84,14 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>[/timeline?t=release|Latest Release]: 2.12.1 (2020-08-19)</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_12_1|Change Summary]
93
94 <hr>
95 <h3>Quick Start</h3>
96
97 1. [/uv/download.html|Download] or install using a package manager or
98
99 DDED www/interwiki.md
--- www/index.wiki
+++ www/index.wiki
@@ -84,14 +84,14 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>[https://www.fossil-scm.org/forum/info/a05ae3ce7760daf6|Latest Release]: 2.12.1 (2020-08-20)</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_12|Change Summary]
93
94 <hr>
95 <h3>Quick Start</h3>
96
97 1. [/uv/download.html|Download] or install using a package manager or
98
99 DDED www/interwiki.md
--- a/www/interwiki.md
+++ b/www/interwiki.md
@@ -0,0 +1,104 @@
1
+Interwiki_links)
2
+ * [](httInterwiki_links)
3
+
4
+Another example: The Fossil Forum is hosted in a separate repository
5
+from the Fossil source code. This page is part of the
6
+source code repository. Interwiki links can be used to more easily
7
+refer to the forum repository:
8
+
9
+ * [](forum:d5508c3bf44c6393df09c)
10
+ * [](https://fossil-scm.org/forum/info/d5508c3bf44c6393df09c)
11
+
12
+## Advantages Over Full URL Targets
13
+
14
+ * Interwiki links are easier to write. There is less typing,
15
+ and fewer op?cmd=ortunities to make mistakes.
16
+
17
+ * Interwiki links are easier to read. With well-chosen
18
+ intermap tags, the links are easier to understand.
19
+
20
+ * Interwiki links continue to work after a domain change on the
21
+ target. If the target of a link moves to a different domain,
22
+ an interwiki link will continue to work, if the intermap is adjusted,
23
+ but a hard-coded link will be permanently broken.
24
+
25
+ * Interwiki links allow clones to use a different target domain from the
26
+ original repository.
27
+
28
+## Details
29
+
30
+Fossil supports interwiki links in both the
31
+[Fossil Wiki](/wiki_rules) and [Markdown](/md_rules) markup
32
+styles. An interwiki link consists of a tag followed by a colon
33
+and the link target:
34
+
35
+> <i>Tag</i><b>:</b><i>PageName</i>
36
+
37
+The Tag must consist of ASCII alphanumeric characters only - no
38
+punctuation or whitespace or characters greater than U+007A.
39
+The PageName is the link notation on the target wiki.
40
+Th different classes of PageNames are recognized by Fossil:
41
+
42
+ 1. <b>Path Links</b> &rarr; the PageName begins with the "/" character
43
+ or is an empty string.
44
+
45
+ 2. <b>Hash Links</b> &rarr; the PageName is a hexadecimal number with
46
+ at least four digits.
47
+
48
+ n 3. <b>Wiki Links</b> &rarr; A PageName that is not a Path or Hash.
49
+
50
+The Intermap defiisg. Path links are appended
51
+directly to the URL contained in the Intermap. The Intermap can define
52
+additional text to put in between the base URL and the PageName for
53
+Hash and Wiki links, respectively.
54
+
55
+<a id="intermap"></a>
56
+## Intermap
57
+
58
+The intermap defines a mapping from interwiki Tags to full URLs. The
59
+Intermap can be viewed and managed using the [fossil s. fors) markup
60
+styles. An interwiki link consists of a tag followed by a colon
61
+and the link tanameet:
62
+
63
+> <i>Tag</i><b>:</b><i>PageName</i>
64
+
65
+The Tag must consist of ASCII alphanumeric characters only - no
66
+punctuation or whitespace or characters greater than U+007A.
67
+The PageName is the link notation on the targeiki link willhelp?cmd=/intermapki links in both the
68
+[Fossil Wiki](/wiki_rules) and [Markdown](/md_rules) markup
69
+styles. An interwiki link consists of a tag followed by a colon
70
+and the link target:
71
+
72
+> <i>Tag</i><b>:</b><i>PageName</i>
73
+
74
+The Tag must consist of ASCII alphanumeric characters only - no
75
+punctuation or whitespace or characters greater than U+007A.
76
+The PageName is the link notation on the target wiki.
77
+Three different classes of PageNames are recognized by Fossil:
78
+
79
+ 1. <b>Path Links</b> &rarr; the PageName begins with the "/" character
80
+ or is an empty string.
81
+
82
+ 2. <b>Hash Links</b> &rarr; the PageName is a hexadecimal number with
83
+ at least four digits.
84
+
85
+ n 3. <b>Wiki Links</b> &rarr; A PageName that is not a Path or Hash.
86
+
87
+The Intermap defines a base URL for each Tag. Path links are appended
88
+directly to the URL contained in the Intermap. The Intermap can define
89
+additional text to put in between the base URL and the PageName for
90
+Hash and Wiki links, respectively.
91
+
92
+<a id="intermap"></a>
93
+## Intermap
94
+
95
+The intermap defines a mapping from interwiki Tags to full URLs. The
96
+Intermap can be viewed and managed using the [fossil s. fors) markup
97
+styles. An interwiki link consists of a tag followed by a colon
98
+and the link tanameet:
99
+
100
+> <i>Tag</i><b>:</b><i>PageName</i>
101
+
102
+The Tag must consist of ASCII alphanumeric characters only - no
103
+punctuation or whitespace or characters greater than U+007A.
104
+The PageName is the link notation on knowing scanning the source
--- a/www/interwiki.md
+++ b/www/interwiki.md
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/www/interwiki.md
+++ b/www/interwiki.md
@@ -0,0 +1,104 @@
1 Interwiki_links)
2 * [](httInterwiki_links)
3
4 Another example: The Fossil Forum is hosted in a separate repository
5 from the Fossil source code. This page is part of the
6 source code repository. Interwiki links can be used to more easily
7 refer to the forum repository:
8
9 * [](forum:d5508c3bf44c6393df09c)
10 * [](https://fossil-scm.org/forum/info/d5508c3bf44c6393df09c)
11
12 ## Advantages Over Full URL Targets
13
14 * Interwiki links are easier to write. There is less typing,
15 and fewer op?cmd=ortunities to make mistakes.
16
17 * Interwiki links are easier to read. With well-chosen
18 intermap tags, the links are easier to understand.
19
20 * Interwiki links continue to work after a domain change on the
21 target. If the target of a link moves to a different domain,
22 an interwiki link will continue to work, if the intermap is adjusted,
23 but a hard-coded link will be permanently broken.
24
25 * Interwiki links allow clones to use a different target domain from the
26 original repository.
27
28 ## Details
29
30 Fossil supports interwiki links in both the
31 [Fossil Wiki](/wiki_rules) and [Markdown](/md_rules) markup
32 styles. An interwiki link consists of a tag followed by a colon
33 and the link target:
34
35 > <i>Tag</i><b>:</b><i>PageName</i>
36
37 The Tag must consist of ASCII alphanumeric characters only - no
38 punctuation or whitespace or characters greater than U+007A.
39 The PageName is the link notation on the target wiki.
40 Th different classes of PageNames are recognized by Fossil:
41
42 1. <b>Path Links</b> &rarr; the PageName begins with the "/" character
43 or is an empty string.
44
45 2. <b>Hash Links</b> &rarr; the PageName is a hexadecimal number with
46 at least four digits.
47
48 n 3. <b>Wiki Links</b> &rarr; A PageName that is not a Path or Hash.
49
50 The Intermap defiisg. Path links are appended
51 directly to the URL contained in the Intermap. The Intermap can define
52 additional text to put in between the base URL and the PageName for
53 Hash and Wiki links, respectively.
54
55 <a id="intermap"></a>
56 ## Intermap
57
58 The intermap defines a mapping from interwiki Tags to full URLs. The
59 Intermap can be viewed and managed using the [fossil s. fors) markup
60 styles. An interwiki link consists of a tag followed by a colon
61 and the link tanameet:
62
63 > <i>Tag</i><b>:</b><i>PageName</i>
64
65 The Tag must consist of ASCII alphanumeric characters only - no
66 punctuation or whitespace or characters greater than U+007A.
67 The PageName is the link notation on the targeiki link willhelp?cmd=/intermapki links in both the
68 [Fossil Wiki](/wiki_rules) and [Markdown](/md_rules) markup
69 styles. An interwiki link consists of a tag followed by a colon
70 and the link target:
71
72 > <i>Tag</i><b>:</b><i>PageName</i>
73
74 The Tag must consist of ASCII alphanumeric characters only - no
75 punctuation or whitespace or characters greater than U+007A.
76 The PageName is the link notation on the target wiki.
77 Three different classes of PageNames are recognized by Fossil:
78
79 1. <b>Path Links</b> &rarr; the PageName begins with the "/" character
80 or is an empty string.
81
82 2. <b>Hash Links</b> &rarr; the PageName is a hexadecimal number with
83 at least four digits.
84
85 n 3. <b>Wiki Links</b> &rarr; A PageName that is not a Path or Hash.
86
87 The Intermap defines a base URL for each Tag. Path links are appended
88 directly to the URL contained in the Intermap. The Intermap can define
89 additional text to put in between the base URL and the PageName for
90 Hash and Wiki links, respectively.
91
92 <a id="intermap"></a>
93 ## Intermap
94
95 The intermap defines a mapping from interwiki Tags to full URLs. The
96 Intermap can be viewed and managed using the [fossil s. fors) markup
97 styles. An interwiki link consists of a tag followed by a colon
98 and the link tanameet:
99
100 > <i>Tag</i><b>:</b><i>PageName</i>
101
102 The Tag must consist of ASCII alphanumeric characters only - no
103 punctuation or whitespace or characters greater than U+007A.
104 The PageName is the link notation on knowing scanning the source
+443 -129
--- www/javascript.md
+++ www/javascript.md
@@ -1,28 +1,28 @@
11
# Use of JavaScript in Fossil
22
3
-## Philosophy
3
+## Philosophy & Policy
44
55
The Fossil development project’s policy is to use JavaScript where it
66
helps make its web UI better, but to offer graceful fallbacks wherever
7
-practical. The intent is that the UI be usable with JavaScript entirely
8
-disabled. In every place where Fossil uses JavaScript, it is an
9
-enhancement to provided functionality, and there is always another way
10
-to accomplish a given end without using JavaScript.
7
+practical. The intent is that the UI be usable with JavaScript
8
+entirely disabled. In almost all places where Fossil uses JavaScript,
9
+it is an enhancement to provided functionality, and there is always
10
+another way to accomplish a given end without using JavaScript.
1111
1212
This is not to say that Fossil’s fall-backs for such cases are always as
1313
elegant and functional as a no-JS purist might wish. That is simply
14
-because [the vast majority of web users run with JS enabled](#stats),
15
-and a minority of those run with some kind of conditional JavaScript
16
-blocking in place. Fossil’s active developers do not deviate from that
14
+because [the vast majority of web users run with JavaScript enabled](#stats),
15
+and a minority of those run with some kind of [conditional JavaScript
16
+blocking](#block) in place. Fossil’s active developers do not deviate from that
1717
norm enough that we have many no-JS purists among us, so the no-JS case
1818
doesn’t get as much attention as some might want. We do [accept code
1919
contributions][cg], and we are philosophically in favor of graceful
2020
fall-backs, so you are welcome to appoint yourself the position of no-JS
2121
czar for the Fossil project!
2222
23
-Evil is in actions, not in nouns, so we do not believe JavaScript *can*
23
+Evil is in actions, not in nouns: we do not believe JavaScript *can*
2424
be evil. It is an active technology, but the actions that matter here
2525
are those of writing the code and checking it into the Fossil project
2626
repository. None of the JavaScript code in Fossil is evil, a fact we
2727
enforce by being careful about who we give check-in rights on the
2828
repository to and by policing what code does get contributed. The Fossil
@@ -30,98 +30,264 @@
3030
3131
We think it’s better to ask not whether Fossil requires JavaScript but
3232
whether Fossil uses JavaScript *well*, so that [you can decide](#block)
3333
to block or allow Fossil’s use of JavaScript.
3434
35
+The Fossil developers want to see the project thrive, and we achieve
36
+that best by making it usable and friendly to a wider audience than the
37
+minority of static web app purists. Modern users generally expect a
38
+smoother experience than was available with 1990s style HTTP
39
+POST-and-response `<form>` based interaction. We also increase the set
40
+of potential Fossil developers if we do not restrict them to such
41
+antiquated methods.
42
+
43
+JavaScript is not perfect, but it's what we have, so we will use it
44
+where we find it advantageous.
45
+
3546
[cg]: ./contribute.wiki
3647
3748
38
-## <a id="block"></a>Blocking JavaScript
39
-
40
-Rather than either block JavaScript wholesale or give up on blocking
41
-JavaScript entirely, we recommend that you use tools like [NoScript][ns]
42
-or [uBlock Origin][ub] to selectively block problematic uses of
43
-JavaScript so the rest of the web can use the technology productively,
44
-as it was intended. There are doubtless other useful tools of this sort;
45
-we recommend only these two due to our limited experience, not out of
46
-any wish to exclude other tools.
47
-
48
-The primary difference between these two for our purposes is that
49
-NoScript lets you select scripts to run on a page on a case-by-case
50
-basis, whereas uBlock Origin delegates those choices to a group of
51
-motivated volunteers who maintain whitelists and blacklists to control
52
-all of this; you can then override UBO’s stock rules as needed.
53
-
54
-[ns]: https://noscript.net/
55
-[ub]: https://github.com/gorhill/uBlock/
56
-
57
-
58
-## <a id="stats"></a>How Many Users Run with JavaScript Disabled Anyway?
59
-
60
-There are several studies that have directly measured the web audience
61
-to answer this question:
62
-
63
-* [What percentage of browsers with javascript disabled?][s1]
64
-* [How many people are missing out on JavaScript enhancement?][s2]
65
-* [Just how many web users really disable cookies or JavaScript?][s3]
66
-
67
-Our sense of this data is that only about 0.2% of web users had
68
-JavaScript disabled while participating in these studies.
69
-
70
-The Fossil user community is not typical of the wider web, but if we
71
-were able to comprehensively survey our users, we’d expect to find an
72
-interesting dichotomy. Because Fossil is targeted at software
73
-developers, who in turn are more likely to be power-users, we’d expect
74
-to find Fossil users to be more in favor of some amount of JavaScript
75
-blocking than the average web user. Yet, we’d also expect to find that
76
-our user base has a disproportionately high number who run [powerful
77
-conditional blocking plugins](#block) in their browsers, rather than
78
-block JS entirely. We suspect that between these two forces, the number
79
-of no-JS purists among Fossil’s user base is still a tiny minority.
80
-
81
-[s1]: https://blockmetry.com/blog/javascript-disabled
82
-[s2]: https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
83
-[s3]: https://w3techs.com/technologies/overview/client_side_language/all
84
-
85
-
86
-## <a id="3pjs"></a>No Third-Party JavaScript in Fossil
87
-
88
-Fossil does not use any third-party JavaScript libraries, not even very
89
-common ones like jQuery. Every bit of JavaScript served by the stock
90
-version of Fossil was written specifically for the Fossil project and is
91
-stored [in its code repository](https://fossil-scm.org/fossil/file).
92
-
93
-Therefore, if you want to hack on the JavaScript code served by Fossil
94
-and mechanisms like [skin editing][cs] don’t suffice for your purposes,
95
-you can hack on the JavaScript in your local instance directly, just as
96
-you can hack on its C, SQL, and Tcl code. Fossil is free and open source
97
-software, under [a single license][2cbsd].
98
-
99
-[2cbsd]: https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt
100
-[cs]: ./customskin.md
101
-
102
-
103
-## <a id="snoop"></a>Fossil Does Not Snoop On You
104
-
105
-There is no tracking or other snooping technology in Fossil other than
106
-that necessary for basic security, such as IP address logging on
107
-check-ins. (This is in part why we have no [comprehensive user
108
-statistics](#stats)!)
109
-
110
-Fossil attempts to set two cookies on all web clients: a login session
111
-cookie and a display preferences cookie. These cookies are restricted to
112
-the Fossil instance, so even this limited data cannot leak between
113
-Fossil instances or into other web sites.
114
-
115
-There is some server-side event logging, but that is done entirely
116
-without JavaScript, so it’s off-topic here.
117
-
49
+## <a id="debate"></a>Arguments Against JavaScript & Our Rebuttals
50
+
51
+There are many common arguments against the use of JavaScript. Rather than
52
+rehash these same arguments on the [forum][ffor], we distill the common
53
+ones we’ve heard before and give our stock answers to them here:
54
+
55
+1. “**It increases the size of the page download.**”
56
+
57
+ The heaviest such pages served by Fossil only have about 8 kB of
58
+ compressed JavaScript. (You have to go out of your way to get Fossil
59
+ to serve uncompressed pages.) This is negligible, even over very
60
+ slow data connections. If you are still somehow on a 56 kbit/sec
61
+ analog telephone modem, this extra script code would download in
62
+ about a second.
63
+
64
+ Most JavaScript-based Fossil pages use less code than that.
65
+
66
+ Atop that, Fossil 2.12 adds new script delivery methods with
67
+ aggressive caching enabled so that typical page loads will skip
68
+ re-loading this content on subsequent loads. These features are
69
+ currently optional: you must either set the new [`fossil server
70
+ --jsmode` option][fsrv] or the corresponding `jsmode` control line
71
+ in your [`fossil cgi`][fcgi] script when setting up your
72
+ [Fossil server][fshome]. That done, Fossil’s JavaScript files will
73
+ load almost instantly from the browser’s cache after the initial
74
+ page load, rather than be re-transferred over the network.
75
+
76
+ Between the improved caching and the fact that it’s quicker to
77
+ transfer a partial Ajax page load than reload the entire page, the
78
+ aggregate cost of such pages is typically *lower* than the older
79
+ methods based on HTTP POST with a full server round-trip. You can
80
+ expect to recover the cost of the initial page load in 1-2
81
+ round-trips. If we were to double the amount of JavaScript code in
82
+ Fossil, the payoff time would increase to 2-4 round-trips.
83
+
84
+2. “**JavaScript is slow.**”
85
+
86
+ It *was*, before September 2008. Google's introduction of [their V8
87
+ JavaScript engine][v8] taught the world that JavaScript need not be
88
+ slow. This competitive pressure caused the other common JavaScript
89
+ interpreters to either improve or be replaced by one of the engines
90
+ that did improve to approach V8’s speed.
91
+
92
+ Nowadays JavaScript is, as a rule, astoundingly fast. As the world
93
+ continues to move more and more to web-based applications and
94
+ services, JavaScript engine developers have ample motivation to keep
95
+ their engines fast and competitive.
96
+
97
+ Once the scripts are cached, Ajax based page updates are faster than
98
+ the alternative, a full HTTP POST round-trip.
99
+
100
+3. <a id="3pjs"></a>“**Third-party JavaScript cannot be trusted.**”
101
+
102
+ Fossil does not use any third-party JavaScript libraries, not even
103
+ very common ones like jQuery. Every bit of JavaScript served by the
104
+ stock version of Fossil was written specifically for the Fossil
105
+ project and is stored [in its code repository][fsrc].
106
+
107
+ Therefore, if you want to hack on the JavaScript code served by
108
+ Fossil and mechanisms like [skin editing][cskin] don’t suffice for your
109
+ purposes, you can hack on the JavaScript in your local instance
110
+ directly, just as you can hack on its C, SQL, and Tcl code. Fossil
111
+ is free and open source software, under [a single license][2cbsd].
112
+
113
+4. <a id="snoop"></a>“**JavaScript and cookies are used to snoop on web users.**”
114
+
115
+ There is no tracking or other snooping technology in Fossil other than
116
+ that necessary for basic security, such as IP address logging on
117
+ check-ins. (This is in part why we have no [comprehensive user
118
+ statistics](#stats)!)
119
+
120
+ Fossil attempts to set two cookies on all web clients: a login session
121
+ cookie and a display preferences cookie. These cookies are restricted to
122
+ the Fossil instance, so even this limited data cannot leak between
123
+ Fossil instances or into other web sites.
124
+
125
+5. “**JavaScript is fundamentally insecure.**”
126
+
127
+ JavaScript is certainly sometimes used for nefarious ends, but if we
128
+ wish to have more features in Fossil, the alternative is to add more
129
+ code to the Fossil binary, [most likely in C][fslpl], a language
130
+ implicated in [over 4× more security vulnerabilities][whmsl].
131
+
132
+ Therefore, does it not make sense to place approximately four times
133
+ as much trust in Fossil’s JavaScript code as in its C code?
134
+
135
+ The question is not whether JavaScript is itself evil, it is whether
136
+ its *authors* are evil. *Every byte* of JavaScript code used within
137
+ the Fossil UI is:
138
+
139
+ * ...written by the Fossil developers, vetted by their peers.
140
+
141
+ * ...[open source][flic] and [available][fsrc] to be inspected,
142
+ audited, and changed by its users.
143
+
144
+ * ...compiled directly into the `fossil` binary in a
145
+ non-obfuscated form during the build process, so there are no
146
+ third-party servers delivering mysterious, obfuscated JavaScript
147
+ code blobs to the user.
148
+
149
+ Local administrators can [modify the repository’s skin][cskin] to
150
+ inject additional JavaScript code into pages served by their Fossil
151
+ server. A typical case is to add a syntax highlighter like
152
+ [Prism.js][pjs] or [highlightjs][hljs] to the local repository. At
153
+ that point, your trust concern is not with Fossil’s use of
154
+ JavaScript, but with your trust in that repository’s administrator.
155
+
156
+ Fossil's [default content security policy][dcsp] (CSP)
157
+ prohibits execution of JavaScript code which is delivered from
158
+ anywhere but the Fossil server which delivers the page. A local
159
+ administrator can change this CSP, but again this comes down to a
160
+ matter of trust with the administrator, not with Fossil itself.
161
+
162
+6. “**Cross-browser compatibility is poor.**”
163
+
164
+ It most certainly was in the first decade or so of JavaScript’s
165
+ lifetime, resulting in the creation of powerful libraries like
166
+ jQuery to patch over the incompatibilities. Over time, the need for
167
+ such libraries has dropped as browser vendors have fixed the
168
+ incompatibilities. Cross-browser JavaScript compatibility issues
169
+ which affect web developers are, by and large, a thing of the past.
170
+
171
+7. “**Fossil UI works fine today without JavaScript. Why break it?**”
172
+
173
+ While this is true today, and we have no philosophical objection to
174
+ it remaining true, we do not intend to limit ourselves to only those
175
+ features that can be created without JavaScript. The mere
176
+ availability of alternatives is not a good justification for holding
177
+ back on notable improvements when they're within easy reach.
178
+
179
+ The no-JS case is a [minority position](#stats), so those that want
180
+ Fossil to have no-JS alternatives and graceful fallbacks will need
181
+ to get involved with the development if they want this state of
182
+ affairs to continue.
183
+
184
+8. <a id="stats"></a>“**A large number of users run without JavaScript enabled.**”
185
+
186
+ That’s not what web audience measurements say:
187
+
188
+ * [What percentage of browsers with javascript disabled?][s1]
189
+ * [How many people are missing out on JavaScript enhancement?][s2]
190
+ * [Just how many web users really disable cookies or JavaScript?][s3]
191
+
192
+ Our sense of this data is that only about 0.2% of web users had
193
+ JavaScript disabled while participating in these studies.
194
+
195
+ The Fossil user community is not typical of the wider web, but if we
196
+ were able to comprehensively survey our users, we’d expect to find
197
+ an interesting dichotomy. Because Fossil is targeted at software
198
+ developers, who in turn are more likely to be power-users, we’d
199
+ expect to find Fossil users to be more in favor of some amount of
200
+ JavaScript blocking than the average web user. Yet, we’d also expect
201
+ to find that our user base has a disproportionately high number who
202
+ run [powerful conditional blocking plugins](#block) in their
203
+ browsers, rather than block JavaScript entirely. We suspect that
204
+ between these two forces, the number of no-JS purists among Fossil’s
205
+ user base is still a tiny minority.
206
+
207
+9. <a id="block"></a>“**I block JavaScript entirely in my browser. That breaks Fossil.**”
208
+
209
+ First, see our philosophy statements above. Briefly, we intend that
210
+ there always be some other way to get any given result without using
211
+ JavaScript, developer interest willing.
212
+
213
+ But second, it doesn’t have to be all-or-nothing. We recommend that
214
+ those interested in blocking problematic uses of JavaScript use
215
+ tools like [NoScript][ns] or [uBlock Origin][ubo] to *selectively*
216
+ block JavaScript so the rest of the web can use the technology
217
+ productively, as it was intended.
218
+
219
+ There are doubtless other useful tools of this sort. We recommend
220
+ these two only from our limited experience, not out of any wish to
221
+ exclude other tools.
222
+
223
+ The primary difference between these two for our purposes is that
224
+ NoScript lets you select scripts to run on a page on a case-by-case
225
+ basis, whereas uBlock Origin delegates those choices to a group of
226
+ motivated volunteers who maintain allow/block lists to control all
227
+ of this; you can then override UBO’s stock rules as needed.
228
+
229
+10. “**My browser doesn’t even *have* a JavaScript interpreter.**”
230
+
231
+ The Fossil open source project has no full-time developers, and only
232
+ a few of these part-timers are responsible for the bulk of the code
233
+ in Fossil. If you want Fossil to support such niche use cases, then
234
+ you will have to [get involved with its development][cg]: it’s
235
+ *your* uncommon itch.
236
+
237
+11. <a id="compat"></a>“**Fossil’s JavaScript code isn’t compatible with my browser.**”
238
+
239
+ The Fossil project’s developers aim to remain compatible with
240
+ the largest portions of the client-side browser base. We use only
241
+ standards-defined JavaScript features which are known to work in the
242
+ overwhelmingly vast majority of browsers going back approximately 5
243
+ years, at minimum, as documented by [Can I Use...?][ciu] We avoid use of
244
+ features added to the language more recently or those which are still in
245
+ flux in standards committees.
246
+
247
+ We set this threshold based on the amount of time it typically takes for
248
+ new standards to propagate through the installed base.
249
+
250
+ As of this writing, this means we are only using features defined in
251
+ [ECMAScript 2015][es2015], colloquially called “JavaScript 6.” That
252
+ is a sufficiently rich standard that it more than suffices for our
253
+ purposes, and it is [widely deployed][es6dep]. The biggest single
254
+ outlier remaining is MSIE 11, and [even Microsoft is moving their
255
+ own products off of it][ie11x].
256
+
257
+[2cbsd]: https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt
258
+[ciu]: https://caniuse.com/
259
+[cskin]: ./customskin.md
260
+[dcsp]: ./defcsp.md
261
+[es2015]: https://ecma-international.org/ecma-262/6.0/
262
+[es6dep]: https://caniuse.com/#feat=es6
263
+[fcgi]: /help?cmd=cgi
264
+[ffor]: https://fossil-scm.org/forum/
265
+[flic]: /doc/trunk/COPYRIGHT-BSD2.txt
266
+[fshome]: /doc/trunk/www/server/
267
+[fslpl]: /doc/trunk/www/fossil-v-git.wiki#portable
268
+[fsrc]: https://fossil-scm.org/home/file/src
269
+[fsrv]: /help?cmd=server
270
+[hljs]: https://fossil-scm.org/forum/forumpost/9150bc22ca
271
+[ie11x]: https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666
272
+[ns]: https://noscript.net/
273
+[pjs]: https://fossil-scm.org/forum/forumpost/1198651c6d
274
+[s1]: https://blockmetry.com/blog/javascript-disabled
275
+[s2]: https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
276
+[s3]: https://w3techs.com/technologies/overview/client_side_language/all
277
+[ubo]: https://github.com/gorhill/uBlock/
278
+[v8]: https://en.wikipedia.org/wiki/V8_(JavaScript_engine)
279
+[whmsl]: https://www.whitesourcesoftware.com/most-secure-programming-languages/
280
+
281
+
282
+----
118283
119284
## <a id="uses"></a>Places Where Fossil’s Web UI Uses JavaScript
120285
121
-The remainder of this document will explain how Fossil currently uses
122
-JavaScript and what it does when these uses are blocked.
286
+This section documents the areas where Fossil currently uses JavaScript
287
+and what it does when these uses are blocked. It also gives common
288
+workarounds where necessary.
123289
124290
125291
### <a id="timeline"></a>Timeline Graph
126292
127293
Fossil’s [web timeline][wt] uses JavaScript to render the graph
@@ -140,45 +306,144 @@
140306
command, by clicking around within the web UI, etc.
141307
142308
_Potential Workaround:_ The timeline could be enhanced with `<noscript>`
143309
tags that replace the graph with a column of checkboxes that control
144310
what a series of form submit buttons do when clicked, replicating the
145
-current JS-based features of the graph using client-server round-trips.
311
+current JavaScript-based features of the graph using client-server round-trips.
146312
For example, you could click two of those checkboxes and then a button
147313
labeled “Diff Selected” to replicate the current “click two nodes to
148314
diff them” feature.
149315
150316
[wt]: https://fossil-scm.org/fossil/timeline
151317
152318
153
-### <a id="wedit"></a>WYSIWYG Wiki Editor
154
-
155
-The Admin → Wiki → “Enable WYSIWYG Wiki Editing” toggle switches the
156
-default plaintext editor for [Fossil wiki][fw] documents to one that
157
-works like a basic word processor. This feature requires JavaScript in
158
-order to react to editor button clicks like the “**B**” button, meaning
159
-“make \[selected\] text boldface.” There is no standard WYSIWYG editor
160
-component in browsers, doubtless because it’s relatively straightforward
161
-to create one using JavaScript.
162
-
163
-_Graceful Fallback:_ Edit your wiki documents in the default plain text
164
-wiki editor. Fossil’s wiki and Markdown language processors were
165
-designed to be edited that way.
166
-
167
-[fw]: ./wikitheory.wiki
319
+### <a id="wedit"></a>The New Wiki Editor
320
+
321
+The [new wiki editor][fwt] added in Fossil 2.12 has many new features, a
322
+few of which are impossible to get without use of JavaScript.
323
+
324
+First, it allows in-browser previews without losing client-side editor
325
+state, such as where your cursor is. With the old editor, you had to
326
+re-locate the place you were last editing on each preview, which would
327
+reduce the incentive to use the preview function. In the new wiki
328
+editor, you just click the Preview tab to see how Fossil interprets your
329
+markup, then click back to the Editor tab to resume work with the prior
330
+context undisturbed.
331
+
332
+Second, it continually saves your document state in client-side storage
333
+in the background while you’re editing it so that if the browser closes
334
+without saving the changes back to the Fossil repository, you can resume
335
+editing from the stored copy without losing work. This feature is not so
336
+much about saving you from crashes of various sorts, since computers are
337
+so much more reliable these days. It is far more likely to save you from
338
+the features of mobile OSes like Android and iOS which aggressively shut
339
+down and restart apps to save on RAM. That OS design philosophy assumes
340
+that there is a way for the app to restore its prior state from
341
+persistent media when it’s restarted, giving the illusion that it was
342
+never shut down in the first place. This feature of Fossil’s new wiki
343
+editor provides that.
344
+
345
+With this change, we lost the old WYSIWYG wiki editor, available since
346
+Fossil version 1.24. It hadn’t been maintained for years, it was
347
+disabled by default, and no one stepped up to defend its existence when
348
+this new editor was created, replacing it. If someone rescues that
349
+feature, merging it in with the new editor, it will doubtless require
350
+JavaScript in order to react to editor button clicks like the “**B**”
351
+button, meaning “make \[selected\] text boldface.” There is no standard
352
+WYSIWYG editor component in browsers, doubtless because it’s relatively
353
+straightforward to create one using JavaScript.
354
+
355
+_Graceful Fallback:_ Unlike in the Fossil 2.11 and earlier days, there
356
+is no longer a script-free wiki editor mode. This is not from lack of
357
+desire, only because the person who wrote the new wiki editor didn’t
358
+want to maintain three different editors. (New Ajaxy editor, old
359
+script-free HTML form based editor, and the old WYSIWYG JavaScript-based
360
+editor.) If someone wants to implement a `<noscript>` alternative to the
361
+new wiki editor, we will likely accept that [contribution][cg] as long
362
+as it doesn’t interfere with the new editor. (The same goes for adding
363
+a WYSIWYG mode to the new Ajaxy wiki editor.)
364
+
365
+_Workaround:_ You don’t have to use the browser-based wiki editor to
366
+maintain your repository’s wiki at all. Fossil’s [`wiki` command][fwc]
367
+lets you manipulate wiki documents from the command line. For example,
368
+consider this Vi based workflow:
369
+
370
+```shell
371
+ $ vi 'My Article.wiki' # begin work on new article
372
+ ...write, write, write...
373
+ :w # save changes to disk copy
374
+ :!fossil wiki create 'My Article' '%' # current file (%) to new article
375
+ ...write, write, write some more...
376
+ :w # save again
377
+ :!fossil wiki commit 'My Article' '%' # update article from disk
378
+ :q # done writing for today
379
+
380
+ ....days later...
381
+ $ vi # work sans named file today
382
+ :r !fossil wiki export 'My Article' - # pull article text into vi buffer
383
+ ...write, write, write yet more...
384
+ :w !fossil wiki commit - # vi buffer updates article
385
+```
386
+
387
+Extending this concept to other text editors is an exercise left to the
388
+reader.
389
+
390
+[fwc]: /help?cmd=wiki
391
+[fwt]: ./wikitheory.wiki
392
+
393
+
394
+### <a id="fedit"></a>The File Editor
395
+
396
+Fossil 2.12 adds the [optional file editor feature][fedit], which works
397
+much like [the new wiki editor](#wedit), only on files committed to the
398
+repository.
399
+
400
+The original designed purpose for this feature is to allow [embedded
401
+documentation][edoc] to be interactively edited in the same way that
402
+wiki articles can be. (Indeed, the associated `fileedit-glob` feature
403
+allows you to restrict the editor to working *only* on files that can be
404
+treated as embedded documentation.) This feature operates in much the
405
+same way as the new wiki editor, so most of what we said above applies.
406
+
407
+_Workaround:_ This feature is an alternative to Fossil’s traditional
408
+mode of file management: clone the repository, open it somewhere, edit a
409
+file locally, and commit the changes.
410
+
411
+_Graceful Fallback:_ There is no technical reason why someone could not
412
+write a `<noscript>` wrapped alternative to the current JavaScript based
413
+`/fileedit` implementation. It would have all of the same downsides as
414
+the old wiki editor: the users would lose their place on each save, they
415
+would have no local backup if something crashes, etc. Still, we are
416
+likely to accept such a [contribution][cg] as long as it doesn’t
417
+interfere with the new editor.
418
+
419
+[edoc]: /doc/trunk/www/embeddeddoc.wiki
420
+[fedit]: /doc/trunk/www/fileedit-page.md
168421
169422
170423
### <a id="ln"></a>Line Numbering
171424
172425
When viewing source files, Fossil offers to show line numbers in some
173
-cases. Toggling them on and off is currently handled in JavaScript.
174
-([Example][mainc].)
426
+cases. ([Example][mainc].) Toggling them on and off is currently handled
427
+in JavaScript, rather than forcing a page-reload via a button click.
428
+
429
+_Workaround:_ Manually edit the URL to give the “`ln`” query parameter
430
+per [the `/file` docs](/help?cmd=/file).
431
+
432
+_Potential Better Workaround:_ Someone sufficiently interested could
433
+[provide a patch][cg] to add a `<noscript>` wrapped HTML button that
434
+would reload the page with this parameter included/excluded to implement
435
+the toggle via a server round-trip.
436
+
437
+As of Fossil 2.12, there is also a JavaScript-based interactive method
438
+for selecting a range of lines by clicking the line numbers when they’re
439
+visible, then copying the resulting URL to share your selection with
440
+others.
175441
176
-_Workaround:_ Edit the URL to give the “`ln`” query parameter per [the
177
-`/file` docs](/help?cmd=/file), or provide a patch to reload the page
178
-with this parameter included/excluded to implement the toggle via a
179
-server round-trip.
442
+_Workaround:_ These interactive features would be difficult and
443
+expensive (in terms of network I/O) to implement without JavaScript. A
444
+far simpler alternative is to manually edit the URL, per above.
180445
181446
[mainc]: https://fossil-scm.org/fossil/artifact?ln&name=87d67e745
182447
183448
184449
### <a id="sxsdiff"></a>Side-by-Side Diff Mode
@@ -224,12 +489,12 @@
224489
similar, hovering over that check-in shows a tooltip with details about
225490
the type of artifact the hash refers to and allows you to click to copy
226491
the hash to the clipboard.
227492
228493
_Graceful Fallback:_ When JavaScript is disabled, these tooltips simply
229
-don’t appear. You can then select and copy the hash using your browser,
230
-make “`fossil info`” queries on those hashes, etc.
494
+don’t appear, but you can still select and copy the hash using your
495
+platform’s “copy selected text” feature.
231496
232497
233498
### <a id="bots"></a>Anti-Bot Defenses
234499
235500
Fossil has [anti-bot defenses][abd], and it has some JavaScript code
@@ -254,26 +519,75 @@
254519
255520
_Graceful Fallback:_ Clicking the hamburger menu button with JavaScript
256521
disabled will take you to the `/sitemap` page instead of showing a
257522
simplified version of that page’s content in a drop-down.
258523
259
-_Workaround:_ You can remove this button by [editing the skin][cs]
524
+_Workaround:_ You can remove this button by [editing the skin][cskin]
260525
header.
261526
262527
263528
### <a id="clock"></a>Clock
264529
265530
Some stock Fossil skins include JavaScript-based features such as the
266531
current time of day. The Xekri skin includes this in its header, for
267
-example. A clock feature requires JavaScript not only to get the time
268
-and update inline on the page once a minute, but also so it displays *in
269
-the local time zone.*
270
-
271
-Since none of this code provides a necessary Fossil feature, the core
272
-developers are unlikely to try to make these features work better in the
273
-absence of JavaScript.
274
-
275
-However, we are willing to study patches to make this better. For
276
-example, the wall clock displays could include the page load time in the
277
-dynamically generated HTML shipped from the remote Fossil server, so
278
-that in the absence of JavaScript, you at least get the page generation
279
-time, expressed in the server’s time zone.
532
+example. A clock feature requires JavaScript to get the time on initial
533
+page load and then to update it once a minute.
534
+
535
+You may observe that the server could provide the current time when
536
+generating the page, but the client and server may not be in the same
537
+time zone, and there is no reliably-provided information from the client
538
+that would let the server give the page load time in the client’s local
539
+time zone. The server could only tell you *its* local time at page
540
+request time, not the client’s time. That still wouldn’t be a “clock,”
541
+since without client-side JavaScript code running, that part of the page
542
+couldn’t update once a second.
543
+
544
+_Potential Graceful Fallback:_ You may consider showing the server’s
545
+page generation time rather than the client’s wall clock time in the
546
+local time zone to be a useful fallback for the current feature, so [a
547
+patch to do this][cg] may well be accepted. Since this is not a
548
+*necessary* Fossil feature, an interested user is unlikely to get the
549
+core developers to do this work for them.
550
+
551
+----
552
+
553
+## <a id="future"></a>Future Plans for JavaScript in Fossil
554
+
555
+As of mid-2020, the informal provisional plan is to increase Fossil
556
+UI's use of JavaScript considerably compared to its historically minimal
557
+uses. To that end, a framework of Fossil-centric APIs is being developed
558
+in conjunction with new features to consolidate Fossil's historical
559
+hodgepodge of JavaScript snippets into a coherent code base.
560
+
561
+When deciding which features to port to JavaScript, the rules of thumb
562
+for this ongoing effort are:
563
+
564
+- Pages which primarily display data (e.g. the timeline) will remain
565
+ largely static HTML with graceful fallbacks for all places they do
566
+ use JavaScript. Though JavaScript can be used effectively to power
567
+ all sorts of wonderful data presentation, Fossil currently doesn't
568
+ benefit greatly from doing so. We use JavaScript on these pages only
569
+ to improve their usability, not to define their primary operations.
570
+
571
+- Pages which act as editors of some sort (e.g. the `/info` page) are
572
+ prime candidates for getting the same treatment as the old wiki
573
+ editor: reimplemented from the ground up in JavaScript using Ajax
574
+ type techniques. Similarly, a JS-driven overhaul is planned for the
575
+ forum’s post editor.
576
+
577
+These are guidelines, not immutable requirements. Our development
578
+direction is guided by our priorities:
579
+
580
+1) Features the developers themselves want to have and/or work on.
581
+
582
+2) Features end users request which catch the interest of one or more
583
+developers, provided the developer(s) in question are in a position to
584
+expend the effort.
585
+
586
+3) Features end users and co-contributors can convince a developer into
587
+coding even when they really don't want to. 😉
588
+
589
+In all of this, Fossil's project lead understandably has the final
590
+say-so in whether any given feature indeed gets merged into the mainline
591
+trunk. Development of any given feature, no matter how much effort was
592
+involved, does not guarantee its eventual inclusion into the public
593
+releases.
280594
--- www/javascript.md
+++ www/javascript.md
@@ -1,28 +1,28 @@
1 # Use of JavaScript in Fossil
2
3 ## Philosophy
4
5 The Fossil development project’s policy is to use JavaScript where it
6 helps make its web UI better, but to offer graceful fallbacks wherever
7 practical. The intent is that the UI be usable with JavaScript entirely
8 disabled. In every place where Fossil uses JavaScript, it is an
9 enhancement to provided functionality, and there is always another way
10 to accomplish a given end without using JavaScript.
11
12 This is not to say that Fossil’s fall-backs for such cases are always as
13 elegant and functional as a no-JS purist might wish. That is simply
14 because [the vast majority of web users run with JS enabled](#stats),
15 and a minority of those run with some kind of conditional JavaScript
16 blocking in place. Fossil’s active developers do not deviate from that
17 norm enough that we have many no-JS purists among us, so the no-JS case
18 doesn’t get as much attention as some might want. We do [accept code
19 contributions][cg], and we are philosophically in favor of graceful
20 fall-backs, so you are welcome to appoint yourself the position of no-JS
21 czar for the Fossil project!
22
23 Evil is in actions, not in nouns, so we do not believe JavaScript *can*
24 be evil. It is an active technology, but the actions that matter here
25 are those of writing the code and checking it into the Fossil project
26 repository. None of the JavaScript code in Fossil is evil, a fact we
27 enforce by being careful about who we give check-in rights on the
28 repository to and by policing what code does get contributed. The Fossil
@@ -30,98 +30,264 @@
30
31 We think it’s better to ask not whether Fossil requires JavaScript but
32 whether Fossil uses JavaScript *well*, so that [you can decide](#block)
33 to block or allow Fossil’s use of JavaScript.
34
 
 
 
 
 
 
 
 
 
 
 
35 [cg]: ./contribute.wiki
36
37
38 ## <a id="block"></a>Blocking JavaScript
39
40 Rather than either block JavaScript wholesale or give up on blocking
41 JavaScript entirely, we recommend that you use tools like [NoScript][ns]
42 or [uBlock Origin][ub] to selectively block problematic uses of
43 JavaScript so the rest of the web can use the technology productively,
44 as it was intended. There are doubtless other useful tools of this sort;
45 we recommend only these two due to our limited experience, not out of
46 any wish to exclude other tools.
47
48 The primary difference between these two for our purposes is that
49 NoScript lets you select scripts to run on a page on a case-by-case
50 basis, whereas uBlock Origin delegates those choices to a group of
51 motivated volunteers who maintain whitelists and blacklists to control
52 all of this; you can then override UBO’s stock rules as needed.
53
54 [ns]: https://noscript.net/
55 [ub]: https://github.com/gorhill/uBlock/
56
57
58 ## <a id="stats"></a>How Many Users Run with JavaScript Disabled Anyway?
59
60 There are several studies that have directly measured the web audience
61 to answer this question:
62
63 * [What percentage of browsers with javascript disabled?][s1]
64 * [How many people are missing out on JavaScript enhancement?][s2]
65 * [Just how many web users really disable cookies or JavaScript?][s3]
66
67 Our sense of this data is that only about 0.2% of web users had
68 JavaScript disabled while participating in these studies.
69
70 The Fossil user community is not typical of the wider web, but if we
71 were able to comprehensively survey our users, we’d expect to find an
72 interesting dichotomy. Because Fossil is targeted at software
73 developers, who in turn are more likely to be power-users, we’d expect
74 to find Fossil users to be more in favor of some amount of JavaScript
75 blocking than the average web user. Yet, we’d also expect to find that
76 our user base has a disproportionately high number who run [powerful
77 conditional blocking plugins](#block) in their browsers, rather than
78 block JS entirely. We suspect that between these two forces, the number
79 of no-JS purists among Fossil’s user base is still a tiny minority.
80
81 [s1]: https://blockmetry.com/blog/javascript-disabled
82 [s2]: https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
83 [s3]: https://w3techs.com/technologies/overview/client_side_language/all
84
85
86 ## <a id="3pjs"></a>No Third-Party JavaScript in Fossil
87
88 Fossil does not use any third-party JavaScript libraries, not even very
89 common ones like jQuery. Every bit of JavaScript served by the stock
90 version of Fossil was written specifically for the Fossil project and is
91 stored [in its code repository](https://fossil-scm.org/fossil/file).
92
93 Therefore, if you want to hack on the JavaScript code served by Fossil
94 and mechanisms like [skin editing][cs] don’t suffice for your purposes,
95 you can hack on the JavaScript in your local instance directly, just as
96 you can hack on its C, SQL, and Tcl code. Fossil is free and open source
97 software, under [a single license][2cbsd].
98
99 [2cbsd]: https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt
100 [cs]: ./customskin.md
101
102
103 ## <a id="snoop"></a>Fossil Does Not Snoop On You
104
105 There is no tracking or other snooping technology in Fossil other than
106 that necessary for basic security, such as IP address logging on
107 check-ins. (This is in part why we have no [comprehensive user
108 statistics](#stats)!)
109
110 Fossil attempts to set two cookies on all web clients: a login session
111 cookie and a display preferences cookie. These cookies are restricted to
112 the Fossil instance, so even this limited data cannot leak between
113 Fossil instances or into other web sites.
114
115 There is some server-side event logging, but that is done entirely
116 without JavaScript, so it’s off-topic here.
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
119 ## <a id="uses"></a>Places Where Fossil’s Web UI Uses JavaScript
120
121 The remainder of this document will explain how Fossil currently uses
122 JavaScript and what it does when these uses are blocked.
 
123
124
125 ### <a id="timeline"></a>Timeline Graph
126
127 Fossil’s [web timeline][wt] uses JavaScript to render the graph
@@ -140,45 +306,144 @@
140 command, by clicking around within the web UI, etc.
141
142 _Potential Workaround:_ The timeline could be enhanced with `<noscript>`
143 tags that replace the graph with a column of checkboxes that control
144 what a series of form submit buttons do when clicked, replicating the
145 current JS-based features of the graph using client-server round-trips.
146 For example, you could click two of those checkboxes and then a button
147 labeled “Diff Selected” to replicate the current “click two nodes to
148 diff them” feature.
149
150 [wt]: https://fossil-scm.org/fossil/timeline
151
152
153 ### <a id="wedit"></a>WYSIWYG Wiki Editor
154
155 The Admin → Wiki → “Enable WYSIWYG Wiki Editing” toggle switches the
156 default plaintext editor for [Fossil wiki][fw] documents to one that
157 works like a basic word processor. This feature requires JavaScript in
158 order to react to editor button clicks like the “**B**” button, meaning
159 “make \[selected\] text boldface.” There is no standard WYSIWYG editor
160 component in browsers, doubtless because it’s relatively straightforward
161 to create one using JavaScript.
162
163 _Graceful Fallback:_ Edit your wiki documents in the default plain text
164 wiki editor. Fossil’s wiki and Markdown language processors were
165 designed to be edited that way.
166
167 [fw]: ./wikitheory.wiki
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
169
170 ### <a id="ln"></a>Line Numbering
171
172 When viewing source files, Fossil offers to show line numbers in some
173 cases. Toggling them on and off is currently handled in JavaScript.
174 ([Example][mainc].)
 
 
 
 
 
 
 
 
 
 
 
 
 
175
176 _Workaround:_ Edit the URL to give the “`ln`” query parameter per [the
177 `/file` docs](/help?cmd=/file), or provide a patch to reload the page
178 with this parameter included/excluded to implement the toggle via a
179 server round-trip.
180
181 [mainc]: https://fossil-scm.org/fossil/artifact?ln&name=87d67e745
182
183
184 ### <a id="sxsdiff"></a>Side-by-Side Diff Mode
@@ -224,12 +489,12 @@
224 similar, hovering over that check-in shows a tooltip with details about
225 the type of artifact the hash refers to and allows you to click to copy
226 the hash to the clipboard.
227
228 _Graceful Fallback:_ When JavaScript is disabled, these tooltips simply
229 don’t appear. You can then select and copy the hash using your browser,
230 make “`fossil info`” queries on those hashes, etc.
231
232
233 ### <a id="bots"></a>Anti-Bot Defenses
234
235 Fossil has [anti-bot defenses][abd], and it has some JavaScript code
@@ -254,26 +519,75 @@
254
255 _Graceful Fallback:_ Clicking the hamburger menu button with JavaScript
256 disabled will take you to the `/sitemap` page instead of showing a
257 simplified version of that page’s content in a drop-down.
258
259 _Workaround:_ You can remove this button by [editing the skin][cs]
260 header.
261
262
263 ### <a id="clock"></a>Clock
264
265 Some stock Fossil skins include JavaScript-based features such as the
266 current time of day. The Xekri skin includes this in its header, for
267 example. A clock feature requires JavaScript not only to get the time
268 and update inline on the page once a minute, but also so it displays *in
269 the local time zone.*
270
271 Since none of this code provides a necessary Fossil feature, the core
272 developers are unlikely to try to make these features work better in the
273 absence of JavaScript.
274
275 However, we are willing to study patches to make this better. For
276 example, the wall clock displays could include the page load time in the
277 dynamically generated HTML shipped from the remote Fossil server, so
278 that in the absence of JavaScript, you at least get the page generation
279 time, expressed in the server’s time zone.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
--- www/javascript.md
+++ www/javascript.md
@@ -1,28 +1,28 @@
1 # Use of JavaScript in Fossil
2
3 ## Philosophy & Policy
4
5 The Fossil development project’s policy is to use JavaScript where it
6 helps make its web UI better, but to offer graceful fallbacks wherever
7 practical. The intent is that the UI be usable with JavaScript
8 entirely disabled. In almost all places where Fossil uses JavaScript,
9 it is an enhancement to provided functionality, and there is always
10 another way to accomplish a given end without using JavaScript.
11
12 This is not to say that Fossil’s fall-backs for such cases are always as
13 elegant and functional as a no-JS purist might wish. That is simply
14 because [the vast majority of web users run with JavaScript enabled](#stats),
15 and a minority of those run with some kind of [conditional JavaScript
16 blocking](#block) in place. Fossil’s active developers do not deviate from that
17 norm enough that we have many no-JS purists among us, so the no-JS case
18 doesn’t get as much attention as some might want. We do [accept code
19 contributions][cg], and we are philosophically in favor of graceful
20 fall-backs, so you are welcome to appoint yourself the position of no-JS
21 czar for the Fossil project!
22
23 Evil is in actions, not in nouns: we do not believe JavaScript *can*
24 be evil. It is an active technology, but the actions that matter here
25 are those of writing the code and checking it into the Fossil project
26 repository. None of the JavaScript code in Fossil is evil, a fact we
27 enforce by being careful about who we give check-in rights on the
28 repository to and by policing what code does get contributed. The Fossil
@@ -30,98 +30,264 @@
30
31 We think it’s better to ask not whether Fossil requires JavaScript but
32 whether Fossil uses JavaScript *well*, so that [you can decide](#block)
33 to block or allow Fossil’s use of JavaScript.
34
35 The Fossil developers want to see the project thrive, and we achieve
36 that best by making it usable and friendly to a wider audience than the
37 minority of static web app purists. Modern users generally expect a
38 smoother experience than was available with 1990s style HTTP
39 POST-and-response `<form>` based interaction. We also increase the set
40 of potential Fossil developers if we do not restrict them to such
41 antiquated methods.
42
43 JavaScript is not perfect, but it's what we have, so we will use it
44 where we find it advantageous.
45
46 [cg]: ./contribute.wiki
47
48
49 ## <a id="debate"></a>Arguments Against JavaScript & Our Rebuttals
50
51 There are many common arguments against the use of JavaScript. Rather than
52 rehash these same arguments on the [forum][ffor], we distill the common
53 ones we’ve heard before and give our stock answers to them here:
54
55 1. “**It increases the size of the page download.**”
56
57 The heaviest such pages served by Fossil only have about 8 kB of
58 compressed JavaScript. (You have to go out of your way to get Fossil
59 to serve uncompressed pages.) This is negligible, even over very
60 slow data connections. If you are still somehow on a 56 kbit/sec
61 analog telephone modem, this extra script code would download in
62 about a second.
63
64 Most JavaScript-based Fossil pages use less code than that.
65
66 Atop that, Fossil 2.12 adds new script delivery methods with
67 aggressive caching enabled so that typical page loads will skip
68 re-loading this content on subsequent loads. These features are
69 currently optional: you must either set the new [`fossil server
70 --jsmode` option][fsrv] or the corresponding `jsmode` control line
71 in your [`fossil cgi`][fcgi] script when setting up your
72 [Fossil server][fshome]. That done, Fossil’s JavaScript files will
73 load almost instantly from the browser’s cache after the initial
74 page load, rather than be re-transferred over the network.
75
76 Between the improved caching and the fact that it’s quicker to
77 transfer a partial Ajax page load than reload the entire page, the
78 aggregate cost of such pages is typically *lower* than the older
79 methods based on HTTP POST with a full server round-trip. You can
80 expect to recover the cost of the initial page load in 1-2
81 round-trips. If we were to double the amount of JavaScript code in
82 Fossil, the payoff time would increase to 2-4 round-trips.
83
84 2. “**JavaScript is slow.**”
85
86 It *was*, before September 2008. Google's introduction of [their V8
87 JavaScript engine][v8] taught the world that JavaScript need not be
88 slow. This competitive pressure caused the other common JavaScript
89 interpreters to either improve or be replaced by one of the engines
90 that did improve to approach V8’s speed.
91
92 Nowadays JavaScript is, as a rule, astoundingly fast. As the world
93 continues to move more and more to web-based applications and
94 services, JavaScript engine developers have ample motivation to keep
95 their engines fast and competitive.
96
97 Once the scripts are cached, Ajax based page updates are faster than
98 the alternative, a full HTTP POST round-trip.
99
100 3. <a id="3pjs"></a>“**Third-party JavaScript cannot be trusted.**”
101
102 Fossil does not use any third-party JavaScript libraries, not even
103 very common ones like jQuery. Every bit of JavaScript served by the
104 stock version of Fossil was written specifically for the Fossil
105 project and is stored [in its code repository][fsrc].
106
107 Therefore, if you want to hack on the JavaScript code served by
108 Fossil and mechanisms like [skin editing][cskin] don’t suffice for your
109 purposes, you can hack on the JavaScript in your local instance
110 directly, just as you can hack on its C, SQL, and Tcl code. Fossil
111 is free and open source software, under [a single license][2cbsd].
112
113 4. <a id="snoop"></a>“**JavaScript and cookies are used to snoop on web users.**”
114
115 There is no tracking or other snooping technology in Fossil other than
116 that necessary for basic security, such as IP address logging on
117 check-ins. (This is in part why we have no [comprehensive user
118 statistics](#stats)!)
119
120 Fossil attempts to set two cookies on all web clients: a login session
121 cookie and a display preferences cookie. These cookies are restricted to
122 the Fossil instance, so even this limited data cannot leak between
123 Fossil instances or into other web sites.
124
125 5. “**JavaScript is fundamentally insecure.**”
126
127 JavaScript is certainly sometimes used for nefarious ends, but if we
128 wish to have more features in Fossil, the alternative is to add more
129 code to the Fossil binary, [most likely in C][fslpl], a language
130 implicated in [over 4× more security vulnerabilities][whmsl].
131
132 Therefore, does it not make sense to place approximately four times
133 as much trust in Fossil’s JavaScript code as in its C code?
134
135 The question is not whether JavaScript is itself evil, it is whether
136 its *authors* are evil. *Every byte* of JavaScript code used within
137 the Fossil UI is:
138
139 * ...written by the Fossil developers, vetted by their peers.
140
141 * ...[open source][flic] and [available][fsrc] to be inspected,
142 audited, and changed by its users.
143
144 * ...compiled directly into the `fossil` binary in a
145 non-obfuscated form during the build process, so there are no
146 third-party servers delivering mysterious, obfuscated JavaScript
147 code blobs to the user.
148
149 Local administrators can [modify the repository’s skin][cskin] to
150 inject additional JavaScript code into pages served by their Fossil
151 server. A typical case is to add a syntax highlighter like
152 [Prism.js][pjs] or [highlightjs][hljs] to the local repository. At
153 that point, your trust concern is not with Fossil’s use of
154 JavaScript, but with your trust in that repository’s administrator.
155
156 Fossil's [default content security policy][dcsp] (CSP)
157 prohibits execution of JavaScript code which is delivered from
158 anywhere but the Fossil server which delivers the page. A local
159 administrator can change this CSP, but again this comes down to a
160 matter of trust with the administrator, not with Fossil itself.
161
162 6. “**Cross-browser compatibility is poor.**”
163
164 It most certainly was in the first decade or so of JavaScript’s
165 lifetime, resulting in the creation of powerful libraries like
166 jQuery to patch over the incompatibilities. Over time, the need for
167 such libraries has dropped as browser vendors have fixed the
168 incompatibilities. Cross-browser JavaScript compatibility issues
169 which affect web developers are, by and large, a thing of the past.
170
171 7. “**Fossil UI works fine today without JavaScript. Why break it?**”
172
173 While this is true today, and we have no philosophical objection to
174 it remaining true, we do not intend to limit ourselves to only those
175 features that can be created without JavaScript. The mere
176 availability of alternatives is not a good justification for holding
177 back on notable improvements when they're within easy reach.
178
179 The no-JS case is a [minority position](#stats), so those that want
180 Fossil to have no-JS alternatives and graceful fallbacks will need
181 to get involved with the development if they want this state of
182 affairs to continue.
183
184 8. <a id="stats"></a>“**A large number of users run without JavaScript enabled.**”
185
186 That’s not what web audience measurements say:
187
188 * [What percentage of browsers with javascript disabled?][s1]
189 * [How many people are missing out on JavaScript enhancement?][s2]
190 * [Just how many web users really disable cookies or JavaScript?][s3]
191
192 Our sense of this data is that only about 0.2% of web users had
193 JavaScript disabled while participating in these studies.
194
195 The Fossil user community is not typical of the wider web, but if we
196 were able to comprehensively survey our users, we’d expect to find
197 an interesting dichotomy. Because Fossil is targeted at software
198 developers, who in turn are more likely to be power-users, we’d
199 expect to find Fossil users to be more in favor of some amount of
200 JavaScript blocking than the average web user. Yet, we’d also expect
201 to find that our user base has a disproportionately high number who
202 run [powerful conditional blocking plugins](#block) in their
203 browsers, rather than block JavaScript entirely. We suspect that
204 between these two forces, the number of no-JS purists among Fossil’s
205 user base is still a tiny minority.
206
207 9. <a id="block"></a>“**I block JavaScript entirely in my browser. That breaks Fossil.**”
208
209 First, see our philosophy statements above. Briefly, we intend that
210 there always be some other way to get any given result without using
211 JavaScript, developer interest willing.
212
213 But second, it doesn’t have to be all-or-nothing. We recommend that
214 those interested in blocking problematic uses of JavaScript use
215 tools like [NoScript][ns] or [uBlock Origin][ubo] to *selectively*
216 block JavaScript so the rest of the web can use the technology
217 productively, as it was intended.
218
219 There are doubtless other useful tools of this sort. We recommend
220 these two only from our limited experience, not out of any wish to
221 exclude other tools.
222
223 The primary difference between these two for our purposes is that
224 NoScript lets you select scripts to run on a page on a case-by-case
225 basis, whereas uBlock Origin delegates those choices to a group of
226 motivated volunteers who maintain allow/block lists to control all
227 of this; you can then override UBO’s stock rules as needed.
228
229 10. “**My browser doesn’t even *have* a JavaScript interpreter.**”
230
231 The Fossil open source project has no full-time developers, and only
232 a few of these part-timers are responsible for the bulk of the code
233 in Fossil. If you want Fossil to support such niche use cases, then
234 you will have to [get involved with its development][cg]: it’s
235 *your* uncommon itch.
236
237 11. <a id="compat"></a>“**Fossil’s JavaScript code isn’t compatible with my browser.**”
238
239 The Fossil project’s developers aim to remain compatible with
240 the largest portions of the client-side browser base. We use only
241 standards-defined JavaScript features which are known to work in the
242 overwhelmingly vast majority of browsers going back approximately 5
243 years, at minimum, as documented by [Can I Use...?][ciu] We avoid use of
244 features added to the language more recently or those which are still in
245 flux in standards committees.
246
247 We set this threshold based on the amount of time it typically takes for
248 new standards to propagate through the installed base.
249
250 As of this writing, this means we are only using features defined in
251 [ECMAScript 2015][es2015], colloquially called “JavaScript 6.” That
252 is a sufficiently rich standard that it more than suffices for our
253 purposes, and it is [widely deployed][es6dep]. The biggest single
254 outlier remaining is MSIE 11, and [even Microsoft is moving their
255 own products off of it][ie11x].
256
257 [2cbsd]: https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt
258 [ciu]: https://caniuse.com/
259 [cskin]: ./customskin.md
260 [dcsp]: ./defcsp.md
261 [es2015]: https://ecma-international.org/ecma-262/6.0/
262 [es6dep]: https://caniuse.com/#feat=es6
263 [fcgi]: /help?cmd=cgi
264 [ffor]: https://fossil-scm.org/forum/
265 [flic]: /doc/trunk/COPYRIGHT-BSD2.txt
266 [fshome]: /doc/trunk/www/server/
267 [fslpl]: /doc/trunk/www/fossil-v-git.wiki#portable
268 [fsrc]: https://fossil-scm.org/home/file/src
269 [fsrv]: /help?cmd=server
270 [hljs]: https://fossil-scm.org/forum/forumpost/9150bc22ca
271 [ie11x]: https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666
272 [ns]: https://noscript.net/
273 [pjs]: https://fossil-scm.org/forum/forumpost/1198651c6d
274 [s1]: https://blockmetry.com/blog/javascript-disabled
275 [s2]: https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
276 [s3]: https://w3techs.com/technologies/overview/client_side_language/all
277 [ubo]: https://github.com/gorhill/uBlock/
278 [v8]: https://en.wikipedia.org/wiki/V8_(JavaScript_engine)
279 [whmsl]: https://www.whitesourcesoftware.com/most-secure-programming-languages/
280
281
282 ----
283
284 ## <a id="uses"></a>Places Where Fossil’s Web UI Uses JavaScript
285
286 This section documents the areas where Fossil currently uses JavaScript
287 and what it does when these uses are blocked. It also gives common
288 workarounds where necessary.
289
290
291 ### <a id="timeline"></a>Timeline Graph
292
293 Fossil’s [web timeline][wt] uses JavaScript to render the graph
@@ -140,45 +306,144 @@
306 command, by clicking around within the web UI, etc.
307
308 _Potential Workaround:_ The timeline could be enhanced with `<noscript>`
309 tags that replace the graph with a column of checkboxes that control
310 what a series of form submit buttons do when clicked, replicating the
311 current JavaScript-based features of the graph using client-server round-trips.
312 For example, you could click two of those checkboxes and then a button
313 labeled “Diff Selected” to replicate the current “click two nodes to
314 diff them” feature.
315
316 [wt]: https://fossil-scm.org/fossil/timeline
317
318
319 ### <a id="wedit"></a>The New Wiki Editor
320
321 The [new wiki editor][fwt] added in Fossil 2.12 has many new features, a
322 few of which are impossible to get without use of JavaScript.
323
324 First, it allows in-browser previews without losing client-side editor
325 state, such as where your cursor is. With the old editor, you had to
326 re-locate the place you were last editing on each preview, which would
327 reduce the incentive to use the preview function. In the new wiki
328 editor, you just click the Preview tab to see how Fossil interprets your
329 markup, then click back to the Editor tab to resume work with the prior
330 context undisturbed.
331
332 Second, it continually saves your document state in client-side storage
333 in the background while you’re editing it so that if the browser closes
334 without saving the changes back to the Fossil repository, you can resume
335 editing from the stored copy without losing work. This feature is not so
336 much about saving you from crashes of various sorts, since computers are
337 so much more reliable these days. It is far more likely to save you from
338 the features of mobile OSes like Android and iOS which aggressively shut
339 down and restart apps to save on RAM. That OS design philosophy assumes
340 that there is a way for the app to restore its prior state from
341 persistent media when it’s restarted, giving the illusion that it was
342 never shut down in the first place. This feature of Fossil’s new wiki
343 editor provides that.
344
345 With this change, we lost the old WYSIWYG wiki editor, available since
346 Fossil version 1.24. It hadn’t been maintained for years, it was
347 disabled by default, and no one stepped up to defend its existence when
348 this new editor was created, replacing it. If someone rescues that
349 feature, merging it in with the new editor, it will doubtless require
350 JavaScript in order to react to editor button clicks like the “**B**”
351 button, meaning “make \[selected\] text boldface.” There is no standard
352 WYSIWYG editor component in browsers, doubtless because it’s relatively
353 straightforward to create one using JavaScript.
354
355 _Graceful Fallback:_ Unlike in the Fossil 2.11 and earlier days, there
356 is no longer a script-free wiki editor mode. This is not from lack of
357 desire, only because the person who wrote the new wiki editor didn’t
358 want to maintain three different editors. (New Ajaxy editor, old
359 script-free HTML form based editor, and the old WYSIWYG JavaScript-based
360 editor.) If someone wants to implement a `<noscript>` alternative to the
361 new wiki editor, we will likely accept that [contribution][cg] as long
362 as it doesn’t interfere with the new editor. (The same goes for adding
363 a WYSIWYG mode to the new Ajaxy wiki editor.)
364
365 _Workaround:_ You don’t have to use the browser-based wiki editor to
366 maintain your repository’s wiki at all. Fossil’s [`wiki` command][fwc]
367 lets you manipulate wiki documents from the command line. For example,
368 consider this Vi based workflow:
369
370 ```shell
371 $ vi 'My Article.wiki' # begin work on new article
372 ...write, write, write...
373 :w # save changes to disk copy
374 :!fossil wiki create 'My Article' '%' # current file (%) to new article
375 ...write, write, write some more...
376 :w # save again
377 :!fossil wiki commit 'My Article' '%' # update article from disk
378 :q # done writing for today
379
380 ....days later...
381 $ vi # work sans named file today
382 :r !fossil wiki export 'My Article' - # pull article text into vi buffer
383 ...write, write, write yet more...
384 :w !fossil wiki commit - # vi buffer updates article
385 ```
386
387 Extending this concept to other text editors is an exercise left to the
388 reader.
389
390 [fwc]: /help?cmd=wiki
391 [fwt]: ./wikitheory.wiki
392
393
394 ### <a id="fedit"></a>The File Editor
395
396 Fossil 2.12 adds the [optional file editor feature][fedit], which works
397 much like [the new wiki editor](#wedit), only on files committed to the
398 repository.
399
400 The original designed purpose for this feature is to allow [embedded
401 documentation][edoc] to be interactively edited in the same way that
402 wiki articles can be. (Indeed, the associated `fileedit-glob` feature
403 allows you to restrict the editor to working *only* on files that can be
404 treated as embedded documentation.) This feature operates in much the
405 same way as the new wiki editor, so most of what we said above applies.
406
407 _Workaround:_ This feature is an alternative to Fossil’s traditional
408 mode of file management: clone the repository, open it somewhere, edit a
409 file locally, and commit the changes.
410
411 _Graceful Fallback:_ There is no technical reason why someone could not
412 write a `<noscript>` wrapped alternative to the current JavaScript based
413 `/fileedit` implementation. It would have all of the same downsides as
414 the old wiki editor: the users would lose their place on each save, they
415 would have no local backup if something crashes, etc. Still, we are
416 likely to accept such a [contribution][cg] as long as it doesn’t
417 interfere with the new editor.
418
419 [edoc]: /doc/trunk/www/embeddeddoc.wiki
420 [fedit]: /doc/trunk/www/fileedit-page.md
421
422
423 ### <a id="ln"></a>Line Numbering
424
425 When viewing source files, Fossil offers to show line numbers in some
426 cases. ([Example][mainc].) Toggling them on and off is currently handled
427 in JavaScript, rather than forcing a page-reload via a button click.
428
429 _Workaround:_ Manually edit the URL to give the “`ln`” query parameter
430 per [the `/file` docs](/help?cmd=/file).
431
432 _Potential Better Workaround:_ Someone sufficiently interested could
433 [provide a patch][cg] to add a `<noscript>` wrapped HTML button that
434 would reload the page with this parameter included/excluded to implement
435 the toggle via a server round-trip.
436
437 As of Fossil 2.12, there is also a JavaScript-based interactive method
438 for selecting a range of lines by clicking the line numbers when they’re
439 visible, then copying the resulting URL to share your selection with
440 others.
441
442 _Workaround:_ These interactive features would be difficult and
443 expensive (in terms of network I/O) to implement without JavaScript. A
444 far simpler alternative is to manually edit the URL, per above.
 
445
446 [mainc]: https://fossil-scm.org/fossil/artifact?ln&name=87d67e745
447
448
449 ### <a id="sxsdiff"></a>Side-by-Side Diff Mode
@@ -224,12 +489,12 @@
489 similar, hovering over that check-in shows a tooltip with details about
490 the type of artifact the hash refers to and allows you to click to copy
491 the hash to the clipboard.
492
493 _Graceful Fallback:_ When JavaScript is disabled, these tooltips simply
494 don’t appear, but you can still select and copy the hash using your
495 platform’s “copy selected text” feature.
496
497
498 ### <a id="bots"></a>Anti-Bot Defenses
499
500 Fossil has [anti-bot defenses][abd], and it has some JavaScript code
@@ -254,26 +519,75 @@
519
520 _Graceful Fallback:_ Clicking the hamburger menu button with JavaScript
521 disabled will take you to the `/sitemap` page instead of showing a
522 simplified version of that page’s content in a drop-down.
523
524 _Workaround:_ You can remove this button by [editing the skin][cskin]
525 header.
526
527
528 ### <a id="clock"></a>Clock
529
530 Some stock Fossil skins include JavaScript-based features such as the
531 current time of day. The Xekri skin includes this in its header, for
532 example. A clock feature requires JavaScript to get the time on initial
533 page load and then to update it once a minute.
534
535 You may observe that the server could provide the current time when
536 generating the page, but the client and server may not be in the same
537 time zone, and there is no reliably-provided information from the client
538 that would let the server give the page load time in the client’s local
539 time zone. The server could only tell you *its* local time at page
540 request time, not the client’s time. That still wouldn’t be a “clock,”
541 since without client-side JavaScript code running, that part of the page
542 couldn’t update once a second.
543
544 _Potential Graceful Fallback:_ You may consider showing the server’s
545 page generation time rather than the client’s wall clock time in the
546 local time zone to be a useful fallback for the current feature, so [a
547 patch to do this][cg] may well be accepted. Since this is not a
548 *necessary* Fossil feature, an interested user is unlikely to get the
549 core developers to do this work for them.
550
551 ----
552
553 ## <a id="future"></a>Future Plans for JavaScript in Fossil
554
555 As of mid-2020, the informal provisional plan is to increase Fossil
556 UI's use of JavaScript considerably compared to its historically minimal
557 uses. To that end, a framework of Fossil-centric APIs is being developed
558 in conjunction with new features to consolidate Fossil's historical
559 hodgepodge of JavaScript snippets into a coherent code base.
560
561 When deciding which features to port to JavaScript, the rules of thumb
562 for this ongoing effort are:
563
564 - Pages which primarily display data (e.g. the timeline) will remain
565 largely static HTML with graceful fallbacks for all places they do
566 use JavaScript. Though JavaScript can be used effectively to power
567 all sorts of wonderful data presentation, Fossil currently doesn't
568 benefit greatly from doing so. We use JavaScript on these pages only
569 to improve their usability, not to define their primary operations.
570
571 - Pages which act as editors of some sort (e.g. the `/info` page) are
572 prime candidates for getting the same treatment as the old wiki
573 editor: reimplemented from the ground up in JavaScript using Ajax
574 type techniques. Similarly, a JS-driven overhaul is planned for the
575 forum’s post editor.
576
577 These are guidelines, not immutable requirements. Our development
578 direction is guided by our priorities:
579
580 1) Features the developers themselves want to have and/or work on.
581
582 2) Features end users request which catch the interest of one or more
583 developers, provided the developer(s) in question are in a position to
584 expend the effort.
585
586 3) Features end users and co-contributors can convince a developer into
587 coding even when they really don't want to. 😉
588
589 In all of this, Fossil's project lead understandably has the final
590 say-so in whether any given feature indeed gets merged into the mainline
591 trunk. Development of any given feature, no matter how much effort was
592 involved, does not guarantee its eventual inclusion into the public
593 releases.
594
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -61,10 +61,11 @@
6161
/help {Lists of Commands and Webpages}
6262
hints.wiki {Fossil Tips And Usage Hints}
6363
history.md {The Purpose And History Of Fossil}
6464
index.wiki {Home Page}
6565
inout.wiki {Import And Export To And From Git}
66
+ interwiki.md {Interwiki Links}
6667
image-format-vs-repo-size.md {Image Format vs Fossil Repo Size}
6768
javascript.md {Use of JavaScript in Fossil}
6869
makefile.wiki {The Fossil Build Process}
6970
mirrorlimitations.md {Limitations On Git Mirrors}
7071
mirrortogithub.md {How To Mirror A Fossil Repository On GitHub}
7172
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -61,10 +61,11 @@
61 /help {Lists of Commands and Webpages}
62 hints.wiki {Fossil Tips And Usage Hints}
63 history.md {The Purpose And History Of Fossil}
64 index.wiki {Home Page}
65 inout.wiki {Import And Export To And From Git}
 
66 image-format-vs-repo-size.md {Image Format vs Fossil Repo Size}
67 javascript.md {Use of JavaScript in Fossil}
68 makefile.wiki {The Fossil Build Process}
69 mirrorlimitations.md {Limitations On Git Mirrors}
70 mirrortogithub.md {How To Mirror A Fossil Repository On GitHub}
71
--- www/mkindex.tcl
+++ www/mkindex.tcl
@@ -61,10 +61,11 @@
61 /help {Lists of Commands and Webpages}
62 hints.wiki {Fossil Tips And Usage Hints}
63 history.md {The Purpose And History Of Fossil}
64 index.wiki {Home Page}
65 inout.wiki {Import And Export To And From Git}
66 interwiki.md {Interwiki Links}
67 image-format-vs-repo-size.md {Image Format vs Fossil Repo Size}
68 javascript.md {Use of JavaScript in Fossil}
69 makefile.wiki {The Fossil Build Process}
70 mirrorlimitations.md {Limitations On Git Mirrors}
71 mirrortogithub.md {How To Mirror A Fossil Repository On GitHub}
72
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -184,14 +184,16 @@
184184
<li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
185185
<li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
186186
<li><a href="fossil-from-msvc.wiki"><b>Integrating Fossil in the Microsoft Express 2010 IDE</b></a></li>
187187
<li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
188188
<li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
189
+<li><a href="interwiki.md"><b>Interwiki Links</b></a></li>
189190
<li><a href="javascript.md">JavaScript in Fossil &mdash; Use of</a></li>
190191
<li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
191192
<li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
192193
<li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li>
194
+<li><a href="interwiki.md">Links &mdash; Interwiki</a></li>
193195
<li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li>
194196
<li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
195197
<li><a href="../../../sitemap">Map &mdash; Site</a></li>
196198
<li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li>
197199
<li><a href="backoffice.md">mechanism of Fossil &mdash; The Backoffice</a></li>
198200
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -184,14 +184,16 @@
184 <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
185 <li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
186 <li><a href="fossil-from-msvc.wiki"><b>Integrating Fossil in the Microsoft Express 2010 IDE</b></a></li>
187 <li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
188 <li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
 
189 <li><a href="javascript.md">JavaScript in Fossil &mdash; Use of</a></li>
190 <li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
191 <li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
192 <li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li>
 
193 <li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li>
194 <li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
195 <li><a href="../../../sitemap">Map &mdash; Site</a></li>
196 <li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li>
197 <li><a href="backoffice.md">mechanism of Fossil &mdash; The Backoffice</a></li>
198
--- www/permutedindex.html
+++ www/permutedindex.html
@@ -184,14 +184,16 @@
184 <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li>
185 <li><a href="build.wiki">Installing Fossil &mdash; Compiling and</a></li>
186 <li><a href="fossil-from-msvc.wiki"><b>Integrating Fossil in the Microsoft Express 2010 IDE</b></a></li>
187 <li><a href="selfcheck.wiki">Integrity Self Checks &mdash; Fossil Repository</a></li>
188 <li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
189 <li><a href="interwiki.md"><b>Interwiki Links</b></a></li>
190 <li><a href="javascript.md">JavaScript in Fossil &mdash; Use of</a></li>
191 <li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
192 <li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
193 <li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li>
194 <li><a href="interwiki.md">Links &mdash; Interwiki</a></li>
195 <li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li>
196 <li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
197 <li><a href="../../../sitemap">Map &mdash; Site</a></li>
198 <li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li>
199 <li><a href="backoffice.md">mechanism of Fossil &mdash; The Backoffice</a></li>
200
+7 -18
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -19,11 +19,13 @@
1919
represented as n-dimensional membranes, mapping the spatial loci of
2020
successive commits onto the projected manifold of each cloned
2121
repository.</nowiki>
2222
2323
<blockquote>
24
-<i>At [http://tartley.com/?p=1267]</i>
24
+<i>Previously at
25
+[https://www.tartley.com/a-guide-to-git-using-spatial-analogies], since
26
+removed;<br>Quoted here: [https://lwn.net/Articles/420152/].</i>
2527
</blockquote>
2628
2729
<li>Git is not a Prius. Git is a Model T.
2830
Its plumbing and wiring sticks out all over the place.
2931
You have to be a mechanic to operate it successfully or you'll be
@@ -50,16 +52,10 @@
5052
<p>* dkf really wishes he could use fossil instead</p>
5153
<blockquote>
5254
<i>by Donal K. Fellow (dkf) on the Tcl/Tk chatroom, 2013-04-09.</i>
5355
</blockquote>
5456
55
-<li>Klingon Code Warriors embrace Git; we enjoy arbitrary conflicts. Git is not for the weak and feeble. TODAY IS A GOOD DAY TO CODE.
56
-
57
-<blockquote>
58
-<i>teastain at [http://www.reddit.com/r/programming/comments/xpitj/10_things_i_hate_about_git/c5oj4fk]
59
-</blockquote>
60
-
6157
<li>&#91;G&#93;it is <i>designed</i> to forget things.
6258
6359
<blockquote>
6460
<i>[http://www.cs.cmu.edu/~davide/howto/git_lose.html]
6561
</blockquote>
@@ -66,11 +62,11 @@
6662
6763
<li>&#91;I&#93;n nearly 31 years of using a computer i have, in total, lost more data to git
6864
(while following the instructions!!!) than any other single piece of software.
6965
7066
<blockquote>
71
-<i>Stephen Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
67
+<i>Stephan Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
7268
2014-09-01.</i>
7369
</blockquote>
7470
7571
<li>If programmers _really_ wanted to help scientists, they'd build a version control
7672
system that was more usable than Git.
@@ -91,11 +87,11 @@
9187
<li value=11>
9288
Fossil mesmerizes me with simplicity especially after I struggled to
9389
get a bug-tracking system to work with mercurial.
9490
9591
<blockquote>
96
-<i>rawjeev at [http://stackoverflow.com/questions/156322/what-do-people-think-of-the-fossil-dvcs]</i>
92
+<i>rawjeev at [https://stackoverflow.com/a/2100469/142454]</i>
9793
</blockquote>
9894
9995
<li>Fossil is the best thing to happen
10096
to my development workflow this year, as I am pretty sure that using
10197
Git has resulted in the premature death of too many of my brain cells.
@@ -104,17 +100,10 @@
104100
105101
<blockquote>
106102
<i>Joe Prostko at [http://www.mail-archive.com/[email protected]/msg16716.html]
107103
</blockquote>
108104
109
-<li>Fossil is awesome!!! I have never seen an app like that before,
110
-such simplicity and flexibility!!!
111
-
112
-<blockquote>
113
-<i>zengr at [http://stackoverflow.com/questions/138621/best-version-control-for-lone-developer]</i>
114
-</blockquote>
115
-
116105
<li>This is my favourite VCS. I can carry it on a USB. And it's a complete system, with it's own
117106
server, ticketing system, Wiki pages, and a very, very helpful timeline visualization. And
118107
the entire program in a single file!
119108
120109
<blockquote>
@@ -126,11 +115,11 @@
126115
127116
128117
<h2>On Git Versus Fossil</h2>
129118
130119
<ol>
131
-<li value=15>
120
+<li value=14>
132121
After prolonged exposure to fossil, i tend to get the jitters when I work with git...
133122
134123
<blockquote>
135124
<i>sriku - at [https://news.ycombinator.com/item?id=16104427]</i>
136125
</blockquote>
@@ -150,11 +139,11 @@
150139
sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it
151140
and teaching a Msc/Phd student (read complete novice) fossil has just
152141
been a smoother ride than Git was.
153142
154143
<blockquote>
155
-<i>viablepanic at [http://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/]</i>
144
+<i>viablepanic at [https://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/c0p30b4?utm_source=share&utm_medium=web2x&context=3]</i>
156145
</blockquote>
157146
158147
<li>In the fossil community - and hence in fossil itself - development history
159148
is pretty much sacrosanct. The very name "fossil" was to chosen to
160149
reflect the unchanging nature of things in that history.
161150
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -19,11 +19,13 @@
19 represented as n-dimensional membranes, mapping the spatial loci of
20 successive commits onto the projected manifold of each cloned
21 repository.</nowiki>
22
23 <blockquote>
24 <i>At [http://tartley.com/?p=1267]</i>
 
 
25 </blockquote>
26
27 <li>Git is not a Prius. Git is a Model T.
28 Its plumbing and wiring sticks out all over the place.
29 You have to be a mechanic to operate it successfully or you'll be
@@ -50,16 +52,10 @@
50 <p>* dkf really wishes he could use fossil instead</p>
51 <blockquote>
52 <i>by Donal K. Fellow (dkf) on the Tcl/Tk chatroom, 2013-04-09.</i>
53 </blockquote>
54
55 <li>Klingon Code Warriors embrace Git; we enjoy arbitrary conflicts. Git is not for the weak and feeble. TODAY IS A GOOD DAY TO CODE.
56
57 <blockquote>
58 <i>teastain at [http://www.reddit.com/r/programming/comments/xpitj/10_things_i_hate_about_git/c5oj4fk]
59 </blockquote>
60
61 <li>&#91;G&#93;it is <i>designed</i> to forget things.
62
63 <blockquote>
64 <i>[http://www.cs.cmu.edu/~davide/howto/git_lose.html]
65 </blockquote>
@@ -66,11 +62,11 @@
66
67 <li>&#91;I&#93;n nearly 31 years of using a computer i have, in total, lost more data to git
68 (while following the instructions!!!) than any other single piece of software.
69
70 <blockquote>
71 <i>Stephen Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
72 2014-09-01.</i>
73 </blockquote>
74
75 <li>If programmers _really_ wanted to help scientists, they'd build a version control
76 system that was more usable than Git.
@@ -91,11 +87,11 @@
91 <li value=11>
92 Fossil mesmerizes me with simplicity especially after I struggled to
93 get a bug-tracking system to work with mercurial.
94
95 <blockquote>
96 <i>rawjeev at [http://stackoverflow.com/questions/156322/what-do-people-think-of-the-fossil-dvcs]</i>
97 </blockquote>
98
99 <li>Fossil is the best thing to happen
100 to my development workflow this year, as I am pretty sure that using
101 Git has resulted in the premature death of too many of my brain cells.
@@ -104,17 +100,10 @@
104
105 <blockquote>
106 <i>Joe Prostko at [http://www.mail-archive.com/[email protected]/msg16716.html]
107 </blockquote>
108
109 <li>Fossil is awesome!!! I have never seen an app like that before,
110 such simplicity and flexibility!!!
111
112 <blockquote>
113 <i>zengr at [http://stackoverflow.com/questions/138621/best-version-control-for-lone-developer]</i>
114 </blockquote>
115
116 <li>This is my favourite VCS. I can carry it on a USB. And it's a complete system, with it's own
117 server, ticketing system, Wiki pages, and a very, very helpful timeline visualization. And
118 the entire program in a single file!
119
120 <blockquote>
@@ -126,11 +115,11 @@
126
127
128 <h2>On Git Versus Fossil</h2>
129
130 <ol>
131 <li value=15>
132 After prolonged exposure to fossil, i tend to get the jitters when I work with git...
133
134 <blockquote>
135 <i>sriku - at [https://news.ycombinator.com/item?id=16104427]</i>
136 </blockquote>
@@ -150,11 +139,11 @@
150 sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it
151 and teaching a Msc/Phd student (read complete novice) fossil has just
152 been a smoother ride than Git was.
153
154 <blockquote>
155 <i>viablepanic at [http://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/]</i>
156 </blockquote>
157
158 <li>In the fossil community - and hence in fossil itself - development history
159 is pretty much sacrosanct. The very name "fossil" was to chosen to
160 reflect the unchanging nature of things in that history.
161
--- www/quotes.wiki
+++ www/quotes.wiki
@@ -19,11 +19,13 @@
19 represented as n-dimensional membranes, mapping the spatial loci of
20 successive commits onto the projected manifold of each cloned
21 repository.</nowiki>
22
23 <blockquote>
24 <i>Previously at
25 [https://www.tartley.com/a-guide-to-git-using-spatial-analogies], since
26 removed;<br>Quoted here: [https://lwn.net/Articles/420152/].</i>
27 </blockquote>
28
29 <li>Git is not a Prius. Git is a Model T.
30 Its plumbing and wiring sticks out all over the place.
31 You have to be a mechanic to operate it successfully or you'll be
@@ -50,16 +52,10 @@
52 <p>* dkf really wishes he could use fossil instead</p>
53 <blockquote>
54 <i>by Donal K. Fellow (dkf) on the Tcl/Tk chatroom, 2013-04-09.</i>
55 </blockquote>
56
 
 
 
 
 
 
57 <li>&#91;G&#93;it is <i>designed</i> to forget things.
58
59 <blockquote>
60 <i>[http://www.cs.cmu.edu/~davide/howto/git_lose.html]
61 </blockquote>
@@ -66,11 +62,11 @@
62
63 <li>&#91;I&#93;n nearly 31 years of using a computer i have, in total, lost more data to git
64 (while following the instructions!!!) than any other single piece of software.
65
66 <blockquote>
67 <i>Stephan Beal on the [http://www.mail-archive.com/[email protected]/msg17181.html|Fossil mailing list]
68 2014-09-01.</i>
69 </blockquote>
70
71 <li>If programmers _really_ wanted to help scientists, they'd build a version control
72 system that was more usable than Git.
@@ -91,11 +87,11 @@
87 <li value=11>
88 Fossil mesmerizes me with simplicity especially after I struggled to
89 get a bug-tracking system to work with mercurial.
90
91 <blockquote>
92 <i>rawjeev at [https://stackoverflow.com/a/2100469/142454]</i>
93 </blockquote>
94
95 <li>Fossil is the best thing to happen
96 to my development workflow this year, as I am pretty sure that using
97 Git has resulted in the premature death of too many of my brain cells.
@@ -104,17 +100,10 @@
100
101 <blockquote>
102 <i>Joe Prostko at [http://www.mail-archive.com/[email protected]/msg16716.html]
103 </blockquote>
104
 
 
 
 
 
 
 
105 <li>This is my favourite VCS. I can carry it on a USB. And it's a complete system, with it's own
106 server, ticketing system, Wiki pages, and a very, very helpful timeline visualization. And
107 the entire program in a single file!
108
109 <blockquote>
@@ -126,11 +115,11 @@
115
116
117 <h2>On Git Versus Fossil</h2>
118
119 <ol>
120 <li value=14>
121 After prolonged exposure to fossil, i tend to get the jitters when I work with git...
122
123 <blockquote>
124 <i>sriku - at [https://news.ycombinator.com/item?id=16104427]</i>
125 </blockquote>
@@ -150,11 +139,11 @@
139 sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it
140 and teaching a Msc/Phd student (read complete novice) fossil has just
141 been a smoother ride than Git was.
142
143 <blockquote>
144 <i>viablepanic at [https://www.reddit.com/r/programming/comments/bxcto/why_not_fossil_scm/c0p30b4?utm_source=share&utm_medium=web2x&context=3]</i>
145 </blockquote>
146
147 <li>In the fossil community - and hence in fossil itself - development history
148 is pretty much sacrosanct. The very name "fossil" was to chosen to
149 reflect the unchanging nature of things in that history.
150
--- www/server/any/scgi.md
+++ www/server/any/scgi.md
@@ -63,11 +63,11 @@
6363
We go into more detail on nginx service setup with Fossil in our
6464
[Debian/Ubuntu specific guide](../debian/nginx.md). Then in [a later
6565
article](../../tls-nginx.md) that builds upon that, we show how to add
6666
TLS encryption to this basic SCGI + nginx setup on Debian type OSes.
6767
68
-Similarly, our [OpenBSD specific guide](../openbsd/httpd.md) details how
68
+Similarly, our [OpenBSD specific guide](../openbsd/fastcgi.md) details how
6969
to setup a Fossil server using httpd and FastCGI on OpenBSD.
7070
7171
*[Return to the top-level Fossil server article.](../)*
7272
7373
[404]: https://en.wikipedia.org/wiki/HTTP_404
7474
--- www/server/any/scgi.md
+++ www/server/any/scgi.md
@@ -63,11 +63,11 @@
63 We go into more detail on nginx service setup with Fossil in our
64 [Debian/Ubuntu specific guide](../debian/nginx.md). Then in [a later
65 article](../../tls-nginx.md) that builds upon that, we show how to add
66 TLS encryption to this basic SCGI + nginx setup on Debian type OSes.
67
68 Similarly, our [OpenBSD specific guide](../openbsd/httpd.md) details how
69 to setup a Fossil server using httpd and FastCGI on OpenBSD.
70
71 *[Return to the top-level Fossil server article.](../)*
72
73 [404]: https://en.wikipedia.org/wiki/HTTP_404
74
--- www/server/any/scgi.md
+++ www/server/any/scgi.md
@@ -63,11 +63,11 @@
63 We go into more detail on nginx service setup with Fossil in our
64 [Debian/Ubuntu specific guide](../debian/nginx.md). Then in [a later
65 article](../../tls-nginx.md) that builds upon that, we show how to add
66 TLS encryption to this basic SCGI + nginx setup on Debian type OSes.
67
68 Similarly, our [OpenBSD specific guide](../openbsd/fastcgi.md) details how
69 to setup a Fossil server using httpd and FastCGI on OpenBSD.
70
71 *[Return to the top-level Fossil server article.](../)*
72
73 [404]: https://en.wikipedia.org/wiki/HTTP_404
74

Keyboard Shortcuts

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