Fossil SCM
Merge in trunk (check-in [f741baa6be])
Commit
5d4a57f2c04f5fe08fe119c30db813f47ddadd6d367a714d66a05d0fe47e7539
Parent
62c316306e6adc2…
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
M
VERSION
+1
-1
| --- VERSION | ||
| +++ VERSION | ||
| @@ -1,1 +1,1 @@ | ||
| 1 | -2.12.1 | |
| 1 | +2.13 | |
| 2 | 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.12.1 |
| 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.13 |
| 2 |
M
auto.def
+5
-2
| --- auto.def | ||
| +++ auto.def | ||
| @@ -176,12 +176,15 @@ | ||
| 176 | 176 | } |
| 177 | 177 | if {!$ok} { |
| 178 | 178 | user-error "unable to compile SQLite compatibility test program" |
| 179 | 179 | } |
| 180 | 180 | 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 | + } | |
| 183 | 186 | } |
| 184 | 187 | file delete ./conftest__ |
| 185 | 188 | } |
| 186 | 189 | test_system_sqlite |
| 187 | 190 | |
| 188 | 191 |
| --- 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 @@ | ||
| 572 | 572 | white-space: nowrap; |
| 573 | 573 | background: #000; |
| 574 | 574 | border: 2px solid #bbb; |
| 575 | 575 | border-radius: 5px |
| 576 | 576 | } |
| 577 | +table.numbered-lines td.file-content > pre { | |
| 578 | + margin-top: -2px/*offset CODE tag border*/; | |
| 579 | +} | |
| 577 | 580 | pre > code { |
| 578 | 581 | padding: 1rem 1.5rem; |
| 579 | 582 | white-space: pre |
| 580 | 583 | } |
| 581 | 584 | td, |
| 582 | 585 |
| --- 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 |
+1
-1
| --- skins/eagle/css.txt | ||
| +++ skins/eagle/css.txt | ||
| @@ -399,11 +399,11 @@ | ||
| 399 | 399 | |
| 400 | 400 | div.filetreeline:hover { |
| 401 | 401 | background-color: #7EA2D9; |
| 402 | 402 | } |
| 403 | 403 | |
| 404 | -div.selectedText { | |
| 404 | +table.numbered-lines td.line-numbers span.selected-line { | |
| 405 | 405 | background-color: #7EA2D9; |
| 406 | 406 | } |
| 407 | 407 | |
| 408 | 408 | .statistics-report-graph-line { |
| 409 | 409 | background-color: #7EA2D9; |
| 410 | 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 | 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 |
+2
-2
| --- skins/xekri/css.txt | ||
| +++ skins/xekri/css.txt | ||
| @@ -1000,15 +1000,15 @@ | ||
| 1000 | 1000 | /************************************** |
| 1001 | 1001 | * Did not encounter these |
| 1002 | 1002 | */ |
| 1003 | 1003 | |
| 1004 | 1004 | /* selected lines of text within a linenumbered artifact display */ |
| 1005 | -div.selectedText { | |
| 1005 | +table.numbered-lines td.line-numbers span.selected-line { | |
| 1006 | 1006 | font-weight: bold; |
| 1007 | 1007 | color: #00f; |
| 1008 | 1008 | background-color: #d5d5ff; |
| 1009 | - border: 1px #00f solid; | |
| 1009 | + border-color: #00f; | |
| 1010 | 1010 | } |
| 1011 | 1011 | |
| 1012 | 1012 | /* format for missing privileges note on user setup page */ |
| 1013 | 1013 | p.missingPriv { |
| 1014 | 1014 | color: #00f; |
| 1015 | 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 | 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 @@ | ||
| 130 | 130 | wiki_render_by_mimetype(pContent, zMime); |
| 131 | 131 | break; |
| 132 | 132 | default:{ |
| 133 | 133 | const char *zContent = blob_str(pContent); |
| 134 | 134 | 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"); | |
| 136 | 137 | }else{ |
| 137 | 138 | const char *zExt = strrchr(zName,'.'); |
| 138 | 139 | if(zExt && zExt[1]){ |
| 139 | 140 | CX("<pre><code class='language-%s'>%h</code></pre>", |
| 140 | 141 | zExt+1, zContent); |
| 141 | 142 |
| --- 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 @@ | ||
| 936 | 936 | ** This is a short name used to identifies the repository in the Subject: |
| 937 | 937 | ** line of email alerts. Traditionally this name is included in square |
| 938 | 938 | ** brackets. Examples: "[fossil-src]", "[sqlite-src]". |
| 939 | 939 | */ |
| 940 | 940 | /* |
| 941 | -** SETTING: email-send-method width=5 default=off | |
| 941 | +** SETTING: email-send-method width=5 default=off sensitive | |
| 942 | 942 | ** Determine the method used to send email. Allowed values are |
| 943 | 943 | ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value |
| 944 | 944 | ** means no email is ever sent. The "relay" value means emails are sent |
| 945 | 945 | ** to an Mail Sending Agent using SMTP located at email-send-relayhost. |
| 946 | 946 | ** The "pipe" value means email messages are piped into a command |
| @@ -949,33 +949,33 @@ | ||
| 949 | 949 | ** by the email-send-dir setting. The "db" value means that emails |
| 950 | 950 | ** are added to an SQLite database named by the* email-send-db setting. |
| 951 | 951 | ** The "stdout" value writes email text to standard output, for debugging. |
| 952 | 952 | */ |
| 953 | 953 | /* |
| 954 | -** SETTING: email-send-command width=40 | |
| 954 | +** SETTING: email-send-command width=40 sensitive | |
| 955 | 955 | ** This is a command to which outbound email content is piped when the |
| 956 | 956 | ** email-send-method is set to "pipe". The command must extract |
| 957 | 957 | ** recipient, sender, subject, and all other relevant information |
| 958 | 958 | ** from the email header. |
| 959 | 959 | */ |
| 960 | 960 | /* |
| 961 | -** SETTING: email-send-dir width=40 | |
| 961 | +** SETTING: email-send-dir width=40 sensitive | |
| 962 | 962 | ** This is a directory into which outbound emails are written as individual |
| 963 | 963 | ** files if the email-send-method is set to "dir". |
| 964 | 964 | */ |
| 965 | 965 | /* |
| 966 | -** SETTING: email-send-db width=40 | |
| 966 | +** SETTING: email-send-db width=40 sensitive | |
| 967 | 967 | ** This is an SQLite database file into which outbound emails are written |
| 968 | 968 | ** if the email-send-method is set to "db". |
| 969 | 969 | */ |
| 970 | 970 | /* |
| 971 | 971 | ** SETTING: email-self width=40 |
| 972 | 972 | ** This is the email address for the repository. Outbound emails add |
| 973 | 973 | ** this email address as the "From:" field. |
| 974 | 974 | */ |
| 975 | 975 | /* |
| 976 | -** SETTING: email-send-relayhost width=40 | |
| 976 | +** SETTING: email-send-relayhost width=40 sensitive | |
| 977 | 977 | ** This is the hostname and TCP port to which output email messages |
| 978 | 978 | ** are sent when email-send-method is "relay". There should be an |
| 979 | 979 | ** SMTP server configured as a Mail Submission Agent listening on the |
| 980 | 980 | ** designated host and port and all times. |
| 981 | 981 | */ |
| @@ -1769,18 +1769,20 @@ | ||
| 1769 | 1769 | "UPDATE subscriber SET sverified=1" |
| 1770 | 1770 | " WHERE subscriberCode=hextoblob(%Q)", |
| 1771 | 1771 | zName); |
| 1772 | 1772 | if( db_get_boolean("selfreg-verify",0) ){ |
| 1773 | 1773 | char *zNewCap = db_get("default-perms","u"); |
| 1774 | + db_unprotect(PROTECT_USER); | |
| 1774 | 1775 | db_multi_exec( |
| 1775 | 1776 | "UPDATE user" |
| 1776 | 1777 | " SET cap=%Q" |
| 1777 | 1778 | " WHERE cap='7' AND login=(" |
| 1778 | 1779 | " SELECT suname FROM subscriber" |
| 1779 | 1780 | " WHERE subscriberCode=hextoblob(%Q))", |
| 1780 | 1781 | zNewCap, zName |
| 1781 | 1782 | ); |
| 1783 | + db_protect_pop(); | |
| 1782 | 1784 | login_set_capabilities(zNewCap, 0); |
| 1783 | 1785 | } |
| 1784 | 1786 | @ <h1>Your email alert subscription has been verified!</h1> |
| 1785 | 1787 | @ <p>Use the form below to update your subscription information.</p> |
| 1786 | 1788 | @ <p>Hint: Bookmark this page so that you can more easily update |
| 1787 | 1789 |
| --- 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 |
+6
| --- src/allrepo.c | ||
| +++ src/allrepo.c | ||
| @@ -299,11 +299,13 @@ | ||
| 299 | 299 | useCheckouts?"ckout":"repo", blob_str(&fn) |
| 300 | 300 | ); |
| 301 | 301 | if( dryRunFlag ){ |
| 302 | 302 | fossil_print("%s\n", blob_sql_text(&sql)); |
| 303 | 303 | }else{ |
| 304 | + db_unprotect(PROTECT_CONFIG); | |
| 304 | 305 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 306 | + db_protect_pop(); | |
| 305 | 307 | } |
| 306 | 308 | } |
| 307 | 309 | db_end_transaction(0); |
| 308 | 310 | blob_reset(&sql); |
| 309 | 311 | blob_reset(&fn); |
| @@ -334,11 +336,13 @@ | ||
| 334 | 336 | "VALUES('repo:%q',1)", z |
| 335 | 337 | ); |
| 336 | 338 | if( dryRunFlag ){ |
| 337 | 339 | fossil_print("%s\n", blob_sql_text(&sql)); |
| 338 | 340 | }else{ |
| 341 | + db_unprotect(PROTECT_CONFIG); | |
| 339 | 342 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 343 | + db_protect_pop(); | |
| 340 | 344 | } |
| 341 | 345 | } |
| 342 | 346 | db_end_transaction(0); |
| 343 | 347 | blob_reset(&sql); |
| 344 | 348 | blob_reset(&fn); |
| @@ -428,9 +432,11 @@ | ||
| 428 | 432 | if( nToDel>0 ){ |
| 429 | 433 | const char *zSql = "DELETE FROM global_config WHERE name IN toDel"; |
| 430 | 434 | if( dryRunFlag ){ |
| 431 | 435 | fossil_print("%s\n", zSql); |
| 432 | 436 | }else{ |
| 437 | + db_unprotect(PROTECT_CONFIG); | |
| 433 | 438 | db_multi_exec("%s", zSql /*safe-for-%s*/ ); |
| 439 | + db_protect_pop(); | |
| 434 | 440 | } |
| 435 | 441 | } |
| 436 | 442 | } |
| 437 | 443 |
| --- 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 @@ | ||
| 617 | 617 | const char *z; |
| 618 | 618 | content_get(ridSrc, &attach); |
| 619 | 619 | blob_to_utf8_no_bom(&attach, 0); |
| 620 | 620 | z = blob_str(&attach); |
| 621 | 621 | if( zLn ){ |
| 622 | - output_text_with_line_numbers(z, zLn); | |
| 622 | + output_text_with_line_numbers(z, blob_size(&attach), zName, zLn); | |
| 623 | 623 | }else{ |
| 624 | 624 | @ <pre> |
| 625 | 625 | @ %h(z) |
| 626 | 626 | @ </pre> |
| 627 | 627 | } |
| 628 | 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, 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 @@ | ||
| 169 | 169 | rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d" |
| 170 | 170 | " ORDER BY mtime DESC LIMIT 1", tagid); |
| 171 | 171 | if( rid==0 ) return; |
| 172 | 172 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 173 | 173 | 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); | |
| 175 | 176 | manifest_destroy(pWiki); |
| 176 | 177 | } |
| 177 | 178 | } |
| 178 | 179 | |
| 179 | 180 | /* |
| 180 | 181 |
| --- 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 |
+4
| --- src/backoffice.c | ||
| +++ src/backoffice.c | ||
| @@ -241,10 +241,11 @@ | ||
| 241 | 241 | ** process (1) no longer exists and the current time exceeds (2). |
| 242 | 242 | */ |
| 243 | 243 | static void backofficeReadLease(Lease *pLease){ |
| 244 | 244 | Stmt q; |
| 245 | 245 | memset(pLease, 0, sizeof(*pLease)); |
| 246 | + db_unprotect(PROTECT_CONFIG); | |
| 246 | 247 | db_prepare(&q, "SELECT value FROM repository.config" |
| 247 | 248 | " WHERE name='backoffice'"); |
| 248 | 249 | if( db_step(&q)==SQLITE_ROW ){ |
| 249 | 250 | const char *z = db_column_text(&q,0); |
| 250 | 251 | z = backofficeParseInt(z, &pLease->idCurrent); |
| @@ -251,10 +252,11 @@ | ||
| 251 | 252 | z = backofficeParseInt(z, &pLease->tmCurrent); |
| 252 | 253 | z = backofficeParseInt(z, &pLease->idNext); |
| 253 | 254 | backofficeParseInt(z, &pLease->tmNext); |
| 254 | 255 | } |
| 255 | 256 | db_finalize(&q); |
| 257 | + db_protect_pop(); | |
| 256 | 258 | } |
| 257 | 259 | |
| 258 | 260 | /* |
| 259 | 261 | ** Return a string that describes how long it has been since the |
| 260 | 262 | ** last backoffice run. The string is obtained from fossil_malloc(). |
| @@ -277,15 +279,17 @@ | ||
| 277 | 279 | |
| 278 | 280 | /* |
| 279 | 281 | ** Write a lease to the backoffice property |
| 280 | 282 | */ |
| 281 | 283 | static void backofficeWriteLease(Lease *pLease){ |
| 284 | + db_unprotect(PROTECT_CONFIG); | |
| 282 | 285 | db_multi_exec( |
| 283 | 286 | "REPLACE INTO repository.config(name,value,mtime)" |
| 284 | 287 | " VALUES('backoffice','%lld %lld %lld %lld',now())", |
| 285 | 288 | pLease->idCurrent, pLease->tmCurrent, |
| 286 | 289 | pLease->idNext, pLease->tmNext); |
| 290 | + db_protect_pop(); | |
| 287 | 291 | } |
| 288 | 292 | |
| 289 | 293 | /* |
| 290 | 294 | ** Check to see if the specified Win32 process is still alive. It |
| 291 | 295 | ** should be noted that even if this function returns non-zero, the |
| 292 | 296 |
| --- 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 |
+2
| --- src/captcha.c | ||
| +++ src/captcha.c | ||
| @@ -458,14 +458,16 @@ | ||
| 458 | 458 | Blob b; |
| 459 | 459 | static char zRes[20]; |
| 460 | 460 | |
| 461 | 461 | zSecret = db_get("captcha-secret", 0); |
| 462 | 462 | if( zSecret==0 ){ |
| 463 | + db_unprotect(PROTECT_CONFIG); | |
| 463 | 464 | db_multi_exec( |
| 464 | 465 | "REPLACE INTO config(name,value)" |
| 465 | 466 | " VALUES('captcha-secret', lower(hex(randomblob(20))));" |
| 466 | 467 | ); |
| 468 | + db_protect_pop(); | |
| 467 | 469 | zSecret = db_get("captcha-secret", 0); |
| 468 | 470 | assert( zSecret!=0 ); |
| 469 | 471 | } |
| 470 | 472 | blob_init(&b, 0, 0); |
| 471 | 473 | blob_appendf(&b, "%s-%x", zSecret, seed); |
| 472 | 474 |
| --- 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 @@ | ||
| 1053 | 1053 | ** assume that PATH_INFO is an empty string and set REQUEST_URI equal |
| 1054 | 1054 | ** to PATH_INFO. |
| 1055 | 1055 | ** |
| 1056 | 1056 | ** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and |
| 1057 | 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 | |
| 1058 | 1069 | */ |
| 1059 | 1070 | void cgi_init(void){ |
| 1060 | 1071 | char *z; |
| 1061 | 1072 | const char *zType; |
| 1062 | 1073 | char *zSemi; |
| 1063 | 1074 | int len; |
| 1064 | 1075 | 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",""); | |
| 1067 | 1078 | #ifdef _WIN32 |
| 1068 | 1079 | const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0); |
| 1069 | 1080 | #endif |
| 1070 | 1081 | |
| 1071 | 1082 | #ifdef FOSSIL_ENABLE_JSON |
| 1072 | 1083 | const int noJson = P("no_json")!=0; |
| 1073 | 1084 | #endif |
| 1074 | 1085 | g.isHTTP = 1; |
| 1075 | 1086 | 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 | + | |
| 1077 | 1104 | #ifdef _WIN32 |
| 1078 | 1105 | /* The Microsoft IIS web server does not define REQUEST_URI, instead it uses |
| 1079 | 1106 | ** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as |
| 1080 | 1107 | ** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the |
| 1081 | 1108 | ** beginning. */ |
| @@ -1257,16 +1284,15 @@ | ||
| 1257 | 1284 | } |
| 1258 | 1285 | } |
| 1259 | 1286 | |
| 1260 | 1287 | /* If no match is found and the name begins with an upper-case |
| 1261 | 1288 | ** 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. | |
| 1264 | 1290 | */ |
| 1265 | 1291 | if( fossil_isupper(zName[0]) ){ |
| 1266 | 1292 | const char *zValue = fossil_getenv(zName); |
| 1267 | - if( zValue && zValue[0] ){ | |
| 1293 | + if( zValue ){ | |
| 1268 | 1294 | cgi_set_parameter_nocopy(zName, zValue, 0); |
| 1269 | 1295 | CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue)); |
| 1270 | 1296 | return zValue; |
| 1271 | 1297 | } |
| 1272 | 1298 | } |
| 1273 | 1299 |
| --- 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 @@ | ||
| 62 | 62 | ** Create a TEMP table named SFILE and add all unmanaged files named on |
| 63 | 63 | ** the command-line to that table. If directories are named, then add |
| 64 | 64 | ** all unmanaged files contained underneath those directories. If there |
| 65 | 65 | ** are no files or directories named on the command-line, then add all |
| 66 | 66 | ** unmanaged files anywhere in the checkout. |
| 67 | +** | |
| 68 | +** This routine never follows symlinks. It always treats symlinks as | |
| 69 | +** object unto themselves. | |
| 67 | 70 | */ |
| 68 | 71 | static void locate_unmanaged_files( |
| 69 | 72 | int argc, /* Number of command-line arguments to examine */ |
| 70 | 73 | char **argv, /* values of command-line arguments */ |
| 71 | 74 | unsigned scanFlags, /* Zero or more SCAN_xxx flags */ |
| @@ -80,19 +83,19 @@ | ||
| 80 | 83 | db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s," |
| 81 | 84 | " mtime INTEGER, size INTEGER)", filename_collation()); |
| 82 | 85 | nRoot = (int)strlen(g.zLocalRoot); |
| 83 | 86 | if( argc==0 ){ |
| 84 | 87 | 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); | |
| 86 | 89 | blob_reset(&name); |
| 87 | 90 | }else{ |
| 88 | 91 | for(i=0; i<argc; i++){ |
| 89 | 92 | file_canonical_name(argv[i], &name, 0); |
| 90 | 93 | zName = blob_str(&name); |
| 91 | - isDir = file_isdir(zName, RepoFILE); | |
| 94 | + isDir = file_isdir(zName, SymFILE); | |
| 92 | 95 | if( isDir==1 ){ |
| 93 | - vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, RepoFILE); | |
| 96 | + vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE); | |
| 94 | 97 | }else if( isDir==0 ){ |
| 95 | 98 | fossil_warning("not found: %s", &zName[nRoot]); |
| 96 | 99 | }else if( file_access(zName, R_OK) ){ |
| 97 | 100 | fossil_fatal("cannot open %s", &zName[nRoot]); |
| 98 | 101 | }else{ |
| @@ -856,14 +859,10 @@ | ||
| 856 | 859 | |
| 857 | 860 | if( zIgnoreFlag==0 ){ |
| 858 | 861 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 859 | 862 | } |
| 860 | 863 | 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 | 864 | locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); |
| 866 | 865 | glob_free(pIgnore); |
| 867 | 866 | |
| 868 | 867 | blob_zero(&report); |
| 869 | 868 | status_report(&report, flags); |
| @@ -1017,14 +1016,10 @@ | ||
| 1017 | 1016 | verify_all_options(); |
| 1018 | 1017 | pIgnore = glob_create(zIgnoreFlag); |
| 1019 | 1018 | pKeep = glob_create(zKeepFlag); |
| 1020 | 1019 | pClean = glob_create(zCleanFlag); |
| 1021 | 1020 | 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 | 1021 | if( !dirsOnlyFlag ){ |
| 1027 | 1022 | Stmt q; |
| 1028 | 1023 | Blob repo; |
| 1029 | 1024 | if( !dryRunFlag && !disableUndo ) undo_begin(); |
| 1030 | 1025 | locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); |
| @@ -2738,11 +2733,10 @@ | ||
| 2738 | 2733 | /* Commit */ |
| 2739 | 2734 | db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
| 2740 | 2735 | db_multi_exec("PRAGMA repository.application_id=252006673;"); |
| 2741 | 2736 | db_multi_exec("PRAGMA localdb.application_id=252006674;"); |
| 2742 | 2737 | if( dryRunFlag ){ |
| 2743 | - leaf_ambiguity_warning(nvid,nvid); | |
| 2744 | 2738 | db_end_transaction(1); |
| 2745 | 2739 | exit(1); |
| 2746 | 2740 | } |
| 2747 | 2741 | db_end_transaction(0); |
| 2748 | 2742 | |
| 2749 | 2743 |
| --- 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 |
+5
| --- src/clone.c | ||
| +++ src/clone.c | ||
| @@ -167,10 +167,11 @@ | ||
| 167 | 167 | if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; |
| 168 | 168 | if( g.url.isFile ){ |
| 169 | 169 | file_copy(g.url.name, g.argv[3]); |
| 170 | 170 | db_close(1); |
| 171 | 171 | db_open_repository(g.argv[3]); |
| 172 | + db_open_config(1,0); | |
| 172 | 173 | db_record_repository_filename(g.argv[3]); |
| 173 | 174 | url_remember(); |
| 174 | 175 | if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content(); |
| 175 | 176 | shun_artifacts(); |
| 176 | 177 | db_create_default_users(1, zDefaultUser); |
| @@ -201,15 +202,17 @@ | ||
| 201 | 202 | blob_zero(&fn); |
| 202 | 203 | file_canonical_name(g.zSSLIdentity, &fn, 0); |
| 203 | 204 | db_set("ssl-identity", blob_str(&fn), 0); |
| 204 | 205 | blob_reset(&fn); |
| 205 | 206 | } |
| 207 | + db_unprotect(PROTECT_CONFIG); | |
| 206 | 208 | db_multi_exec( |
| 207 | 209 | "REPLACE INTO config(name,value,mtime)" |
| 208 | 210 | " VALUES('server-code', lower(hex(randomblob(20))), now());" |
| 209 | 211 | "DELETE FROM config WHERE name='project-code';" |
| 210 | 212 | ); |
| 213 | + db_protect_pop(); | |
| 211 | 214 | url_enable_proxy(0); |
| 212 | 215 | clone_ssh_db_set_options(); |
| 213 | 216 | url_get_password_if_needed(); |
| 214 | 217 | g.xlinkClusterOnly = 1; |
| 215 | 218 | nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0); |
| @@ -235,11 +238,13 @@ | ||
| 235 | 238 | fossil_print("Vacuuming the database... "); fflush(stdout); |
| 236 | 239 | if( db_int(0, "PRAGMA page_count")>1000 |
| 237 | 240 | && db_int(0, "PRAGMA page_size")<8192 ){ |
| 238 | 241 | db_multi_exec("PRAGMA page_size=8192;"); |
| 239 | 242 | } |
| 243 | + db_unprotect(PROTECT_ALL); | |
| 240 | 244 | db_multi_exec("VACUUM"); |
| 245 | + db_protect_pop(); | |
| 241 | 246 | fossil_print("\nproject-id: %s\n", db_get("project-code", 0)); |
| 242 | 247 | fossil_print("server-id: %s\n", db_get("server-code", 0)); |
| 243 | 248 | zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); |
| 244 | 249 | fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword); |
| 245 | 250 | } |
| 246 | 251 |
| --- 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 @@ | ||
| 37 | 37 | #define CONFIGSET_USER 0x000020 /* The USER table */ |
| 38 | 38 | #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */ |
| 39 | 39 | #define CONFIGSET_XFER 0x000080 /* Transfer configuration */ |
| 40 | 40 | #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */ |
| 41 | 41 | #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 */ | |
| 43 | 44 | |
| 44 | 45 | #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ |
| 45 | 46 | |
| 46 | 47 | /* |
| 47 | 48 | ** This mask is used for the common TH1 configuration settings (i.e. those |
| @@ -58,22 +59,23 @@ | ||
| 58 | 59 | static struct { |
| 59 | 60 | const char *zName; /* Name of the configuration set */ |
| 60 | 61 | int groupMask; /* Mask for that configuration set */ |
| 61 | 62 | const char *zHelp; /* What it does */ |
| 62 | 63 | } 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" }, | |
| 65 | 66 | { "/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" }, | |
| 75 | 77 | }; |
| 76 | 78 | |
| 77 | 79 | |
| 78 | 80 | /* |
| 79 | 81 | ** The following is a list of settings that we are willing to |
| @@ -143,13 +145,10 @@ | ||
| 143 | 145 | { "keep-glob", CONFIGSET_PROJ }, |
| 144 | 146 | { "crlf-glob", CONFIGSET_PROJ }, |
| 145 | 147 | { "crnl-glob", CONFIGSET_PROJ }, |
| 146 | 148 | { "encoding-glob", CONFIGSET_PROJ }, |
| 147 | 149 | { "empty-dirs", CONFIGSET_PROJ }, |
| 148 | -#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS | |
| 149 | - { "allow-symlinks", CONFIGSET_PROJ }, | |
| 150 | -#endif | |
| 151 | 150 | { "dotfiles", CONFIGSET_PROJ }, |
| 152 | 151 | { "parent-project-code", CONFIGSET_PROJ }, |
| 153 | 152 | { "parent-project-name", CONFIGSET_PROJ }, |
| 154 | 153 | { "hash-policy", CONFIGSET_PROJ }, |
| 155 | 154 | { "comment-format", CONFIGSET_PROJ }, |
| @@ -176,10 +175,12 @@ | ||
| 176 | 175 | { "@shun", CONFIGSET_SHUN }, |
| 177 | 176 | |
| 178 | 177 | { "@alias", CONFIGSET_ALIAS }, |
| 179 | 178 | |
| 180 | 179 | { "@subscriber", CONFIGSET_SCRIBER }, |
| 180 | + | |
| 181 | + { "@interwiki", CONFIGSET_IWIKI }, | |
| 181 | 182 | |
| 182 | 183 | { "xfer-common-script", CONFIGSET_XFER }, |
| 183 | 184 | { "xfer-push-script", CONFIGSET_XFER }, |
| 184 | 185 | { "xfer-commit-script", CONFIGSET_XFER }, |
| 185 | 186 | { "xfer-ticket-script", CONFIGSET_XFER }, |
| @@ -259,10 +260,13 @@ | ||
| 259 | 260 | return m; |
| 260 | 261 | } |
| 261 | 262 | } |
| 262 | 263 | if( strncmp(zName, "walias:/", 8)==0 ){ |
| 263 | 264 | return CONFIGSET_ALIAS; |
| 265 | + } | |
| 266 | + if( strncmp(zName, "interwiki:", 10)==0 ){ | |
| 267 | + return CONFIGSET_IWIKI; | |
| 264 | 268 | } |
| 265 | 269 | return 0; |
| 266 | 270 | } |
| 267 | 271 | |
| 268 | 272 | /* |
| @@ -441,10 +445,11 @@ | ||
| 441 | 445 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 442 | 446 | azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/); |
| 443 | 447 | for(jj=2; jj<nToken; jj+=2){ |
| 444 | 448 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 445 | 449 | } |
| 450 | + db_protect_only(PROTECT_SENSITIVE); | |
| 446 | 451 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 447 | 452 | if( db_changes()==0 ){ |
| 448 | 453 | blob_reset(&sql); |
| 449 | 454 | blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", |
| 450 | 455 | &zName[1], azToken[0]/*safe-for-%s*/); |
| @@ -455,10 +460,11 @@ | ||
| 455 | 460 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", |
| 456 | 461 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 457 | 462 | azToken[0]/*safe-for-%s*/); |
| 458 | 463 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 459 | 464 | } |
| 465 | + db_protect_pop(); | |
| 460 | 466 | blob_reset(&sql); |
| 461 | 467 | rebuildMask |= thisMask; |
| 462 | 468 | } |
| 463 | 469 | } |
| 464 | 470 | |
| @@ -596,10 +602,26 @@ | ||
| 596 | 602 | blob_size(&rec), blob_str(&rec)); |
| 597 | 603 | nCard++; |
| 598 | 604 | blob_reset(&rec); |
| 599 | 605 | } |
| 600 | 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); | |
| 601 | 623 | } |
| 602 | 624 | if( (groupMask & CONFIGSET_SCRIBER)!=0 |
| 603 | 625 | && db_table_exists("repository","subscriber") |
| 604 | 626 | ){ |
| 605 | 627 | db_prepare(&q, "SELECT mtime, quote(semail)," |
| @@ -710,11 +732,12 @@ | ||
| 710 | 732 | ** > fossil configuration export AREA FILENAME |
| 711 | 733 | ** |
| 712 | 734 | ** Write to FILENAME exported configuration information for AREA. |
| 713 | 735 | ** AREA can be one of: |
| 714 | 736 | ** |
| 715 | -** all email project shun skin ticket user alias subscriber | |
| 737 | +** all email interwiki project shun skin | |
| 738 | +** ticket user alias subscriber | |
| 716 | 739 | ** |
| 717 | 740 | ** > fossil configuration import FILENAME |
| 718 | 741 | ** |
| 719 | 742 | ** Read a configuration from FILENAME, overwriting the current |
| 720 | 743 | ** configuration. |
| @@ -839,13 +862,17 @@ | ||
| 839 | 862 | export_config(mask, g.argv[3], 0, zBackup); |
| 840 | 863 | for(i=0; i<count(aConfig); i++){ |
| 841 | 864 | const char *zName = aConfig[i].zName; |
| 842 | 865 | if( (aConfig[i].groupMask & mask)==0 ) continue; |
| 843 | 866 | if( zName[0]!='@' ){ |
| 867 | + db_unprotect(PROTECT_CONFIG); | |
| 844 | 868 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 869 | + db_protect_pop(); | |
| 845 | 870 | }else if( fossil_strcmp(zName,"@user")==0 ){ |
| 871 | + db_unprotect(PROTECT_USER); | |
| 846 | 872 | db_multi_exec("DELETE FROM user"); |
| 873 | + db_protect_pop(); | |
| 847 | 874 | db_create_default_users(0, 0); |
| 848 | 875 | }else if( fossil_strcmp(zName,"@concealed")==0 ){ |
| 849 | 876 | db_multi_exec("DELETE FROM concealed"); |
| 850 | 877 | }else if( fossil_strcmp(zName,"@shun")==0 ){ |
| 851 | 878 | db_multi_exec("DELETE FROM shun"); |
| @@ -1053,10 +1080,11 @@ | ||
| 1053 | 1080 | }else if( zBlob ){ |
| 1054 | 1081 | blob_read_from_file(&x, zBlob, ExtFILE); |
| 1055 | 1082 | }else{ |
| 1056 | 1083 | blob_init(&x,g.argv[3],-1); |
| 1057 | 1084 | } |
| 1085 | + db_unprotect(PROTECT_CONFIG); | |
| 1058 | 1086 | db_prepare(&ins, |
| 1059 | 1087 | "REPLACE INTO config(name,value,mtime)" |
| 1060 | 1088 | "VALUES(%Q,:val,now())", zVar); |
| 1061 | 1089 | if( zBlob ){ |
| 1062 | 1090 | db_bind_blob(&ins, ":val", &x); |
| @@ -1063,7 +1091,8 @@ | ||
| 1063 | 1091 | }else{ |
| 1064 | 1092 | db_bind_text(&ins, ":val", blob_str(&x)); |
| 1065 | 1093 | } |
| 1066 | 1094 | db_step(&ins); |
| 1067 | 1095 | db_finalize(&ins); |
| 1096 | + db_protect_pop(); | |
| 1068 | 1097 | blob_reset(&x); |
| 1069 | 1098 | } |
| 1070 | 1099 |
| --- 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 @@ | ||
| 80 | 80 | }.bind(null,this.id),400); |
| 81 | 81 | } |
| 82 | 82 | /* Create a temporary <textarea> element and copy the contents to clipboard. */ |
| 83 | 83 | function copyTextToClipboard(text){ |
| 84 | 84 | if( window.clipboardData && window.clipboardData.setData ){ |
| 85 | - clipboardData.setData('Text',text); | |
| 85 | + window.clipboardData.setData('Text',text); | |
| 86 | 86 | }else{ |
| 87 | 87 | var x = document.createElement("textarea"); |
| 88 | 88 | x.style.position = 'fixed'; |
| 89 | 89 | x.value = text; |
| 90 | 90 | document.body.appendChild(x); |
| 91 | 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 | 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 |
M
src/db.c
+327
-120
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -69,10 +69,11 @@ | ||
| 69 | 69 | #endif /* INTERFACE */ |
| 70 | 70 | const struct Stmt empty_Stmt = empty_Stmt_m; |
| 71 | 71 | |
| 72 | 72 | /* |
| 73 | 73 | ** Call this routine when a database error occurs. |
| 74 | +** This routine throws a fatal error. It does not return. | |
| 74 | 75 | */ |
| 75 | 76 | static void db_err(const char *zFormat, ...){ |
| 76 | 77 | va_list ap; |
| 77 | 78 | char *z; |
| 78 | 79 | va_start(ap, zFormat); |
| @@ -113,10 +114,11 @@ | ||
| 113 | 114 | /* |
| 114 | 115 | ** All static variable that a used by only this file are gathered into |
| 115 | 116 | ** the following structure. |
| 116 | 117 | */ |
| 117 | 118 | static struct DbLocalData { |
| 119 | + unsigned protectMask; /* Prevent changes to database */ | |
| 118 | 120 | int nBegin; /* Nesting depth of BEGIN */ |
| 119 | 121 | int doRollback; /* True to force a rollback */ |
| 120 | 122 | int nCommitHook; /* Number of commit hooks */ |
| 121 | 123 | int wrTxn; /* Outer-most TNX is a write */ |
| 122 | 124 | Stmt *pAllStmt; /* List of all unfinalized statements */ |
| @@ -133,11 +135,16 @@ | ||
| 133 | 135 | const char *zStartFile; /* File in which transaction was started */ |
| 134 | 136 | int iStartLine; /* Line of zStartFile where transaction started */ |
| 135 | 137 | int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); |
| 136 | 138 | void *pAuthArg; /* Argument to the authorizer */ |
| 137 | 139 | 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, }; | |
| 139 | 146 | |
| 140 | 147 | /* |
| 141 | 148 | ** Arrange for the given file to be deleted on a failure. |
| 142 | 149 | */ |
| 143 | 150 | void db_delete_on_failure(const char *zFilename){ |
| @@ -241,17 +248,19 @@ | ||
| 241 | 248 | db.nBegin--; |
| 242 | 249 | if( db.nBegin==0 ){ |
| 243 | 250 | int i; |
| 244 | 251 | if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){ |
| 245 | 252 | i = 0; |
| 253 | + db_protect_only(PROTECT_SENSITIVE); | |
| 246 | 254 | while( db.nBeforeCommit ){ |
| 247 | 255 | db.nBeforeCommit--; |
| 248 | 256 | sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0); |
| 249 | 257 | sqlite3_free(db.azBeforeCommit[i]); |
| 250 | 258 | i++; |
| 251 | 259 | } |
| 252 | 260 | leaf_do_pending_checks(); |
| 261 | + db_protect_pop(); | |
| 253 | 262 | } |
| 254 | 263 | for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){ |
| 255 | 264 | int rc = db.aHook[i].xHook(); |
| 256 | 265 | if( rc ){ |
| 257 | 266 | db.doRollback = 1; |
| @@ -319,10 +328,205 @@ | ||
| 319 | 328 | } |
| 320 | 329 | db.aHook[db.nCommitHook].sequence = sequence; |
| 321 | 330 | db.aHook[db.nCommitHook].xHook = x; |
| 322 | 331 | db.nCommitHook++; |
| 323 | 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 | +} | |
| 324 | 528 | |
| 325 | 529 | /* |
| 326 | 530 | ** Set or unset the query authorizer callback function |
| 327 | 531 | */ |
| 328 | 532 | void db_set_authorizer( |
| @@ -331,23 +535,22 @@ | ||
| 331 | 535 | const char *zName /* for tracing */ |
| 332 | 536 | ){ |
| 333 | 537 | if( db.xAuth ){ |
| 334 | 538 | fossil_panic("multiple active db_set_authorizer() calls"); |
| 335 | 539 | } |
| 336 | - if( g.db ) sqlite3_set_authorizer(g.db, xAuth, pArg); | |
| 337 | 540 | db.xAuth = xAuth; |
| 338 | 541 | db.pAuthArg = pArg; |
| 339 | 542 | db.zAuthName = zName; |
| 340 | 543 | if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName); |
| 341 | 544 | } |
| 342 | 545 | void db_clear_authorizer(void){ |
| 343 | 546 | if( db.zAuthName && g.fSqlTrace ){ |
| 344 | 547 | fossil_trace("-- discontinue authorizer %s\n", db.zAuthName); |
| 345 | 548 | } |
| 346 | - if( g.db ) sqlite3_set_authorizer(g.db, 0, 0); | |
| 347 | 549 | db.xAuth = 0; |
| 348 | 550 | db.pAuthArg = 0; |
| 551 | + db.zAuthName = 0; | |
| 349 | 552 | } |
| 350 | 553 | |
| 351 | 554 | #if INTERFACE |
| 352 | 555 | /* |
| 353 | 556 | ** Possible flags to db_vprepare |
| @@ -363,21 +566,24 @@ | ||
| 363 | 566 | */ |
| 364 | 567 | int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){ |
| 365 | 568 | int rc; |
| 366 | 569 | int prepFlags = 0; |
| 367 | 570 | char *zSql; |
| 571 | + const char *zExtra = 0; | |
| 368 | 572 | blob_zero(&pStmt->sql); |
| 369 | 573 | blob_vappendf(&pStmt->sql, zFormat, ap); |
| 370 | 574 | va_end(ap); |
| 371 | 575 | zSql = blob_str(&pStmt->sql); |
| 372 | 576 | db.nPrepare++; |
| 373 | 577 | if( flags & DB_PREPARE_PERSISTENT ){ |
| 374 | 578 | prepFlags = SQLITE_PREPARE_PERSISTENT; |
| 375 | 579 | } |
| 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); | |
| 377 | 581 | if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){ |
| 378 | 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); | |
| 379 | 585 | } |
| 380 | 586 | pStmt->pNext = db.pAllStmt; |
| 381 | 587 | pStmt->pPrev = 0; |
| 382 | 588 | if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt; |
| 383 | 589 | db.pAllStmt = pStmt; |
| @@ -640,10 +846,11 @@ | ||
| 640 | 846 | return rc; |
| 641 | 847 | } |
| 642 | 848 | |
| 643 | 849 | /* |
| 644 | 850 | ** COMMAND: test-db-exec-error |
| 851 | +** Usage: %fossil test-db-exec-error | |
| 645 | 852 | ** |
| 646 | 853 | ** Invoke the db_exec() interface with an erroneous SQL statement |
| 647 | 854 | ** in order to verify the error handling logic. |
| 648 | 855 | */ |
| 649 | 856 | void db_test_db_exec_cmd(void){ |
| @@ -650,10 +857,27 @@ | ||
| 650 | 857 | Stmt err; |
| 651 | 858 | db_find_and_open_repository(0,0); |
| 652 | 859 | db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);"); |
| 653 | 860 | db_exec(&err); |
| 654 | 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 | +} | |
| 655 | 879 | |
| 656 | 880 | /* |
| 657 | 881 | ** Print the output of one or more SQL queries on standard output. |
| 658 | 882 | ** This routine is used for debugging purposes only. |
| 659 | 883 | */ |
| @@ -880,13 +1104,10 @@ | ||
| 880 | 1104 | const char *zSql; |
| 881 | 1105 | va_list ap; |
| 882 | 1106 | |
| 883 | 1107 | xdb = db_open(zFileName ? zFileName : ":memory:"); |
| 884 | 1108 | sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0); |
| 885 | - if( db.xAuth ){ | |
| 886 | - sqlite3_set_authorizer(xdb, db.xAuth, db.pAuthArg); | |
| 887 | - } | |
| 888 | 1109 | rc = sqlite3_exec(xdb, zSchema, 0, 0, 0); |
| 889 | 1110 | if( rc!=SQLITE_OK ){ |
| 890 | 1111 | db_err("%s", sqlite3_errmsg(xdb)); |
| 891 | 1112 | } |
| 892 | 1113 | va_start(ap, zSchema); |
| @@ -1092,10 +1313,37 @@ | ||
| 1092 | 1313 | } |
| 1093 | 1314 | strcpy(zOut, zTemp = obscure((char*)zIn)); |
| 1094 | 1315 | fossil_free(zTemp); |
| 1095 | 1316 | sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free); |
| 1096 | 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 | +} | |
| 1097 | 1345 | |
| 1098 | 1346 | /* |
| 1099 | 1347 | ** Register the SQL functions that are useful both to the internal |
| 1100 | 1348 | ** representation and to the "fossil sql" command. |
| 1101 | 1349 | */ |
| @@ -1122,10 +1370,12 @@ | ||
| 1122 | 1370 | alert_find_emailaddr_func, 0, 0); |
| 1123 | 1371 | sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0, |
| 1124 | 1372 | alert_display_name_func, 0, 0); |
| 1125 | 1373 | sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0, |
| 1126 | 1374 | db_obscure, 0, 0); |
| 1375 | + sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0, | |
| 1376 | + db_protected_setting_func, 0, 0); | |
| 1127 | 1377 | } |
| 1128 | 1378 | |
| 1129 | 1379 | #if USE_SEE |
| 1130 | 1380 | /* |
| 1131 | 1381 | ** This is a pointer to the saved database encryption key string. |
| @@ -1380,10 +1630,11 @@ | ||
| 1380 | 1630 | if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0); |
| 1381 | 1631 | db_add_aux_functions(db); |
| 1382 | 1632 | re_add_sql_func(db); /* The REGEXP operator */ |
| 1383 | 1633 | foci_register(db); /* The "files_of_checkin" virtual table */ |
| 1384 | 1634 | sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc); |
| 1635 | + sqlite3_set_authorizer(db, db_top_authorizer, db); | |
| 1385 | 1636 | return db; |
| 1386 | 1637 | } |
| 1387 | 1638 | |
| 1388 | 1639 | |
| 1389 | 1640 | /* |
| @@ -1815,27 +2066,18 @@ | ||
| 1815 | 2066 | zRepo = db_lget("repository", 0); |
| 1816 | 2067 | if( zRepo && !file_is_absolute_path(zRepo) ){ |
| 1817 | 2068 | char * zFree = zRepo; |
| 1818 | 2069 | zRepo = mprintf("%s%s", g.zLocalRoot, zRepo); |
| 1819 | 2070 | fossil_free(zFree); |
| 2071 | + zFree = zRepo; | |
| 2072 | + zRepo = file_canonical_name_dup(zFree); | |
| 2073 | + fossil_free(zFree); | |
| 1820 | 2074 | } |
| 1821 | 2075 | } |
| 1822 | 2076 | return zRepo; |
| 1823 | 2077 | } |
| 1824 | 2078 | |
| 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 | 2079 | /* |
| 1838 | 2080 | ** Returns non-zero if support for symlinks is currently enabled. |
| 1839 | 2081 | */ |
| 1840 | 2082 | int db_allow_symlinks(void){ |
| 1841 | 2083 | return g.allowSymlinks; |
| @@ -1877,13 +2119,14 @@ | ||
| 1877 | 2119 | g.zRepositoryName = mprintf("%s", zDbName); |
| 1878 | 2120 | db_open_or_attach(g.zRepositoryName, "repository"); |
| 1879 | 2121 | g.repositoryOpen = 1; |
| 1880 | 2122 | sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION, |
| 1881 | 2123 | &g.iRepoDataVers); |
| 2124 | + | |
| 1882 | 2125 | /* 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 | + | |
| 1885 | 2128 | g.zAuxSchema = db_get("aux-schema",""); |
| 1886 | 2129 | g.eHashPolicy = db_get_int("hash-policy",-1); |
| 1887 | 2130 | if( g.eHashPolicy<0 ){ |
| 1888 | 2131 | g.eHashPolicy = hname_default_policy(); |
| 1889 | 2132 | db_set_int("hash-policy", g.eHashPolicy, 0); |
| @@ -2169,11 +2412,13 @@ | ||
| 2169 | 2412 | */ |
| 2170 | 2413 | if( db_database_slot("localdb")>=0 ){ |
| 2171 | 2414 | int nFree = db_int(0, "PRAGMA localdb.freelist_count"); |
| 2172 | 2415 | int nTotal = db_int(0, "PRAGMA localdb.page_count"); |
| 2173 | 2416 | if( nFree>nTotal/4 ){ |
| 2417 | + db_unprotect(PROTECT_ALL); | |
| 2174 | 2418 | db_multi_exec("VACUUM localdb;"); |
| 2419 | + db_protect_pop(); | |
| 2175 | 2420 | } |
| 2176 | 2421 | } |
| 2177 | 2422 | |
| 2178 | 2423 | if( g.db ){ |
| 2179 | 2424 | int rc; |
| @@ -2187,10 +2432,11 @@ | ||
| 2187 | 2432 | } |
| 2188 | 2433 | g.db = 0; |
| 2189 | 2434 | } |
| 2190 | 2435 | g.repositoryOpen = 0; |
| 2191 | 2436 | g.localOpen = 0; |
| 2437 | + db.bProtectTriggers = 0; | |
| 2192 | 2438 | assert( g.dbConfig==0 ); |
| 2193 | 2439 | assert( g.zConfigDbName==0 ); |
| 2194 | 2440 | backoffice_run_if_needed(); |
| 2195 | 2441 | } |
| 2196 | 2442 | |
| @@ -2249,10 +2495,11 @@ | ||
| 2249 | 2495 | zUser = fossil_getenv("USERNAME"); |
| 2250 | 2496 | } |
| 2251 | 2497 | if( zUser==0 ){ |
| 2252 | 2498 | zUser = "root"; |
| 2253 | 2499 | } |
| 2500 | + db_unprotect(PROTECT_USER); | |
| 2254 | 2501 | db_multi_exec( |
| 2255 | 2502 | "INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser |
| 2256 | 2503 | ); |
| 2257 | 2504 | db_multi_exec( |
| 2258 | 2505 | "UPDATE user SET cap='s', pw=%Q" |
| @@ -2268,10 +2515,11 @@ | ||
| 2268 | 2515 | " VALUES('developer','','ei','Dev');" |
| 2269 | 2516 | "INSERT OR IGNORE INTO user(login,pw,cap,info)" |
| 2270 | 2517 | " VALUES('reader','','kptw','Reader');" |
| 2271 | 2518 | ); |
| 2272 | 2519 | } |
| 2520 | + db_protect_pop(); | |
| 2273 | 2521 | } |
| 2274 | 2522 | |
| 2275 | 2523 | /* |
| 2276 | 2524 | ** Return a pointer to a string that contains the RHS of an IN operator |
| 2277 | 2525 | ** that will select CONFIG table names that are in the list of control |
| @@ -2319,10 +2567,11 @@ | ||
| 2319 | 2567 | ){ |
| 2320 | 2568 | char *zDate; |
| 2321 | 2569 | Blob hash; |
| 2322 | 2570 | Blob manifest; |
| 2323 | 2571 | |
| 2572 | + db_unprotect(PROTECT_ALL); | |
| 2324 | 2573 | db_set("content-schema", CONTENT_SCHEMA, 0); |
| 2325 | 2574 | db_set("aux-schema", AUX_SCHEMA_MAX, 0); |
| 2326 | 2575 | db_set("rebuilt", get_version(), 0); |
| 2327 | 2576 | db_set("admin-log", "1", 0); |
| 2328 | 2577 | db_set("access-log", "1", 0); |
| @@ -2377,10 +2626,11 @@ | ||
| 2377 | 2626 | " photo = (SELECT u2.photo FROM settingSrc.user u2" |
| 2378 | 2627 | " WHERE u2.login = user.login)" |
| 2379 | 2628 | " WHERE user.login IN ('anonymous','nobody','developer','reader');" |
| 2380 | 2629 | ); |
| 2381 | 2630 | } |
| 2631 | + db_protect_pop(); | |
| 2382 | 2632 | |
| 2383 | 2633 | if( zInitialDate ){ |
| 2384 | 2634 | int rid; |
| 2385 | 2635 | blob_zero(&manifest); |
| 2386 | 2636 | blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n"); |
| @@ -2873,10 +3123,12 @@ | ||
| 2873 | 3123 | z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z); |
| 2874 | 3124 | } |
| 2875 | 3125 | return z; |
| 2876 | 3126 | } |
| 2877 | 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); | |
| 2878 | 3130 | db_begin_transaction(); |
| 2879 | 3131 | if( globalFlag ){ |
| 2880 | 3132 | db_swap_connections(); |
| 2881 | 3133 | db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)", |
| 2882 | 3134 | zName, zValue); |
| @@ -2887,13 +3139,15 @@ | ||
| 2887 | 3139 | } |
| 2888 | 3140 | if( globalFlag && g.repositoryOpen ){ |
| 2889 | 3141 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 2890 | 3142 | } |
| 2891 | 3143 | db_end_transaction(0); |
| 3144 | + db_protect_pop(); | |
| 2892 | 3145 | } |
| 2893 | 3146 | void db_unset(const char *zName, int globalFlag){ |
| 2894 | 3147 | db_begin_transaction(); |
| 3148 | + db_unprotect(PROTECT_CONFIG); | |
| 2895 | 3149 | if( globalFlag ){ |
| 2896 | 3150 | db_swap_connections(); |
| 2897 | 3151 | db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName); |
| 2898 | 3152 | db_swap_connections(); |
| 2899 | 3153 | }else{ |
| @@ -2900,10 +3154,11 @@ | ||
| 2900 | 3154 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 2901 | 3155 | } |
| 2902 | 3156 | if( globalFlag && g.repositoryOpen ){ |
| 2903 | 3157 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 2904 | 3158 | } |
| 3159 | + db_protect_pop(); | |
| 2905 | 3160 | db_end_transaction(0); |
| 2906 | 3161 | } |
| 2907 | 3162 | int db_is_global(const char *zName){ |
| 2908 | 3163 | int rc = 0; |
| 2909 | 3164 | if( g.zConfigDbName ){ |
| @@ -2933,10 +3188,12 @@ | ||
| 2933 | 3188 | db_swap_connections(); |
| 2934 | 3189 | } |
| 2935 | 3190 | return v; |
| 2936 | 3191 | } |
| 2937 | 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); | |
| 2938 | 3195 | if( globalFlag ){ |
| 2939 | 3196 | db_swap_connections(); |
| 2940 | 3197 | db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)", |
| 2941 | 3198 | zName, value); |
| 2942 | 3199 | db_swap_connections(); |
| @@ -2945,10 +3202,11 @@ | ||
| 2945 | 3202 | zName, value); |
| 2946 | 3203 | } |
| 2947 | 3204 | if( globalFlag && g.repositoryOpen ){ |
| 2948 | 3205 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 2949 | 3206 | } |
| 3207 | + db_protect_pop(); | |
| 2950 | 3208 | } |
| 2951 | 3209 | int db_get_boolean(const char *zName, int dflt){ |
| 2952 | 3210 | char *zVal = db_get(zName, dflt ? "on" : "off"); |
| 2953 | 3211 | if( is_truth(zVal) ){ |
| 2954 | 3212 | dflt = 1; |
| @@ -2956,19 +3214,17 @@ | ||
| 2956 | 3214 | dflt = 0; |
| 2957 | 3215 | } |
| 2958 | 3216 | fossil_free(zVal); |
| 2959 | 3217 | return dflt; |
| 2960 | 3218 | } |
| 2961 | -#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS | |
| 2962 | 3219 | int db_get_versioned_boolean(const char *zName, int dflt){ |
| 2963 | 3220 | char *zVal = db_get_versioned(zName, 0); |
| 2964 | 3221 | if( zVal==0 ) return dflt; |
| 2965 | 3222 | if( is_truth(zVal) ) return 1; |
| 2966 | 3223 | if( is_false(zVal) ) return 0; |
| 2967 | 3224 | return dflt; |
| 2968 | 3225 | } |
| 2969 | -#endif /* FOSSIL_LEGACY_ALLOW_SYMLINKS */ | |
| 2970 | 3226 | char *db_lget(const char *zName, const char *zDefault){ |
| 2971 | 3227 | return db_text(zDefault, |
| 2972 | 3228 | "SELECT value FROM vvar WHERE name=%Q", zName); |
| 2973 | 3229 | } |
| 2974 | 3230 | void db_lset(const char *zName, const char *zValue){ |
| @@ -3076,24 +3332,28 @@ | ||
| 3076 | 3332 | } |
| 3077 | 3333 | file_canonical_name(zName, &full, 0); |
| 3078 | 3334 | (void)filename_collation(); /* Initialize before connection swap */ |
| 3079 | 3335 | db_swap_connections(); |
| 3080 | 3336 | zRepoSetting = mprintf("repo:%q", blob_str(&full)); |
| 3337 | + | |
| 3338 | + db_unprotect(PROTECT_CONFIG); | |
| 3081 | 3339 | db_multi_exec( |
| 3082 | 3340 | "DELETE FROM global_config WHERE name %s = %Q;", |
| 3083 | 3341 | filename_collation(), zRepoSetting |
| 3084 | 3342 | ); |
| 3085 | 3343 | db_multi_exec( |
| 3086 | 3344 | "INSERT OR IGNORE INTO global_config(name,value)" |
| 3087 | 3345 | "VALUES(%Q,1);", |
| 3088 | 3346 | zRepoSetting |
| 3089 | 3347 | ); |
| 3348 | + db_protect_pop(); | |
| 3090 | 3349 | fossil_free(zRepoSetting); |
| 3091 | 3350 | if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){ |
| 3092 | 3351 | Blob localRoot; |
| 3093 | 3352 | file_canonical_name(g.zLocalRoot, &localRoot, 1); |
| 3094 | 3353 | zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot)); |
| 3354 | + db_unprotect(PROTECT_CONFIG); | |
| 3095 | 3355 | db_multi_exec( |
| 3096 | 3356 | "DELETE FROM global_config WHERE name %s = %Q;", |
| 3097 | 3357 | filename_collation(), zCkoutSetting |
| 3098 | 3358 | ); |
| 3099 | 3359 | db_multi_exec( |
| @@ -3109,10 +3369,11 @@ | ||
| 3109 | 3369 | db_optional_sql("repository", |
| 3110 | 3370 | "REPLACE INTO config(name,value,mtime)" |
| 3111 | 3371 | "VALUES(%Q,1,now());", |
| 3112 | 3372 | zCkoutSetting |
| 3113 | 3373 | ); |
| 3374 | + db_protect_pop(); | |
| 3114 | 3375 | fossil_free(zCkoutSetting); |
| 3115 | 3376 | blob_reset(&localRoot); |
| 3116 | 3377 | }else{ |
| 3117 | 3378 | db_swap_connections(); |
| 3118 | 3379 | } |
| @@ -3147,11 +3408,11 @@ | ||
| 3147 | 3408 | ** |
| 3148 | 3409 | ** Options: |
| 3149 | 3410 | ** --empty Initialize checkout as being empty, but still connected |
| 3150 | 3411 | ** with the local repository. If you commit this checkout, |
| 3151 | 3412 | ** 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 | |
| 3153 | 3414 | ** not empty. |
| 3154 | 3415 | ** --force-missing Force opening a repository with missing content |
| 3155 | 3416 | ** --keep Only modify the manifest and manifest.uuid files |
| 3156 | 3417 | ** --nested Allow opening a repository inside an opened checkout |
| 3157 | 3418 | ** --repodir DIR If REPOSITORY is a URI that will be cloned, store |
| @@ -3167,13 +3428,10 @@ | ||
| 3167 | 3428 | void cmd_open(void){ |
| 3168 | 3429 | int emptyFlag; |
| 3169 | 3430 | int keepFlag; |
| 3170 | 3431 | int forceMissingFlag; |
| 3171 | 3432 | int allowNested; |
| 3172 | -#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS | |
| 3173 | - int allowSymlinks; | |
| 3174 | -#endif | |
| 3175 | 3433 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 3176 | 3434 | int bForce = 0; /* --force. Open even if non-empty dir */ |
| 3177 | 3435 | static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 }; |
| 3178 | 3436 | const char *zWorkDir; /* --workdir value */ |
| 3179 | 3437 | const char *zRepo = 0; /* Name of the repository file */ |
| @@ -3187,11 +3445,11 @@ | ||
| 3187 | 3445 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 3188 | 3446 | allowNested = find_option("nested",0,0)!=0; |
| 3189 | 3447 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 3190 | 3448 | zWorkDir = find_option("workdir",0,1); |
| 3191 | 3449 | zRepoDir = find_option("repodir",0,1); |
| 3192 | - bForce = find_option("force",0,0)!=0; | |
| 3450 | + bForce = find_option("force","f",0)!=0; | |
| 3193 | 3451 | zPwd = file_getcwd(0,0); |
| 3194 | 3452 | |
| 3195 | 3453 | |
| 3196 | 3454 | /* We should be done with options.. */ |
| 3197 | 3455 | verify_all_options(); |
| @@ -3226,11 +3484,11 @@ | ||
| 3226 | 3484 | fossil_fatal("unable to make %s the working directory", zWorkDir); |
| 3227 | 3485 | } |
| 3228 | 3486 | } |
| 3229 | 3487 | if( keepFlag==0 && bForce==0 && file_directory_size(".", 0, 1)>0 ){ |
| 3230 | 3488 | 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)); | |
| 3232 | 3490 | } |
| 3233 | 3491 | |
| 3234 | 3492 | if( db_open_local_v2(0, allowNested) ){ |
| 3235 | 3493 | fossil_fatal("there is already an open tree at %s", g.zLocalRoot); |
| 3236 | 3494 | } |
| @@ -3280,25 +3538,10 @@ | ||
| 3280 | 3538 | }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){ |
| 3281 | 3539 | g.zOpenRevision = db_get("main-branch", 0); |
| 3282 | 3540 | } |
| 3283 | 3541 | } |
| 3284 | 3542 | |
| 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 | 3543 | |
| 3301 | 3544 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 3302 | 3545 | # define LOCALDB_NAME "./_FOSSIL_" |
| 3303 | 3546 | #else |
| 3304 | 3547 | # define LOCALDB_NAME "./.fslckout" |
| @@ -3308,28 +3551,10 @@ | ||
| 3308 | 3551 | "COMMIT; PRAGMA journal_mode=WAL; BEGIN;", |
| 3309 | 3552 | #endif |
| 3310 | 3553 | (char*)0); |
| 3311 | 3554 | db_delete_on_failure(LOCALDB_NAME); |
| 3312 | 3555 | 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 | 3556 | db_lset("repository", zRepo); |
| 3332 | 3557 | db_record_repository_filename(zRepo); |
| 3333 | 3558 | db_set_checkout(0); |
| 3334 | 3559 | azNewArgv[0] = g.argv[0]; |
| 3335 | 3560 | g.argv = azNewArgv; |
| @@ -3418,12 +3643,13 @@ | ||
| 3418 | 3643 | const char *name; /* Name of the setting */ |
| 3419 | 3644 | const char *var; /* Internal variable name used by db_set() */ |
| 3420 | 3645 | int width; /* Width of display. 0 for boolean values and |
| 3421 | 3646 | ** negative for values which should not appear |
| 3422 | 3647 | ** 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 */ | |
| 3425 | 3651 | const char *def; /* Default value */ |
| 3426 | 3652 | }; |
| 3427 | 3653 | #endif /* INTERFACE */ |
| 3428 | 3654 | |
| 3429 | 3655 | /* |
| @@ -3437,50 +3663,29 @@ | ||
| 3437 | 3663 | ** SETTING: admin-log boolean default=off |
| 3438 | 3664 | ** |
| 3439 | 3665 | ** When the admin-log setting is enabled, configuration changes are recorded |
| 3440 | 3666 | ** in the "admin_log" table of the repository. |
| 3441 | 3667 | */ |
| 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 | +*/ | |
| 3482 | 3687 | /* |
| 3483 | 3688 | ** SETTING: auto-captcha boolean default=on variable=autocaptcha |
| 3484 | 3689 | ** If enabled, the /login page provides a button that will automatically |
| 3485 | 3690 | ** fill in the captcha password. This makes things easier for human users, |
| 3486 | 3691 | ** at the expense of also making logins easier for malicious robots. |
| @@ -3530,11 +3735,11 @@ | ||
| 3530 | 3735 | ** there is no cron job periodically running "fossil backoffice", |
| 3531 | 3736 | ** email notifications and other work normally done by the |
| 3532 | 3737 | ** backoffice will not occur. |
| 3533 | 3738 | */ |
| 3534 | 3739 | /* |
| 3535 | -** SETTING: backoffice-logfile width=40 | |
| 3740 | +** SETTING: backoffice-logfile width=40 sensitive | |
| 3536 | 3741 | ** If backoffice-logfile is not an empty string and is a valid |
| 3537 | 3742 | ** filename, then a one-line message is appended to that file |
| 3538 | 3743 | ** every time the backoffice runs. This can be used for debugging, |
| 3539 | 3744 | ** to ensure that backoffice is running appropriately. |
| 3540 | 3745 | */ |
| @@ -3607,11 +3812,11 @@ | ||
| 3607 | 3812 | /* |
| 3608 | 3813 | ** SETTING: crnl-glob width=40 versionable block-text |
| 3609 | 3814 | ** This is an alias for the crlf-glob setting. |
| 3610 | 3815 | */ |
| 3611 | 3816 | /* |
| 3612 | -** SETTING: default-perms width=16 default=u | |
| 3817 | +** SETTING: default-perms width=16 default=u sensitive | |
| 3613 | 3818 | ** Permissions given automatically to new users. For more |
| 3614 | 3819 | ** information on permissions see the Users page in Server |
| 3615 | 3820 | ** Administration of the HTTP UI. |
| 3616 | 3821 | */ |
| 3617 | 3822 | /* |
| @@ -3619,11 +3824,11 @@ | ||
| 3619 | 3824 | ** If enabled, permit files that may be binary |
| 3620 | 3825 | ** or that match the "binary-glob" setting to be used with |
| 3621 | 3826 | ** external diff programs. If disabled, skip these files. |
| 3622 | 3827 | */ |
| 3623 | 3828 | /* |
| 3624 | -** SETTING: diff-command width=40 | |
| 3829 | +** SETTING: diff-command width=40 sensitive | |
| 3625 | 3830 | ** The value is an external command to run when performing a diff. |
| 3626 | 3831 | ** If undefined, the internal text diff will be used. |
| 3627 | 3832 | */ |
| 3628 | 3833 | /* |
| 3629 | 3834 | ** SETTING: dont-push boolean default=off |
| @@ -3634,11 +3839,11 @@ | ||
| 3634 | 3839 | /* |
| 3635 | 3840 | ** SETTING: dotfiles boolean versionable default=off |
| 3636 | 3841 | ** If enabled, include --dotfiles option for all compatible commands. |
| 3637 | 3842 | */ |
| 3638 | 3843 | /* |
| 3639 | -** SETTING: editor width=32 | |
| 3844 | +** SETTING: editor width=32 sensitive | |
| 3640 | 3845 | ** The value is an external command that will launch the |
| 3641 | 3846 | ** text editor command used for check-in comments. |
| 3642 | 3847 | */ |
| 3643 | 3848 | /* |
| 3644 | 3849 | ** SETTING: empty-dirs width=40 versionable block-text |
| @@ -3677,16 +3882,16 @@ | ||
| 3677 | 3882 | ** An empty list prohibits editing via that page. Note that |
| 3678 | 3883 | ** it cannot edit binary files, so the list should not |
| 3679 | 3884 | ** contain any globs for, e.g., images or PDFs. |
| 3680 | 3885 | */ |
| 3681 | 3886 | /* |
| 3682 | -** SETTING: gdiff-command width=40 default=gdiff | |
| 3887 | +** SETTING: gdiff-command width=40 default=gdiff sensitive | |
| 3683 | 3888 | ** The value is an external command to run when performing a graphical |
| 3684 | 3889 | ** diff. If undefined, text diff will be used. |
| 3685 | 3890 | */ |
| 3686 | 3891 | /* |
| 3687 | -** SETTING: gmerge-command width=40 | |
| 3892 | +** SETTING: gmerge-command width=40 sensitive | |
| 3688 | 3893 | ** The value is a graphical merge conflict resolver command operating |
| 3689 | 3894 | ** on four files. Examples: |
| 3690 | 3895 | ** |
| 3691 | 3896 | ** kdiff3 "%baseline" "%original" "%merge" -o "%output" |
| 3692 | 3897 | ** xxdiff "%original" "%baseline" "%merge" -M "%output" |
| @@ -3817,11 +4022,11 @@ | ||
| 3817 | 4022 | ** the associated files within the checkout -AND- the "rm" |
| 3818 | 4023 | ** and "delete" commands will also remove the associated |
| 3819 | 4024 | ** files from within the checkout. |
| 3820 | 4025 | */ |
| 3821 | 4026 | /* |
| 3822 | -** SETTING: pgp-command width=40 | |
| 4027 | +** SETTING: pgp-command width=40 sensitive | |
| 3823 | 4028 | ** Command used to clear-sign manifests at check-in. |
| 3824 | 4029 | ** Default value is "gpg --clearsign -o" |
| 3825 | 4030 | */ |
| 3826 | 4031 | /* |
| 3827 | 4032 | ** SETTING: forbid-delta-manifests boolean default=off |
| @@ -3877,22 +4082,22 @@ | ||
| 3877 | 4082 | ** |
| 3878 | 4083 | ** If repolist-skin has a value of 2, then the repository is omitted from |
| 3879 | 4084 | ** the list in use cases 1 through 4, but not for 5 and 6. |
| 3880 | 4085 | */ |
| 3881 | 4086 | /* |
| 3882 | -** SETTING: self-register boolean default=off | |
| 4087 | +** SETTING: self-register boolean default=off sensitive | |
| 3883 | 4088 | ** Allow users to register themselves through the HTTP UI. |
| 3884 | 4089 | ** This is useful if you want to see other names than |
| 3885 | 4090 | ** "Anonymous" in e.g. ticketing system. On the other hand |
| 3886 | 4091 | ** users can not be deleted. |
| 3887 | 4092 | */ |
| 3888 | 4093 | /* |
| 3889 | -** SETTING: ssh-command width=40 | |
| 4094 | +** SETTING: ssh-command width=40 sensitive | |
| 3890 | 4095 | ** The command used to talk to a remote machine with the "ssh://" protocol. |
| 3891 | 4096 | */ |
| 3892 | 4097 | /* |
| 3893 | -** SETTING: ssl-ca-location width=40 | |
| 4098 | +** SETTING: ssl-ca-location width=40 sensitive | |
| 3894 | 4099 | ** The full pathname to a file containing PEM encoded |
| 3895 | 4100 | ** CA root certificates, or a directory of certificates |
| 3896 | 4101 | ** with filenames formed from the certificate hashes as |
| 3897 | 4102 | ** required by OpenSSL. |
| 3898 | 4103 | ** |
| @@ -3902,11 +4107,11 @@ | ||
| 3902 | 4107 | ** Checking your platform behaviour is required if the |
| 3903 | 4108 | ** exact contents of the CA root is critical for your |
| 3904 | 4109 | ** application. |
| 3905 | 4110 | */ |
| 3906 | 4111 | /* |
| 3907 | -** SETTING: ssl-identity width=40 | |
| 4112 | +** SETTING: ssl-identity width=40 sensitive | |
| 3908 | 4113 | ** The full pathname to a file containing a certificate |
| 3909 | 4114 | ** and private key in PEM format. Create by concatenating |
| 3910 | 4115 | ** the certificate and private key files. |
| 3911 | 4116 | ** |
| 3912 | 4117 | ** This identity will be presented to SSL servers to |
| @@ -3913,33 +4118,33 @@ | ||
| 3913 | 4118 | ** authenticate this client, in addition to the normal |
| 3914 | 4119 | ** password authentication. |
| 3915 | 4120 | */ |
| 3916 | 4121 | #ifdef FOSSIL_ENABLE_TCL |
| 3917 | 4122 | /* |
| 3918 | -** SETTING: tcl boolean default=off | |
| 4123 | +** SETTING: tcl boolean default=off sensitive | |
| 3919 | 4124 | ** If enabled Tcl integration commands will be added to the TH1 |
| 3920 | 4125 | ** interpreter, allowing arbitrary Tcl expressions and |
| 3921 | 4126 | ** scripts to be evaluated from TH1. Additionally, the Tcl |
| 3922 | 4127 | ** interpreter will be able to evaluate arbitrary TH1 |
| 3923 | 4128 | ** expressions and scripts. |
| 3924 | 4129 | */ |
| 3925 | 4130 | /* |
| 3926 | -** SETTING: tcl-setup width=40 block-text | |
| 4131 | +** SETTING: tcl-setup width=40 block-text sensitive | |
| 3927 | 4132 | ** This is the setup script to be evaluated after creating |
| 3928 | 4133 | ** and initializing the Tcl interpreter. By default, this |
| 3929 | 4134 | ** is empty and no extra setup is performed. |
| 3930 | 4135 | */ |
| 3931 | 4136 | #endif /* FOSSIL_ENABLE_TCL */ |
| 3932 | 4137 | /* |
| 3933 | -** SETTING: tclsh width=80 default=tclsh | |
| 4138 | +** SETTING: tclsh width=80 default=tclsh sensitive | |
| 3934 | 4139 | ** Name of the external TCL interpreter used for such things |
| 3935 | 4140 | ** as running the GUI diff viewer launched by the --tk option |
| 3936 | 4141 | ** of the various "diff" commands. |
| 3937 | 4142 | */ |
| 3938 | 4143 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 3939 | 4144 | /* |
| 3940 | -** SETTING: th1-docs boolean default=off | |
| 4145 | +** SETTING: th1-docs boolean default=off sensitive | |
| 3941 | 4146 | ** If enabled, this allows embedded documentation files to contain |
| 3942 | 4147 | ** arbitrary TH1 scripts that are evaluated on the server. If native |
| 3943 | 4148 | ** Tcl integration is also enabled, this setting has the |
| 3944 | 4149 | ** potential to allow anybody with check-in privileges to |
| 3945 | 4150 | ** do almost anything that the associated operating system |
| @@ -3992,11 +4197,11 @@ | ||
| 3992 | 4197 | ** of a "fossil clone" or "fossil sync" command. The |
| 3993 | 4198 | ** default is false, in which case the -u option is |
| 3994 | 4199 | ** needed to clone or sync unversioned files. |
| 3995 | 4200 | */ |
| 3996 | 4201 | /* |
| 3997 | -** SETTING: web-browser width=30 | |
| 4202 | +** SETTING: web-browser width=30 sensitive | |
| 3998 | 4203 | ** A shell command used to launch your preferred |
| 3999 | 4204 | ** web browser when given a URL as an argument. |
| 4000 | 4205 | ** Defaults to "start" on windows, "open" on Mac, |
| 4001 | 4206 | ** and "firefox" on Unix. |
| 4002 | 4207 | */ |
| @@ -4118,11 +4323,13 @@ | ||
| 4118 | 4323 | fossil_fatal("cannot set 'manifest' globally"); |
| 4119 | 4324 | } |
| 4120 | 4325 | if( unsetFlag ){ |
| 4121 | 4326 | db_unset(pSetting->name, globalFlag); |
| 4122 | 4327 | }else{ |
| 4328 | + db_protect_only(PROTECT_NONE); | |
| 4123 | 4329 | db_set(pSetting->name, g.argv[3], globalFlag); |
| 4330 | + db_protect_pop(); | |
| 4124 | 4331 | } |
| 4125 | 4332 | if( isManifest && g.localOpen ){ |
| 4126 | 4333 | manifest_to_disk(db_lget_int("checkout", 0)); |
| 4127 | 4334 | } |
| 4128 | 4335 | }else{ |
| 4129 | 4336 |
| --- 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 @@ | ||
| 440 | 440 | content:"'"; |
| 441 | 441 | } |
| 442 | 442 | span.usertype:after { |
| 443 | 443 | content:"'"; |
| 444 | 444 | } |
| 445 | -div.selectedText { | |
| 446 | - font-weight: bold; | |
| 447 | - color: blue; | |
| 448 | - background-color: #d5d5ff; | |
| 449 | - border: 1px blue solid; | |
| 450 | -} | |
| 451 | 445 | p.missingPriv { |
| 452 | 446 | color: blue; |
| 453 | 447 | } |
| 454 | 448 | span.wikiruleHead { |
| 455 | 449 | font-weight: bold; |
| @@ -948,19 +942,23 @@ | ||
| 948 | 942 | .error { |
| 949 | 943 | color: darkred; |
| 950 | 944 | background: yellow; |
| 951 | 945 | } |
| 952 | 946 | .warning { |
| 953 | - color: darkred; | |
| 947 | + color: black; | |
| 954 | 948 | background: yellow; |
| 955 | - opacity: 0.7; | |
| 956 | 949 | } |
| 957 | 950 | .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; | |
| 962 | 960 | } |
| 963 | 961 | input { |
| 964 | 962 | max-width: 95%; |
| 965 | 963 | } |
| 966 | 964 | textarea { |
| @@ -994,21 +992,11 @@ | ||
| 994 | 992 | flex-direction: column; |
| 995 | 993 | border-width: 1px; |
| 996 | 994 | border-style: outset; |
| 997 | 995 | border-color: inherit; |
| 998 | 996 | } |
| 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 { | |
| 1010 | 998 | align-self: stretch; |
| 1011 | 999 | flex: 10 1 auto; |
| 1012 | 1000 | display: block; |
| 1013 | 1001 | border: 0; |
| 1014 | 1002 | padding: 0; |
| @@ -1158,5 +1146,152 @@ | ||
| 1158 | 1146 | .input-with-label > label { |
| 1159 | 1147 | font-weight: initial; |
| 1160 | 1148 | margin: 0 0.25em 0 0.25em; |
| 1161 | 1149 | vertical-align: middle; |
| 1162 | 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 | +} | |
| 1163 | 1298 |
| --- 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 @@ | ||
| 125 | 125 | ** in the count even if it lacks the \n terminator. If an empty string |
| 126 | 126 | ** is specified, the number of lines is zero. For the purposes of this |
| 127 | 127 | ** function, a string is considered empty if it contains no characters |
| 128 | 128 | ** -OR- it contains only NUL characters. |
| 129 | 129 | */ |
| 130 | -static int count_lines( | |
| 130 | +int count_lines( | |
| 131 | 131 | const char *z, |
| 132 | 132 | int n, |
| 133 | 133 | int *pnLine |
| 134 | 134 | ){ |
| 135 | 135 | int nLine; |
| @@ -1566,11 +1566,11 @@ | ||
| 1566 | 1566 | iSYp = iSY; |
| 1567 | 1567 | iEXp = iEX; |
| 1568 | 1568 | iEYp = iEY; |
| 1569 | 1569 | } |
| 1570 | 1570 | } |
| 1571 | - if( iSXb==iEXb && (iE1-iS1)*(iE2-iS2)<400 ){ | |
| 1571 | + if( iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<400 ){ | |
| 1572 | 1572 | /* If no common sequence is found using the hashing heuristic and |
| 1573 | 1573 | ** the input is not too big, use the expensive exact solution */ |
| 1574 | 1574 | optimalLCS(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY); |
| 1575 | 1575 | }else{ |
| 1576 | 1576 | *piSX = iSXb; |
| 1577 | 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 | 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 @@ | ||
| 47 | 47 | ** used for files that are under management by a Fossil repository. ExtFILE |
| 48 | 48 | ** should be used for files that are not under management. SymFILE is for |
| 49 | 49 | ** a few special cases such as the "fossil test-tarball" command when we never |
| 50 | 50 | ** want to follow symlinks. |
| 51 | 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. | |
| 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. | |
| 64 | 63 | */ |
| 65 | 64 | #define ExtFILE 0 /* Always follow symlinks */ |
| 66 | 65 | #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */ |
| 67 | 66 | #define SymFILE 2 /* Never follow symlinks */ |
| 68 | 67 | |
| @@ -134,13 +133,16 @@ | ||
| 134 | 133 | int eFType /* Look at symlink itself if RepoFILE and enabled. */ |
| 135 | 134 | ){ |
| 136 | 135 | int rc; |
| 137 | 136 | void *zMbcs = fossil_utf8_to_path(zFilename, 0); |
| 138 | 137 | #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 */ | |
| 140 | 141 | rc = lstat(zMbcs, buf); |
| 141 | 142 | }else{ |
| 143 | + /* Symlinks look like the object to which they point */ | |
| 142 | 144 | rc = stat(zMbcs, buf); |
| 143 | 145 | } |
| 144 | 146 | #else |
| 145 | 147 | rc = win32_stat(zMbcs, buf, eFType); |
| 146 | 148 | #endif |
| @@ -316,11 +318,12 @@ | ||
| 316 | 318 | |
| 317 | 319 | /* |
| 318 | 320 | ** Return TRUE if the named file is a symlink and symlinks are allowed. |
| 319 | 321 | ** Return false for all other cases. |
| 320 | 322 | ** |
| 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. | |
| 322 | 325 | ** |
| 323 | 326 | ** On Windows, always return False. |
| 324 | 327 | */ |
| 325 | 328 | int file_islink(const char *zFilename){ |
| 326 | 329 | return file_perm(zFilename, RepoFILE)==PERM_LNK; |
| @@ -1311,12 +1314,12 @@ | ||
| 1311 | 1314 | sqlite3_int64 iMtime; |
| 1312 | 1315 | struct fossilStat testFileStat; |
| 1313 | 1316 | memset(zBuf, 0, sizeof(zBuf)); |
| 1314 | 1317 | blob_zero(&x); |
| 1315 | 1318 | 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); | |
| 1318 | 1321 | memset(&testFileStat, 0, sizeof(struct fossilStat)); |
| 1319 | 1322 | rc = fossil_stat(zPath, &testFileStat, 0); |
| 1320 | 1323 | fossil_print(" stat_rc = %d\n", rc); |
| 1321 | 1324 | sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size); |
| 1322 | 1325 | fossil_print(" stat_size = %s\n", zBuf); |
| @@ -1360,10 +1363,13 @@ | ||
| 1360 | 1363 | fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath)); |
| 1361 | 1364 | fossil_print(" file_islink = %d\n", file_islink(zPath)); |
| 1362 | 1365 | fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE)); |
| 1363 | 1366 | fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE)); |
| 1364 | 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); | |
| 1365 | 1371 | if( reset ) resetStat(); |
| 1366 | 1372 | } |
| 1367 | 1373 | |
| 1368 | 1374 | /* |
| 1369 | 1375 | ** COMMAND: test-file-environment |
| @@ -1375,32 +1381,45 @@ | ||
| 1375 | 1381 | ** |
| 1376 | 1382 | ** Options: |
| 1377 | 1383 | ** |
| 1378 | 1384 | ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off |
| 1379 | 1385 | ** --open-config Open the configuration database first. |
| 1380 | -** --slash Trailing slashes, if any, are retained. | |
| 1381 | 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. | |
| 1382 | 1389 | */ |
| 1383 | 1390 | void cmd_test_file_environment(void){ |
| 1384 | 1391 | int i; |
| 1385 | 1392 | int slashFlag = find_option("slash",0,0)!=0; |
| 1386 | 1393 | int resetFlag = find_option("reset",0,0)!=0; |
| 1394 | + const char *zRoot = find_option("root",0,1); | |
| 1387 | 1395 | const char *zAllow = find_option("allow-symlinks",0,1); |
| 1388 | 1396 | if( find_option("open-config", 0, 0)!=0 ){ |
| 1389 | 1397 | Th_OpenConfig(1); |
| 1390 | 1398 | } |
| 1391 | 1399 | db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0); |
| 1392 | 1400 | fossil_print("filenames_are_case_sensitive() = %d\n", |
| 1393 | 1401 | filenames_are_case_sensitive()); |
| 1394 | - fossil_print("db_allow_symlinks_by_default() = %d\n", | |
| 1395 | - db_allow_symlinks_by_default()); | |
| 1396 | 1402 | if( zAllow ){ |
| 1397 | 1403 | g.allowSymlinks = !is_false(zAllow); |
| 1398 | 1404 | } |
| 1405 | + if( zRoot==0 ) zRoot = g.zLocalRoot; | |
| 1399 | 1406 | fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); |
| 1407 | + fossil_print("local-root = [%s]\n", zRoot); | |
| 1400 | 1408 | for(i=2; i<g.argc; i++){ |
| 1409 | + char *z; | |
| 1401 | 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); | |
| 1402 | 1421 | } |
| 1403 | 1422 | } |
| 1404 | 1423 | |
| 1405 | 1424 | /* |
| 1406 | 1425 | ** COMMAND: test-canonical-name |
| @@ -2472,10 +2491,21 @@ | ||
| 2472 | 2491 | changeCount); |
| 2473 | 2492 | }else{ |
| 2474 | 2493 | fossil_print("Touched %d file(s)\n", changeCount); |
| 2475 | 2494 | } |
| 2476 | 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 | +} | |
| 2477 | 2507 | |
| 2478 | 2508 | /* |
| 2479 | 2509 | ** Returns non-zero if the specified file name ends with any reserved name, |
| 2480 | 2510 | ** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match |
| 2481 | 2511 | ** or 2 for a tail match on a longer file name. |
| @@ -2534,26 +2564,5 @@ | ||
| 2534 | 2564 | default:{ |
| 2535 | 2565 | return 0; |
| 2536 | 2566 | } |
| 2537 | 2567 | } |
| 2538 | 2568 | } |
| 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 | 2569 |
| --- 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 @@ | ||
| 26 | 26 | */ |
| 27 | 27 | #define DEFAULT_FORUM_MIMETYPE "text/x-markdown" |
| 28 | 28 | |
| 29 | 29 | #if INTERFACE |
| 30 | 30 | /* |
| 31 | -** Each instance of the following object represents a single message - | |
| 31 | +** Each instance of the following object represents a single message - | |
| 32 | 32 | ** either the initial post, an edit to a post, a reply, or an edit to |
| 33 | 33 | ** a reply. |
| 34 | 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 */ | |
| 35 | +struct ForumPost { | |
| 36 | + int fpid; /* rid for this post */ | |
| 41 | 37 | int sid; /* Serial ID number */ |
| 38 | + int rev; /* Revision number */ | |
| 42 | 39 | 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 */ | |
| 49 | 50 | }; |
| 50 | 51 | |
| 51 | 52 | /* |
| 52 | 53 | ** A single instance of the following tracks all entries for a thread. |
| 53 | 54 | */ |
| 54 | 55 | 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 */ | |
| 59 | 60 | int mxIndent; /* Maximum indentation level */ |
| 60 | 61 | }; |
| 61 | 62 | #endif /* INTERFACE */ |
| 62 | 63 | |
| 63 | 64 | /* |
| 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 | |
| 65 | 66 | ** subsequently edited. |
| 66 | 67 | */ |
| 67 | 68 | int forum_rid_has_been_edited(int rid){ |
| 68 | 69 | static Stmt q; |
| 69 | 70 | int res; |
| @@ -79,41 +80,39 @@ | ||
| 79 | 80 | |
| 80 | 81 | /* |
| 81 | 82 | ** Delete a complete ForumThread and all its entries. |
| 82 | 83 | */ |
| 83 | 84 | 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); | |
| 89 | 90 | } |
| 90 | 91 | fossil_free(pThread); |
| 91 | 92 | } |
| 92 | 93 | |
| 93 | -#if 0 /* not used */ | |
| 94 | 94 | /* |
| 95 | -** Search a ForumEntry list forwards looking for the entry with fpid | |
| 95 | +** Search a ForumPost list forwards looking for the post with fpid | |
| 96 | 96 | */ |
| 97 | -static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){ | |
| 97 | +static ForumPost *forumpost_forward(ForumPost *p, int fpid){ | |
| 98 | 98 | while( p && p->fpid!=fpid ) p = p->pNext; |
| 99 | 99 | return p; |
| 100 | 100 | } |
| 101 | -#endif | |
| 102 | 101 | |
| 103 | 102 | /* |
| 104 | -** Search backwards for a ForumEntry | |
| 103 | +** Search backwards for a ForumPost | |
| 105 | 104 | */ |
| 106 | -static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){ | |
| 105 | +static ForumPost *forumpost_backward(ForumPost *p, int fpid){ | |
| 107 | 106 | while( p && p->fpid!=fpid ) p = p->pPrev; |
| 108 | 107 | return p; |
| 109 | 108 | } |
| 110 | 109 | |
| 111 | 110 | /* |
| 112 | -** Add an entry to the display list | |
| 111 | +** Add a post to the display list | |
| 113 | 112 | */ |
| 114 | -static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){ | |
| 113 | +static void forumpost_add_to_display(ForumThread *pThread, ForumPost *p){ | |
| 115 | 114 | if( pThread->pDisplay==0 ){ |
| 116 | 115 | pThread->pDisplay = p; |
| 117 | 116 | }else{ |
| 118 | 117 | pThread->pTail->pDisplay = p; |
| 119 | 118 | } |
| @@ -120,108 +119,112 @@ | ||
| 120 | 119 | pThread->pTail = p; |
| 121 | 120 | } |
| 122 | 121 | |
| 123 | 122 | /* |
| 124 | 123 | ** 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". | |
| 127 | 126 | */ |
| 128 | 127 | static void forumthread_display_order( |
| 129 | 128 | ForumThread *pThread, /* The complete thread */ |
| 130 | - ForumEntry *pBase /* Add replies to this entry */ | |
| 129 | + ForumPost *pBase /* Add replies to this post */ | |
| 131 | 130 | ){ |
| 132 | - ForumEntry *p; | |
| 133 | - ForumEntry *pPrev = 0; | |
| 131 | + ForumPost *p; | |
| 132 | + ForumPost *pPrev = 0; | |
| 133 | + ForumPost *pBaseIrt; | |
| 134 | 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; | |
| 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 | + } | |
| 143 | 145 | } |
| 144 | 146 | } |
| 145 | 147 | if( pPrev ){ |
| 146 | 148 | pPrev->nIndent = pBase->nIndent + 1; |
| 147 | 149 | if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent; |
| 148 | - forumentry_add_to_display(pThread, pPrev); | |
| 150 | + forumpost_add_to_display(pThread, pPrev); | |
| 149 | 151 | forumthread_display_order(pThread, pPrev); |
| 150 | 152 | } |
| 151 | 153 | } |
| 152 | 154 | |
| 153 | 155 | /* |
| 154 | 156 | ** Construct a ForumThread object given the root record id. |
| 155 | 157 | */ |
| 156 | 158 | static ForumThread *forumthread_create(int froot, int computeHierarchy){ |
| 157 | 159 | ForumThread *pThread; |
| 158 | - ForumEntry *pEntry; | |
| 160 | + ForumPost *pPost; | |
| 161 | + ForumPost *p; | |
| 159 | 162 | Stmt q; |
| 160 | 163 | int sid = 1; |
| 161 | - Bag seen = Bag_INIT; | |
| 164 | + int firt, fprev; | |
| 162 | 165 | pThread = fossil_malloc( sizeof(*pThread) ); |
| 163 | 166 | memset(pThread, 0, sizeof(*pThread)); |
| 164 | 167 | db_prepare(&q, |
| 165 | 168 | "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)" |
| 166 | 169 | " FROM forumpost" |
| 167 | 170 | " WHERE froot=%d ORDER BY fmtime", |
| 168 | 171 | froot |
| 169 | 172 | ); |
| 170 | 173 | 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; | |
| 182 | 183 | 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); | |
| 215 | 218 | |
| 216 | 219 | if( computeHierarchy ){ |
| 217 | 220 | /* Compute the hierarchical display order */ |
| 218 | - pEntry = pThread->pFirst; | |
| 219 | - pEntry->nIndent = 1; | |
| 221 | + pPost = pThread->pFirst; | |
| 222 | + pPost->nIndent = 1; | |
| 220 | 223 | 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); | |
| 223 | 226 | } |
| 224 | 227 | |
| 225 | 228 | /* Return the result */ |
| 226 | 229 | return pThread; |
| 227 | 230 | } |
| @@ -265,11 +268,11 @@ | ||
| 265 | 268 | void forumthread_cmd(void){ |
| 266 | 269 | int fpid; |
| 267 | 270 | int froot; |
| 268 | 271 | const char *zName; |
| 269 | 272 | ForumThread *pThread; |
| 270 | - ForumEntry *p; | |
| 273 | + ForumPost *p; | |
| 271 | 274 | |
| 272 | 275 | db_find_and_open_repository(0,0); |
| 273 | 276 | verify_all_options(); |
| 274 | 277 | if( g.argc==2 ){ |
| 275 | 278 | forum_thread_list(); |
| @@ -293,21 +296,22 @@ | ||
| 293 | 296 | pThread = forumthread_create(froot, 1); |
| 294 | 297 | fossil_print("Chronological:\n"); |
| 295 | 298 | fossil_print( |
| 296 | 299 | /* 0 1 2 3 4 5 6 7 */ |
| 297 | 300 | /* 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"); | |
| 299 | 302 | 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); | |
| 303 | 307 | } |
| 304 | 308 | fossil_print("\nDisplay\n"); |
| 305 | 309 | for(p=pThread->pDisplay; p; p=p->pDisplay){ |
| 306 | 310 | 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); | |
| 309 | 313 | }else{ |
| 310 | 314 | fossil_print("%d\n", p->fpid); |
| 311 | 315 | } |
| 312 | 316 | } |
| 313 | 317 | forumthread_delete(pThread); |
| @@ -352,28 +356,10 @@ | ||
| 352 | 356 | if( zClass ){ |
| 353 | 357 | @ </div> |
| 354 | 358 | } |
| 355 | 359 | } |
| 356 | 360 | |
| 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 | 361 | /* |
| 376 | 362 | ** Compute a display name from a login name. |
| 377 | 363 | ** |
| 378 | 364 | ** If the input login is found in the USER table, then check the USER.INFO |
| 379 | 365 | ** field to see if it has display-name followed by an email address. |
| @@ -403,413 +389,326 @@ | ||
| 403 | 389 | db_reset(&q); |
| 404 | 390 | return zResult; |
| 405 | 391 | } |
| 406 | 392 | |
| 407 | 393 | /* |
| 408 | -** Display all posts in a forum thread in chronological order | |
| 394 | +** Display a single post in a forum thread. | |
| 409 | 395 | */ |
| 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) | |
| 440 | 444 | fossil_free(zDisplayName); |
| 441 | 445 | 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> | |
| 445 | 454 | } |
| 455 | + | |
| 456 | + /* If debugging is enabled, link to the artifact page. */ | |
| 446 | 457 | if( g.perm.Debug ){ |
| 447 | 458 | @ <span class="debug">\ |
| 448 | 459 | @ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span> |
| 449 | 460 | } |
| 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 ){ | |
| 489 | 513 | @ <div><form action="%R/forumedit" method="POST"> |
| 490 | 514 | @ <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. */ | |
| 494 | 517 | @ <input type="submit" name="reply" value="Reply"> |
| 495 | - if( g.perm.Admin || sameUser ){ | |
| 518 | + if( g.perm.Admin || bSameUser ){ | |
| 496 | 519 | @ <input type="submit" name="edit" value="Edit"> |
| 497 | 520 | @ <input type="submit" name="nullout" value="Delete"> |
| 498 | 521 | } |
| 499 | 522 | }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. */ | |
| 502 | 526 | @ <input type="submit" name="approve" value="Approve"> |
| 503 | 527 | @ <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. */ | |
| 508 | 537 | @ <input type="submit" name="reject" value="Delete"> |
| 509 | 538 | } |
| 510 | 539 | @ </form></div> |
| 511 | 540 | } |
| 512 | - manifest_destroy(pPost); | |
| 513 | 541 | @ </div> |
| 514 | 542 | } |
| 515 | 543 | |
| 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. */ | |
| 519 | 659 | if( PB("threadtable") ){ |
| 520 | 660 | @ <hr> |
| 521 | 661 | @ <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 | |
| 523 | 664 | 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)\ | |
| 527 | 672 | @ <td>%S(p->zUuid)</tr> |
| 528 | 673 | } |
| 529 | 674 | @ </table> |
| 530 | 675 | } |
| 531 | 676 | |
| 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); | |
| 765 | 680 | } |
| 766 | 681 | |
| 767 | 682 | /* |
| 768 | 683 | ** WEBPAGE: forumpost |
| 769 | 684 | ** |
| 770 | 685 | ** 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 | |
| 772 | 687 | ** <div class='forumSel'>...</div>. Javascript is used to move the |
| 773 | 688 | ** selected posting into view after the page loads. |
| 774 | 689 | ** |
| 775 | 690 | ** Query parameters: |
| 776 | 691 | ** |
| 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. | |
| 786 | 705 | */ |
| 787 | 706 | void forumpost_page(void){ |
| 788 | 707 | forumthread_page(); |
| 789 | 708 | } |
| 790 | 709 | |
| 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 | 710 | /* |
| 812 | 711 | ** WEBPAGE: forumthread |
| 813 | 712 | ** |
| 814 | 713 | ** Show all forum messages associated with a particular message thread. |
| 815 | 714 | ** The result is basically the same as /forumpost except that none of |
| @@ -816,24 +715,28 @@ | ||
| 816 | 715 | ** the postings in the thread are selected. |
| 817 | 716 | ** |
| 818 | 717 | ** Query parameters: |
| 819 | 718 | ** |
| 820 | 719 | ** 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. | |
| 828 | 727 | */ |
| 829 | 728 | void forumthread_page(void){ |
| 830 | 729 | int fpid; |
| 831 | 730 | int froot; |
| 731 | + char *zThreadTitle; | |
| 832 | 732 | const char *zName = P("name"); |
| 833 | 733 | const char *zMode = PD("t","a"); |
| 834 | 734 | int bRaw = PB("raw"); |
| 735 | + int bUnf = PB("unf"); | |
| 736 | + int bHist = PB("hist"); | |
| 737 | + int mode = 0; | |
| 835 | 738 | login_check_credentials(); |
| 836 | 739 | if( !g.perm.RdForum ){ |
| 837 | 740 | login_needed(g.anon.RdForum); |
| 838 | 741 | return; |
| 839 | 742 | } |
| @@ -847,54 +750,70 @@ | ||
| 847 | 750 | froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); |
| 848 | 751 | if( froot==0 ){ |
| 849 | 752 | webpage_error("Not a forum post: \"%s\"", zName); |
| 850 | 753 | } |
| 851 | 754 | 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. */ | |
| 896 | 815 | style_footer(); |
| 897 | 816 | } |
| 898 | 817 | |
| 899 | 818 | /* |
| 900 | 819 | ** Return true if a forum post should be moderated. |
| @@ -949,11 +868,11 @@ | ||
| 949 | 868 | webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 ); |
| 950 | 869 | blob_init(&x, 0, 0); |
| 951 | 870 | zDate = date_in_standard_format("now"); |
| 952 | 871 | blob_appendf(&x, "D %s\n", zDate); |
| 953 | 872 | fossil_free(zDate); |
| 954 | - zG = db_text(0, | |
| 873 | + zG = db_text(0, | |
| 955 | 874 | "SELECT uuid FROM blob, forumpost" |
| 956 | 875 | " WHERE blob.rid==forumpost.froot" |
| 957 | 876 | " AND forumpost.fpid=%d", iBasis); |
| 958 | 877 | if( zG ){ |
| 959 | 878 | blob_appendf(&x, "G %s\n", zG); |
| @@ -1017,11 +936,11 @@ | ||
| 1017 | 936 | } |
| 1018 | 937 | |
| 1019 | 938 | /* |
| 1020 | 939 | ** Paint the form elements for entering a Forum post |
| 1021 | 940 | */ |
| 1022 | -static void forum_entry_widget( | |
| 941 | +static void forum_post_widget( | |
| 1023 | 942 | const char *zTitle, |
| 1024 | 943 | const char *zMimetype, |
| 1025 | 944 | const char *zContent |
| 1026 | 945 | ){ |
| 1027 | 946 | if( zTitle ){ |
| @@ -1126,11 +1045,11 @@ | ||
| 1126 | 1045 | } |
| 1127 | 1046 | style_header("New Forum Thread"); |
| 1128 | 1047 | @ <form action="%R/forume1" method="POST"> |
| 1129 | 1048 | @ <h1>New Thread:</h1> |
| 1130 | 1049 | forum_from_line(); |
| 1131 | - forum_entry_widget(zTitle, zMimetype, zContent); | |
| 1050 | + forum_post_widget(zTitle, zMimetype, zContent); | |
| 1132 | 1051 | @ <input type="submit" name="preview" value="Preview"> |
| 1133 | 1052 | if( P("preview") && !whitespace_only(zContent) ){ |
| 1134 | 1053 | @ <input type="submit" name="submit" value="Submit"> |
| 1135 | 1054 | }else{ |
| 1136 | 1055 | @ <input type="submit" name="submit" value="Submit" disabled> |
| @@ -1195,19 +1114,21 @@ | ||
| 1195 | 1114 | moderation_approve('f', fpid); |
| 1196 | 1115 | if( g.perm.AdminForum |
| 1197 | 1116 | && PB("trust") |
| 1198 | 1117 | && (zUserToTrust = P("trustuser"))!=0 |
| 1199 | 1118 | ){ |
| 1119 | + db_unprotect(PROTECT_USER); | |
| 1200 | 1120 | db_multi_exec("UPDATE user SET cap=cap||'4' " |
| 1201 | 1121 | "WHERE login=%Q AND cap NOT GLOB '*4*'", |
| 1202 | 1122 | zUserToTrust); |
| 1123 | + db_protect_pop(); | |
| 1203 | 1124 | } |
| 1204 | 1125 | cgi_redirectf("%R/forumpost/%S",P("fpid")); |
| 1205 | 1126 | return; |
| 1206 | 1127 | } |
| 1207 | 1128 | if( P("reject") ){ |
| 1208 | - char *zParent = | |
| 1129 | + char *zParent = | |
| 1209 | 1130 | db_text(0, |
| 1210 | 1131 | "SELECT uuid FROM forumpost, blob" |
| 1211 | 1132 | " WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt", |
| 1212 | 1133 | fpid |
| 1213 | 1134 | ); |
| @@ -1276,11 +1197,11 @@ | ||
| 1276 | 1197 | @ <h2>Revised Message:</h2> |
| 1277 | 1198 | @ <form action="%R/forume2" method="POST"> |
| 1278 | 1199 | @ <input type="hidden" name="fpid" value="%h(P("fpid"))"> |
| 1279 | 1200 | @ <input type="hidden" name="edit" value="1"> |
| 1280 | 1201 | forum_from_line(); |
| 1281 | - forum_entry_widget(zTitle, zMimetype, zContent); | |
| 1202 | + forum_post_widget(zTitle, zMimetype, zContent); | |
| 1282 | 1203 | }else{ |
| 1283 | 1204 | /* Reply */ |
| 1284 | 1205 | char *zDisplayName; |
| 1285 | 1206 | zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE); |
| 1286 | 1207 | zContent = PDT("content",""); |
| @@ -1302,11 +1223,11 @@ | ||
| 1302 | 1223 | @ <h2>Enter Reply:</h2> |
| 1303 | 1224 | @ <form action="%R/forume2" method="POST"> |
| 1304 | 1225 | @ <input type="hidden" name="fpid" value="%h(P("fpid"))"> |
| 1305 | 1226 | @ <input type="hidden" name="reply" value="1"> |
| 1306 | 1227 | forum_from_line(); |
| 1307 | - forum_entry_widget(0, zMimetype, zContent); | |
| 1228 | + forum_post_widget(0, zMimetype, zContent); | |
| 1308 | 1229 | } |
| 1309 | 1230 | if( !isDelete ){ |
| 1310 | 1231 | @ <input type="submit" name="preview" value="Preview"> |
| 1311 | 1232 | } |
| 1312 | 1233 | @ <input type="submit" name="cancel" value="Cancel"> |
| 1313 | 1234 |
| --- 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 |
+12
| --- src/fossil.bootstrap.js | ||
| +++ src/fossil.bootstrap.js | ||
| @@ -1,6 +1,18 @@ | ||
| 1 | 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 | +})(); | |
| 2 | 14 | (function(global){ |
| 3 | 15 | /* Bootstrapping bits for the global.fossil object. Must be |
| 4 | 16 | loaded after style.c:style_emit_script_tag() has initialized |
| 5 | 17 | that object. |
| 6 | 18 | */ |
| 7 | 19 |
| --- 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 |
+7
-4
| --- src/fossil.confirmer.js | ||
| +++ src/fossil.confirmer.js | ||
| @@ -164,15 +164,18 @@ | ||
| 164 | 164 | if(opt.pinSize && opt.confirmText){ |
| 165 | 165 | /* Try to pin the element's width the the greater of its |
| 166 | 166 | current width or its waiting-on-confirmation width |
| 167 | 167 | to avoid layout reflow when it's activated. */ |
| 168 | 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); | |
| 169 | + const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1)); | |
| 170 | + const w1 = parseInt(target.getBoundingClientRect().width); | |
| 171 | 171 | 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 | + } | |
| 174 | 177 | } |
| 175 | 178 | updateText(this.opt.initialText); |
| 176 | 179 | if(this.opt.ticks && !this.opt.ontick){ |
| 177 | 180 | this.opt.ontick = function(tick){ |
| 178 | 181 | updateText(formatCountdown(self.opt.confirmText,tick)); |
| 179 | 182 | |
| 180 | 183 | 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 |
+97
-2
| --- src/fossil.dom.js | ||
| +++ src/fossil.dom.js | ||
| @@ -218,14 +218,16 @@ | ||
| 218 | 218 | const a = argsToArray(arguments); |
| 219 | 219 | a.shift(); |
| 220 | 220 | for(let i in a) { |
| 221 | 221 | var e = a[i]; |
| 222 | 222 | if(isArray(e) || e.forEach){ |
| 223 | - e.forEach((x)=>f.call(this, parent,e)); | |
| 223 | + e.forEach((x)=>f.call(this, parent,x)); | |
| 224 | 224 | continue; |
| 225 | 225 | } |
| 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); | |
| 227 | 229 | parent.appendChild(e); |
| 228 | 230 | } |
| 229 | 231 | return parent; |
| 230 | 232 | }; |
| 231 | 233 | |
| @@ -465,11 +467,104 @@ | ||
| 465 | 467 | e = src.querySelector(x); |
| 466 | 468 | if(!e){ |
| 467 | 469 | e = new Error("Cannot find DOM element: "+x); |
| 468 | 470 | console.error(e, src); |
| 469 | 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 | + } | |
| 470 | 565 | } |
| 471 | 566 | return e; |
| 472 | 567 | }; |
| 473 | 568 | |
| 474 | 569 | return F.dom = dom; |
| 475 | 570 | })(window.fossil); |
| 476 | 571 | |
| 477 | 572 | 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 |
+2
-2
| --- src/fossil.page.fileedit.js | ||
| +++ src/fossil.page.fileedit.js | ||
| @@ -638,11 +638,11 @@ | ||
| 638 | 638 | }; |
| 639 | 639 | |
| 640 | 640 | F.onPageLoad(function() { |
| 641 | 641 | P.base = {tag: E('base')}; |
| 642 | 642 | P.base.originalHref = P.base.tag.href; |
| 643 | - P.tabs = new fossil.TabManager('#fileedit-tabs'); | |
| 643 | + P.tabs = new F.TabManager('#fileedit-tabs'); | |
| 644 | 644 | P.e = { /* various DOM elements we work with... */ |
| 645 | 645 | taEditor: E('#fileedit-content-editor'), |
| 646 | 646 | taCommentSmall: E('#fileedit-comment'), |
| 647 | 647 | taCommentBig: E('#fileedit-comment-big'), |
| 648 | 648 | taComment: undefined/*gets set to one of taComment{Big,Small}*/, |
| @@ -1142,11 +1142,11 @@ | ||
| 1142 | 1142 | mimetype: P.finfo.mimetype, |
| 1143 | 1143 | element: P.e.previewTarget |
| 1144 | 1144 | }); |
| 1145 | 1145 | }, |
| 1146 | 1146 | onerror: (e)=>{ |
| 1147 | - fossil.fetch.onerror(e); | |
| 1147 | + F.fetch.onerror(e); | |
| 1148 | 1148 | callback("Error fetching preview: "+e); |
| 1149 | 1149 | } |
| 1150 | 1150 | }); |
| 1151 | 1151 | return this; |
| 1152 | 1152 | }; |
| 1153 | 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 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 |
+1
-1
| --- src/fossil.page.forumpost.js | ||
| +++ src/fossil.page.forumpost.js | ||
| @@ -1,9 +1,9 @@ | ||
| 1 | 1 | (function(F/*the fossil object*/){ |
| 2 | 2 | "use strict"; |
| 3 | 3 | /* 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; | |
| 5 | 5 | |
| 6 | 6 | F.onPageLoad(function(){ |
| 7 | 7 | const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight; |
| 8 | 8 | /* Returns an event handler which implements the post expand/collapse toggle |
| 9 | 9 | on contentElem when the given widget is activated. */ |
| 10 | 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 = 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 |
+2
-2
| --- src/fossil.page.wikiedit.js | ||
| +++ src/fossil.page.wikiedit.js | ||
| @@ -855,11 +855,11 @@ | ||
| 855 | 855 | diff: E('#wikiedit-tab-diff'), |
| 856 | 856 | misc: E('#wikiedit-tab-misc') |
| 857 | 857 | //commit: E('#wikiedit-tab-commit') |
| 858 | 858 | } |
| 859 | 859 | }; |
| 860 | - P.tabs = new fossil.TabManager(D.clearElement(P.e.tabContainer)); | |
| 860 | + P.tabs = new F.TabManager(D.clearElement(P.e.tabContainer)); | |
| 861 | 861 | P.tabs.e.container.insertBefore( |
| 862 | 862 | /* Move the status bar between the tab buttons and |
| 863 | 863 | tab panels. Seems to be the best fit in terms of |
| 864 | 864 | functionality and visibility. */ |
| 865 | 865 | E('#fossil-status-bar'), P.tabs.e.tabs |
| @@ -1322,11 +1322,11 @@ | ||
| 1322 | 1322 | mimetype: mimetype, |
| 1323 | 1323 | element: P.e.previewTarget |
| 1324 | 1324 | }); |
| 1325 | 1325 | }, |
| 1326 | 1326 | onerror: (e)=>{ |
| 1327 | - fossil.fetch.onerror(e); | |
| 1327 | + F.fetch.onerror(e); | |
| 1328 | 1328 | callback("Error fetching preview: "+e); |
| 1329 | 1329 | } |
| 1330 | 1330 | }); |
| 1331 | 1331 | return this; |
| 1332 | 1332 | }; |
| 1333 | 1333 | |
| 1334 | 1334 | 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); |
+1
-1
| --- src/fossil.storage.js | ||
| +++ src/fossil.storage.js | ||
| @@ -89,11 +89,11 @@ | ||
| 89 | 89 | page-instance-local proxy, if neither one is availble. |
| 90 | 90 | |
| 91 | 91 | Which exact storage implementation is uses is unspecified, and |
| 92 | 92 | apps must not rely on it. |
| 93 | 93 | */ |
| 94 | - fossil.storage = { | |
| 94 | + F.storage = { | |
| 95 | 95 | storageKeyPrefix: storageKeyPrefix, |
| 96 | 96 | /** Sets the storage key k to value v, implicitly converting |
| 97 | 97 | it to a string. */ |
| 98 | 98 | set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), |
| 99 | 99 | /** Sets storage key k to JSON.stringify(v). */ |
| 100 | 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 | 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 |
+6
-23
| --- src/fossil.tabs.js | ||
| +++ src/fossil.tabs.js | ||
| @@ -30,42 +30,28 @@ | ||
| 30 | 30 | tabAccessKeys: true |
| 31 | 31 | }; |
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | 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. | |
| 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. | |
| 37 | 38 | */ |
| 38 | 39 | const tabArg = function(arg,tabMgr){ |
| 39 | 40 | if('string'===typeof arg) arg = E(arg); |
| 40 | 41 | else if(tabMgr && 'number'===typeof arg && arg>=0){ |
| 41 | 42 | arg = tabMgr.e.tabs.childNodes[arg]; |
| 42 | 43 | } |
| 43 | - if(arg){ | |
| 44 | - if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){ | |
| 45 | - arg = arg.firstElementChild; | |
| 46 | - } | |
| 47 | - } | |
| 48 | 44 | return arg; |
| 49 | 45 | }; |
| 50 | 46 | |
| 51 | 47 | /** |
| 52 | 48 | 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. | |
| 57 | 50 | */ |
| 58 | 51 | 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'); | |
| 67 | 53 | }; |
| 68 | 54 | |
| 69 | 55 | TabManager.prototype = { |
| 70 | 56 | /** |
| 71 | 57 | Initializes the tabs associated with the given tab container |
| @@ -164,13 +150,11 @@ | ||
| 164 | 150 | e.target.$manager.switchToTab(e.target.$tab); |
| 165 | 151 | }; |
| 166 | 152 | } |
| 167 | 153 | tab = tabArg(tab); |
| 168 | 154 | 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')); | |
| 172 | 156 | const tabCount = this.e.tabBar.childNodes.length+1; |
| 173 | 157 | const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount; |
| 174 | 158 | const btn = D.addClass(D.append(D.span(), lbl), 'tab-button'); |
| 175 | 159 | D.append(this.e.tabBar,btn); |
| 176 | 160 | btn.$manager = this; |
| @@ -238,11 +222,10 @@ | ||
| 238 | 222 | this._dispatchEvent('before-switch-from', this._currentTab); |
| 239 | 223 | } |
| 240 | 224 | delete this._currentTab; |
| 241 | 225 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 242 | 226 | const btn = this.e.tabBar.childNodes[ndx]; |
| 243 | - e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */; | |
| 244 | 227 | if(e===tab){ |
| 245 | 228 | if(D.hasClass(e,'selected')){ |
| 246 | 229 | return; |
| 247 | 230 | } |
| 248 | 231 | self._dispatchEvent('before-switch-to',tab); |
| 249 | 232 |
| --- 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 @@ | ||
| 123 | 123 | ** If N==0, then there is no expectation of new artifacts arriving |
| 124 | 124 | ** soon and so post-receive hooks can be run without delay. |
| 125 | 125 | */ |
| 126 | 126 | void hook_expecting_more_artifacts(int N){ |
| 127 | 127 | if( N>0 ){ |
| 128 | + db_unprotect(PROTECT_CONFIG); | |
| 128 | 129 | db_multi_exec( |
| 129 | 130 | "REPLACE INTO config(name,value,mtime)" |
| 130 | 131 | "VALUES('hook-embargo',now()+%d,now())", |
| 131 | 132 | N |
| 132 | 133 | ); |
| 134 | + db_protect_pop(); | |
| 133 | 135 | }else{ |
| 134 | 136 | db_unset("hook-embargo",0); |
| 135 | 137 | } |
| 136 | 138 | } |
| 137 | 139 | |
| @@ -243,10 +245,11 @@ | ||
| 243 | 245 | fossil_fatal("the --command and --type options are required"); |
| 244 | 246 | } |
| 245 | 247 | validate_type(zType); |
| 246 | 248 | nSeq = zSeq ? atoi(zSeq) : 10; |
| 247 | 249 | db_begin_write(); |
| 250 | + db_unprotect(PROTECT_CONFIG); | |
| 248 | 251 | db_multi_exec( |
| 249 | 252 | "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n" |
| 250 | 253 | "UPDATE config" |
| 251 | 254 | " SET value=json_insert(" |
| 252 | 255 | " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]'," |
| @@ -253,10 +256,11 @@ | ||
| 253 | 256 | " json_object('cmd',%Q,'type',%Q,'seq',%d))," |
| 254 | 257 | " mtime=now()" |
| 255 | 258 | " WHERE name='hooks';", |
| 256 | 259 | zCmd, zType, nSeq |
| 257 | 260 | ); |
| 261 | + db_protect_pop(); | |
| 258 | 262 | db_commit_transaction(); |
| 259 | 263 | }else |
| 260 | 264 | if( strncmp(zCmd, "edit", nCmd)==0 ){ |
| 261 | 265 | const char *zCmd = find_option("command",0,1); |
| 262 | 266 | const char *zType = find_option("type",0,1); |
| @@ -290,20 +294,23 @@ | ||
| 290 | 294 | } |
| 291 | 295 | if( zSeq ){ |
| 292 | 296 | blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq); |
| 293 | 297 | } |
| 294 | 298 | blob_append_sql(&sql,") WHERE name='hooks';"); |
| 299 | + db_unprotect(PROTECT_CONFIG); | |
| 295 | 300 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 301 | + db_protect_pop(); | |
| 296 | 302 | blob_reset(&sql); |
| 297 | 303 | } |
| 298 | 304 | db_commit_transaction(); |
| 299 | 305 | }else |
| 300 | 306 | if( strncmp(zCmd, "delete", nCmd)==0 ){ |
| 301 | 307 | int i; |
| 302 | 308 | verify_all_options(); |
| 303 | 309 | if( g.argc<4 ) usage("delete ID ..."); |
| 304 | 310 | db_begin_write(); |
| 311 | + db_unprotect(PROTECT_CONFIG); | |
| 305 | 312 | db_multi_exec( |
| 306 | 313 | "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n" |
| 307 | 314 | ); |
| 308 | 315 | for(i=3; i<g.argc; i++){ |
| 309 | 316 | const char *zId = g.argv[i]; |
| @@ -321,10 +328,11 @@ | ||
| 321 | 328 | " mtime=now()" |
| 322 | 329 | " WHERE name='hooks';", |
| 323 | 330 | atoi(zId) |
| 324 | 331 | ); |
| 325 | 332 | } |
| 333 | + db_protect_pop(); | |
| 326 | 334 | db_commit_transaction(); |
| 327 | 335 | }else |
| 328 | 336 | if( strncmp(zCmd, "list", nCmd)==0 ){ |
| 329 | 337 | Stmt q; |
| 330 | 338 | int n = 0; |
| 331 | 339 |
| --- 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 |
+2
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -576,16 +576,18 @@ | ||
| 576 | 576 | Blob sql; |
| 577 | 577 | char *zSep = "("; |
| 578 | 578 | db_begin_transaction(); |
| 579 | 579 | blob_init(&sql, 0, 0); |
| 580 | 580 | if( g.argc==4 && find_option("all",0,0)!=0 ){ |
| 581 | + db_unprotect(PROTECT_CONFIG); | |
| 581 | 582 | blob_append_sql(&sql, |
| 582 | 583 | "DELETE FROM global_config WHERE name GLOB 'cert:*';\n" |
| 583 | 584 | "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n" |
| 584 | 585 | "DELETE FROM config WHERE name GLOB 'cert:*';\n" |
| 585 | 586 | "DELETE FROM config WHERE name GLOB 'trusted:*';\n" |
| 586 | 587 | ); |
| 588 | + db_protect_pop(); | |
| 587 | 589 | }else{ |
| 588 | 590 | if( g.argc<4 ){ |
| 589 | 591 | usage("remove-exception DOMAIN-NAME ..."); |
| 590 | 592 | } |
| 591 | 593 | blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN "); |
| 592 | 594 |
| --- 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 |
+1
| --- src/import.c | ||
| +++ src/import.c | ||
| @@ -1759,10 +1759,11 @@ | ||
| 1759 | 1759 | if( forceFlag ) file_delete(g.argv[2]); |
| 1760 | 1760 | db_create_repository(g.argv[2]); |
| 1761 | 1761 | } |
| 1762 | 1762 | db_open_repository(g.argv[2]); |
| 1763 | 1763 | db_open_config(0, 0); |
| 1764 | + db_unprotect(PROTECT_ALL); | |
| 1764 | 1765 | |
| 1765 | 1766 | db_begin_transaction(); |
| 1766 | 1767 | if( !incrFlag ){ |
| 1767 | 1768 | db_initial_setup(0, 0, zDefaultUser); |
| 1768 | 1769 | db_set("main-branch", gimport.zTrunkName, 0); |
| 1769 | 1770 |
| --- 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 @@ | ||
| 2011 | 2011 | manifest_destroy(pManifest); |
| 2012 | 2012 | return rid; |
| 2013 | 2013 | } |
| 2014 | 2014 | |
| 2015 | 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. | |
| 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. | |
| 2018 | 2023 | ** |
| 2019 | 2024 | ** zLn is the ?ln= parameter for the HTTP query. If there is an argument, |
| 2020 | 2025 | ** then highlight that line number and scroll to it once the page loads. |
| 2021 | 2026 | ** If there are two line numbers, highlight the range of lines. |
| 2022 | 2027 | ** Multiple ranges can be highlighed by adding additional line numbers |
| 2023 | 2028 | ** separated by a non-digit character (also not one of [-,.]). |
| 2024 | 2029 | */ |
| 2025 | 2030 | void output_text_with_line_numbers( |
| 2026 | 2031 | const char *z, |
| 2032 | + int nZ, | |
| 2033 | + const char *zName, | |
| 2027 | 2034 | const char *zLn |
| 2028 | 2035 | ){ |
| 2029 | 2036 | int iStart, iEnd; /* Start and end of region to highlight */ |
| 2030 | 2037 | int n = 0; /* Current line number */ |
| 2031 | 2038 | int i = 0; /* Loop index */ |
| 2032 | 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); | |
| 2033 | 2043 | Stmt q; |
| 2034 | 2044 | |
| 2035 | 2045 | iStart = iEnd = atoi(zLn); |
| 2036 | 2046 | db_multi_exec( |
| 2037 | 2047 | "CREATE TEMP TABLE lnos(iStart INTEGER PRIMARY KEY, iEnd INTEGER)"); |
| @@ -2047,56 +2057,101 @@ | ||
| 2047 | 2057 | while( fossil_isdigit(zLn[i]) ) i++; |
| 2048 | 2058 | if( iEnd<iStart ) iEnd = iStart; |
| 2049 | 2059 | db_multi_exec( |
| 2050 | 2060 | "INSERT OR REPLACE INTO lnos VALUES(%d,%d)", iStart, iEnd |
| 2051 | 2061 | ); |
| 2062 | + ++nSpans; | |
| 2052 | 2063 | iStart = iEnd = atoi(&zLn[i++]); |
| 2053 | 2064 | }while( zLn[i] && iStart && iEnd ); |
| 2054 | 2065 | } |
| 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"); | |
| 2093 | 2120 | if( db_int(0, "SELECT EXISTS(SELECT 1 FROM lnos)") ){ |
| 2094 | 2121 | builtin_request_js("scroll.js"); |
| 2095 | 2122 | } |
| 2123 | + style_emit_fossil_js_apis(0, "dom", "copybutton", "popupwidget", | |
| 2124 | + "numbered-lines", 0); | |
| 2096 | 2125 | } |
| 2097 | 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 | +} | |
| 2098 | 2153 | |
| 2099 | 2154 | /* |
| 2100 | 2155 | ** WEBPAGE: artifact |
| 2101 | 2156 | ** WEBPAGE: file |
| 2102 | 2157 | ** WEBPAGE: whatis |
| @@ -2251,17 +2306,19 @@ | ||
| 2251 | 2306 | const char *zPath; |
| 2252 | 2307 | Blob path; |
| 2253 | 2308 | blob_zero(&path); |
| 2254 | 2309 | hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO); |
| 2255 | 2310 | 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); | |
| 2257 | 2314 | if( isBranchCI ){ |
| 2258 | 2315 | @ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2> |
| 2259 | 2316 | }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> | |
| 2261 | 2318 | }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> | |
| 2263 | 2320 | } |
| 2264 | 2321 | blob_reset(&path); |
| 2265 | 2322 | } |
| 2266 | 2323 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2267 | 2324 | style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", |
| @@ -2387,25 +2444,26 @@ | ||
| 2387 | 2444 | if( zLn==0 || atoi(zLn)==0 ){ |
| 2388 | 2445 | style_submenu_checkbox("ln", "Line Numbers", 0, 0); |
| 2389 | 2446 | } |
| 2390 | 2447 | blob_to_utf8_no_bom(&content, 0); |
| 2391 | 2448 | zMime = mimetype_from_content(&content); |
| 2392 | - @ <blockquote> | |
| 2449 | + @ <blockquote class="file-content"> | |
| 2393 | 2450 | if( zMime==0 ){ |
| 2394 | 2451 | const char *z, *zFileName, *zExt; |
| 2395 | 2452 | z = blob_str(&content); |
| 2396 | 2453 | zFileName = db_text(0, |
| 2397 | 2454 | "SELECT name FROM mlink, filename" |
| 2398 | 2455 | " WHERE filename.fnid=mlink.fnid" |
| 2399 | 2456 | " AND mlink.fid=%d", |
| 2400 | 2457 | rid); |
| 2401 | - zExt = zFileName ? strrchr(zFileName, '.') : 0; | |
| 2458 | + zExt = zFileName ? file_extension(zFileName) : 0; | |
| 2402 | 2459 | if( zLn ){ |
| 2403 | - output_text_with_line_numbers(z, zLn); | |
| 2460 | + output_text_with_line_numbers(z, blob_size(&content), | |
| 2461 | + zFileName, zLn); | |
| 2404 | 2462 | }else if( zExt && zExt[1] ){ |
| 2405 | 2463 | @ <pre> |
| 2406 | - @ <code class="language-%s(zExt+1)">%h(z)</code> | |
| 2464 | + @ <code class="language-%s(zExt)">%h(z)</code> | |
| 2407 | 2465 | @ </pre> |
| 2408 | 2466 | }else{ |
| 2409 | 2467 | @ <pre> |
| 2410 | 2468 | @ %h(z) |
| 2411 | 2469 | @ </pre> |
| @@ -3343,5 +3401,40 @@ | ||
| 3343 | 3401 | } |
| 3344 | 3402 | if( g.localOpen ){ |
| 3345 | 3403 | manifest_to_disk(rid); |
| 3346 | 3404 | } |
| 3347 | 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 | +} | |
| 3348 | 3441 | |
| 3349 | 3442 | 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 |
+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 |
| --- 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 |
-3
| --- src/json_config.c | ||
| +++ src/json_config.c | ||
| @@ -83,13 +83,10 @@ | ||
| 83 | 83 | { "keep-glob", CONFIGSET_PROJ }, |
| 84 | 84 | { "crlf-glob", CONFIGSET_PROJ }, |
| 85 | 85 | { "crnl-glob", CONFIGSET_PROJ }, |
| 86 | 86 | { "encoding-glob", CONFIGSET_PROJ }, |
| 87 | 87 | { "empty-dirs", CONFIGSET_PROJ }, |
| 88 | -#ifdef FOSSIL_LEGACY_ALLOW_SYMLINKS | |
| 89 | -{ "allow-symlinks", CONFIGSET_PROJ }, | |
| 90 | -#endif | |
| 91 | 88 | { "dotfiles", CONFIGSET_PROJ }, |
| 92 | 89 | |
| 93 | 90 | { "ticket-table", CONFIGSET_TKT }, |
| 94 | 91 | { "ticket-common", CONFIGSET_TKT }, |
| 95 | 92 | { "ticket-change", CONFIGSET_TKT }, |
| 96 | 93 |
| --- 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 |
+6
| --- src/json_user.c | ||
| +++ src/json_user.c | ||
| @@ -212,13 +212,15 @@ | ||
| 212 | 212 | json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS, |
| 213 | 213 | "User %s already exists.", zName); |
| 214 | 214 | goto error; |
| 215 | 215 | }else{ |
| 216 | 216 | Stmt ins = empty_Stmt; |
| 217 | + db_unprotect(PROTECT_USER); | |
| 217 | 218 | db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName); |
| 218 | 219 | db_step( &ins ); |
| 219 | 220 | db_finalize(&ins); |
| 221 | + db_protect_pop(); | |
| 220 | 222 | uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); |
| 221 | 223 | assert(uid>0); |
| 222 | 224 | zNameNew = zName; |
| 223 | 225 | cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); |
| 224 | 226 | } |
| @@ -345,13 +347,15 @@ | ||
| 345 | 347 | #endif |
| 346 | 348 | #if 0 |
| 347 | 349 | puts(blob_str(&sql)); |
| 348 | 350 | cson_output_FILE( cson_object_value(pUser), stdout, NULL ); |
| 349 | 351 | #endif |
| 352 | + db_unprotect(PROTECT_USER); | |
| 350 | 353 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 351 | 354 | db_exec(&q); |
| 352 | 355 | db_finalize(&q); |
| 356 | + db_protect_pop(); | |
| 353 | 357 | #if TRY_LOGIN_GROUP |
| 354 | 358 | if( zPW || cson_value_get_bool(forceLogout) ){ |
| 355 | 359 | Blob groupSql = empty_blob; |
| 356 | 360 | char * zErr = NULL; |
| 357 | 361 | blob_append_sql(&groupSql, |
| @@ -358,11 +362,13 @@ | ||
| 358 | 362 | "INSERT INTO user(login)" |
| 359 | 363 | " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", |
| 360 | 364 | zName, zName |
| 361 | 365 | ); |
| 362 | 366 | blob_append(&groupSql, blob_str(&sql), blob_size(&sql)); |
| 367 | + db_unprotect(PROTECT_USER); | |
| 363 | 368 | login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr); |
| 369 | + db_protect_pop(); | |
| 364 | 370 | blob_reset(&groupSql); |
| 365 | 371 | if( zErr ){ |
| 366 | 372 | json_set_err( FSL_JSON_E_UNKNOWN, |
| 367 | 373 | "Repo-group update at least partially failed: %s", |
| 368 | 374 | zErr); |
| 369 | 375 |
| --- 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 @@ | ||
| 458 | 458 | json_set_err(FSL_JSON_E_DENIED, |
| 459 | 459 | "Requires 'j' or 'o' permissions."); |
| 460 | 460 | return NULL; |
| 461 | 461 | } |
| 462 | 462 | 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-*'", | |
| 465 | 466 | -1); |
| 466 | 467 | zGlob = json_find_option_cstr("glob",NULL,"g"); |
| 467 | 468 | if(zGlob && *zGlob){ |
| 468 | 469 | blob_append_sql(&sql," AND name %s GLOB %Q", |
| 469 | 470 | fInvert ? "NOT" : "", zGlob); |
| 470 | 471 |
| --- 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 @@ | ||
| 293 | 293 | if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))"); |
| 294 | 294 | zCookie = login_gen_user_cookie_value(zUsername, zHash); |
| 295 | 295 | cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), |
| 296 | 296 | bSessionCookie ? 0 : expires); |
| 297 | 297 | record_login_attempt(zUsername, zIpAddr, 1); |
| 298 | + db_unprotect(PROTECT_USER); | |
| 298 | 299 | db_multi_exec("UPDATE user SET cookie=%Q," |
| 299 | 300 | " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", |
| 300 | 301 | zHash, expires, uid); |
| 302 | + db_protect_pop(); | |
| 301 | 303 | fossil_free(zHash); |
| 302 | 304 | if( zDest ){ |
| 303 | 305 | *zDest = zCookie; |
| 304 | 306 | }else{ |
| 305 | 307 | free(zCookie); |
| @@ -356,14 +358,16 @@ | ||
| 356 | 358 | }else{ |
| 357 | 359 | const char *cookie = login_cookie_name(); |
| 358 | 360 | /* To logout, change the cookie value to an empty string */ |
| 359 | 361 | cgi_set_cookie(cookie, "", |
| 360 | 362 | login_cookie_path(), -86400); |
| 363 | + db_unprotect(PROTECT_USER); | |
| 361 | 364 | db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " |
| 362 | 365 | " cexpire=0 WHERE uid=%d" |
| 363 | 366 | " AND login NOT IN ('anonymous','nobody'," |
| 364 | 367 | " 'developer','reader')", g.userUid); |
| 368 | + db_protect_pop(); | |
| 365 | 369 | cgi_replace_parameter(cookie, NULL); |
| 366 | 370 | cgi_replace_parameter("anon", NULL); |
| 367 | 371 | } |
| 368 | 372 | } |
| 369 | 373 | |
| @@ -580,22 +584,27 @@ | ||
| 580 | 584 | ; |
| 581 | 585 | }else{ |
| 582 | 586 | char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); |
| 583 | 587 | char *zChngPw; |
| 584 | 588 | char *zErr; |
| 589 | + int rc; | |
| 590 | + | |
| 591 | + db_unprotect(PROTECT_USER); | |
| 585 | 592 | db_multi_exec( |
| 586 | 593 | "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid |
| 587 | 594 | ); |
| 588 | - fossil_free(zNewPw); | |
| 589 | 595 | zChngPw = mprintf( |
| 590 | 596 | "UPDATE user" |
| 591 | 597 | " SET pw=shared_secret(%Q,%Q," |
| 592 | 598 | " (SELECT value FROM config WHERE name='project-code'))" |
| 593 | 599 | " WHERE login=%Q", |
| 594 | 600 | zNew1, g.zLogin, g.zLogin |
| 595 | 601 | ); |
| 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 ){ | |
| 597 | 606 | zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr); |
| 598 | 607 | fossil_free(zErr); |
| 599 | 608 | }else{ |
| 600 | 609 | redirect_to_g(); |
| 601 | 610 | return; |
| @@ -835,16 +844,18 @@ | ||
| 835 | 844 | zLogin, zHash |
| 836 | 845 | ); |
| 837 | 846 | pStmt = 0; |
| 838 | 847 | rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); |
| 839 | 848 | if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 849 | + db_unprotect(PROTECT_USER); | |
| 840 | 850 | db_multi_exec( |
| 841 | 851 | "UPDATE user SET cookie=%Q, cexpire=%.17g" |
| 842 | 852 | " WHERE login=%Q", |
| 843 | 853 | zHash, |
| 844 | 854 | sqlite3_column_double(pStmt, 0), zLogin |
| 845 | 855 | ); |
| 856 | + db_protect_pop(); | |
| 846 | 857 | nXfer++; |
| 847 | 858 | } |
| 848 | 859 | sqlite3_finalize(pStmt); |
| 849 | 860 | } |
| 850 | 861 | sqlite3_close(pOther); |
| @@ -1619,11 +1630,13 @@ | ||
| 1619 | 1630 | "INSERT INTO user(login,pw,cap,info,mtime)\n" |
| 1620 | 1631 | "VALUES(%Q,%Q,%Q," |
| 1621 | 1632 | "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())", |
| 1622 | 1633 | zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr); |
| 1623 | 1634 | fossil_free(zPass); |
| 1635 | + db_unprotect(PROTECT_USER); | |
| 1624 | 1636 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1637 | + db_protect_pop(); | |
| 1625 | 1638 | uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID); |
| 1626 | 1639 | login_set_user_cookie(zUserID, uid, NULL, 0); |
| 1627 | 1640 | if( doAlerts ){ |
| 1628 | 1641 | /* Also make the new user a subscriber. */ |
| 1629 | 1642 | Blob hdr, body; |
| @@ -1832,14 +1845,16 @@ | ||
| 1832 | 1845 | while( db_step(&q)==SQLITE_ROW ){ |
| 1833 | 1846 | const char *zRepoName = db_column_text(&q, 1); |
| 1834 | 1847 | if( file_size(zRepoName, ExtFILE)<0 ){ |
| 1835 | 1848 | /* Silently remove non-existent repositories from the login group. */ |
| 1836 | 1849 | const char *zLabel = db_column_text(&q, 0); |
| 1850 | + db_unprotect(PROTECT_CONFIG); | |
| 1837 | 1851 | db_multi_exec( |
| 1838 | 1852 | "DELETE FROM config WHERE name GLOB 'peer-*-%q'", |
| 1839 | 1853 | &zLabel[10] |
| 1840 | 1854 | ); |
| 1855 | + db_protect_pop(); | |
| 1841 | 1856 | continue; |
| 1842 | 1857 | } |
| 1843 | 1858 | rc = sqlite3_open_v2( |
| 1844 | 1859 | zRepoName, &pPeer, |
| 1845 | 1860 | SQLITE_OPEN_READWRITE, |
| @@ -2004,11 +2019,13 @@ | ||
| 2004 | 2019 | "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());" |
| 2005 | 2020 | "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());" |
| 2006 | 2021 | "COMMIT;", |
| 2007 | 2022 | zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo |
| 2008 | 2023 | ); |
| 2024 | + db_unprotect(PROTECT_CONFIG); | |
| 2009 | 2025 | login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
| 2026 | + db_protect_pop(); | |
| 2010 | 2027 | fossil_free(zSql); |
| 2011 | 2028 | } |
| 2012 | 2029 | |
| 2013 | 2030 | /* |
| 2014 | 2031 | ** Leave the login group that we are currently part of. |
| @@ -2025,17 +2042,19 @@ | ||
| 2025 | 2042 | " WHERE name='login-group-name'" |
| 2026 | 2043 | " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;", |
| 2027 | 2044 | zProjCode |
| 2028 | 2045 | ); |
| 2029 | 2046 | fossil_free(zProjCode); |
| 2047 | + db_unprotect(PROTECT_CONFIG); | |
| 2030 | 2048 | login_group_sql(zSql, "<li> ", "</li>", pzErrMsg); |
| 2031 | 2049 | fossil_free(zSql); |
| 2032 | 2050 | db_multi_exec( |
| 2033 | 2051 | "DELETE FROM config " |
| 2034 | 2052 | " WHERE name GLOB 'peer-*'" |
| 2035 | 2053 | " OR name GLOB 'login-group-*';" |
| 2036 | 2054 | ); |
| 2055 | + db_protect_pop(); | |
| 2037 | 2056 | } |
| 2038 | 2057 | |
| 2039 | 2058 | /* |
| 2040 | 2059 | ** COMMAND: login-group* |
| 2041 | 2060 | ** |
| 2042 | 2061 |
| --- 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 @@ | ||
| 1227 | 1227 | #if defined(FOSSIL_DYNAMIC_BUILD) |
| 1228 | 1228 | blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1); |
| 1229 | 1229 | #else |
| 1230 | 1230 | blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1); |
| 1231 | 1231 | #endif |
| 1232 | -#if defined(FOSSIL_LEGACY_ALLOW_SYMLINKS) | |
| 1233 | - blob_append(pOut, "FOSSIL_LEGACY_ALLOW_SYMLINKS\n", -1); | |
| 1234 | -#endif | |
| 1235 | 1232 | #if defined(HAVE_PLEDGE) |
| 1236 | 1233 | blob_append(pOut, "HAVE_PLEDGE\n", -1); |
| 1237 | 1234 | #endif |
| 1238 | 1235 | #if defined(USE_MMAN_H) |
| 1239 | 1236 | blob_append(pOut, "USE_MMAN_H\n", -1); |
| @@ -1375,19 +1372,21 @@ | ||
| 1375 | 1372 | g.zTop = &g.zBaseURL[7+strlen(zHost)]; |
| 1376 | 1373 | g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur); |
| 1377 | 1374 | } |
| 1378 | 1375 | } |
| 1379 | 1376 | if( db_is_writeable("repository") ){ |
| 1377 | + db_unprotect(PROTECT_CONFIG); | |
| 1380 | 1378 | if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){ |
| 1381 | 1379 | db_multi_exec("INSERT INTO config(name,value,mtime)" |
| 1382 | 1380 | "VALUES('baseurl:%q',1,now())", g.zBaseURL); |
| 1383 | 1381 | }else{ |
| 1384 | 1382 | db_optional_sql("repository", |
| 1385 | 1383 | "REPLACE INTO config(name,value,mtime)" |
| 1386 | 1384 | "VALUES('baseurl:%q',1,now())", g.zBaseURL |
| 1387 | 1385 | ); |
| 1388 | 1386 | } |
| 1387 | + db_protect_pop(); | |
| 1389 | 1388 | } |
| 1390 | 1389 | } |
| 1391 | 1390 | |
| 1392 | 1391 | /* |
| 1393 | 1392 | ** Send an HTTP redirect back to the designated Index Page. |
| @@ -2637,10 +2636,11 @@ | ||
| 2637 | 2636 | g.zExtRoot = find_option("extroot",0,1); |
| 2638 | 2637 | find_server_repository(2, 0); |
| 2639 | 2638 | g.cgiOutput = 1; |
| 2640 | 2639 | g.fNoHttpCompress = 1; |
| 2641 | 2640 | g.fullHttpReply = 1; |
| 2641 | + g.sslNotAvailable = 1; /* Avoid attempts to redirect */ | |
| 2642 | 2642 | zIpAddr = cgi_ssh_remote_addr(0); |
| 2643 | 2643 | if( zIpAddr && zIpAddr[0] ){ |
| 2644 | 2644 | g.fSshClient |= CGI_SSH_CLIENT; |
| 2645 | 2645 | ssh_request_loop(zIpAddr, 0); |
| 2646 | 2646 | }else{ |
| 2647 | 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(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 @@ | ||
| 73 | 73 | $(SRCDIR)/http_socket.c \ |
| 74 | 74 | $(SRCDIR)/http_ssl.c \ |
| 75 | 75 | $(SRCDIR)/http_transport.c \ |
| 76 | 76 | $(SRCDIR)/import.c \ |
| 77 | 77 | $(SRCDIR)/info.c \ |
| 78 | + $(SRCDIR)/interwiki.c \ | |
| 78 | 79 | $(SRCDIR)/json.c \ |
| 79 | 80 | $(SRCDIR)/json_artifact.c \ |
| 80 | 81 | $(SRCDIR)/json_branch.c \ |
| 81 | 82 | $(SRCDIR)/json_config.c \ |
| 82 | 83 | $(SRCDIR)/json_diff.c \ |
| @@ -223,16 +224,19 @@ | ||
| 223 | 224 | $(SRCDIR)/default.css \ |
| 224 | 225 | $(SRCDIR)/diff.tcl \ |
| 225 | 226 | $(SRCDIR)/forum.js \ |
| 226 | 227 | $(SRCDIR)/fossil.bootstrap.js \ |
| 227 | 228 | $(SRCDIR)/fossil.confirmer.js \ |
| 229 | + $(SRCDIR)/fossil.copybutton.js \ | |
| 228 | 230 | $(SRCDIR)/fossil.dom.js \ |
| 229 | 231 | $(SRCDIR)/fossil.fetch.js \ |
| 232 | + $(SRCDIR)/fossil.numbered-lines.js \ | |
| 230 | 233 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 231 | 234 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 232 | 235 | $(SRCDIR)/fossil.page.whistory.js \ |
| 233 | 236 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 237 | + $(SRCDIR)/fossil.popupwidget.js \ | |
| 234 | 238 | $(SRCDIR)/fossil.storage.js \ |
| 235 | 239 | $(SRCDIR)/fossil.tabs.js \ |
| 236 | 240 | $(SRCDIR)/graph.js \ |
| 237 | 241 | $(SRCDIR)/href.js \ |
| 238 | 242 | $(SRCDIR)/login.js \ |
| @@ -323,10 +327,11 @@ | ||
| 323 | 327 | $(OBJDIR)/http_socket_.c \ |
| 324 | 328 | $(OBJDIR)/http_ssl_.c \ |
| 325 | 329 | $(OBJDIR)/http_transport_.c \ |
| 326 | 330 | $(OBJDIR)/import_.c \ |
| 327 | 331 | $(OBJDIR)/info_.c \ |
| 332 | + $(OBJDIR)/interwiki_.c \ | |
| 328 | 333 | $(OBJDIR)/json_.c \ |
| 329 | 334 | $(OBJDIR)/json_artifact_.c \ |
| 330 | 335 | $(OBJDIR)/json_branch_.c \ |
| 331 | 336 | $(OBJDIR)/json_config_.c \ |
| 332 | 337 | $(OBJDIR)/json_diff_.c \ |
| @@ -468,10 +473,11 @@ | ||
| 468 | 473 | $(OBJDIR)/http_socket.o \ |
| 469 | 474 | $(OBJDIR)/http_ssl.o \ |
| 470 | 475 | $(OBJDIR)/http_transport.o \ |
| 471 | 476 | $(OBJDIR)/import.o \ |
| 472 | 477 | $(OBJDIR)/info.o \ |
| 478 | + $(OBJDIR)/interwiki.o \ | |
| 473 | 479 | $(OBJDIR)/json.o \ |
| 474 | 480 | $(OBJDIR)/json_artifact.o \ |
| 475 | 481 | $(OBJDIR)/json_branch.o \ |
| 476 | 482 | $(OBJDIR)/json_config.o \ |
| 477 | 483 | $(OBJDIR)/json_diff.o \ |
| @@ -803,10 +809,11 @@ | ||
| 803 | 809 | $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ |
| 804 | 810 | $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| 805 | 811 | $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ |
| 806 | 812 | $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ |
| 807 | 813 | $(OBJDIR)/info_.c:$(OBJDIR)/info.h \ |
| 814 | + $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \ | |
| 808 | 815 | $(OBJDIR)/json_.c:$(OBJDIR)/json.h \ |
| 809 | 816 | $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \ |
| 810 | 817 | $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \ |
| 811 | 818 | $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \ |
| 812 | 819 | $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \ |
| @@ -1365,10 +1372,18 @@ | ||
| 1365 | 1372 | |
| 1366 | 1373 | $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h |
| 1367 | 1374 | $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c |
| 1368 | 1375 | |
| 1369 | 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 | |
| 1370 | 1385 | |
| 1371 | 1386 | $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate |
| 1372 | 1387 | $(OBJDIR)/translate $(SRCDIR)/json.c >$@ |
| 1373 | 1388 | |
| 1374 | 1389 | $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h |
| 1375 | 1390 |
| --- 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 @@ | ||
| 73 | 73 | $(SRCDIR)/http_socket.c \ |
| 74 | 74 | $(SRCDIR)/http_ssl.c \ |
| 75 | 75 | $(SRCDIR)/http_transport.c \ |
| 76 | 76 | $(SRCDIR)/import.c \ |
| 77 | 77 | $(SRCDIR)/info.c \ |
| 78 | + $(SRCDIR)/interwiki.c \ | |
| 78 | 79 | $(SRCDIR)/json.c \ |
| 79 | 80 | $(SRCDIR)/json_artifact.c \ |
| 80 | 81 | $(SRCDIR)/json_branch.c \ |
| 81 | 82 | $(SRCDIR)/json_config.c \ |
| 82 | 83 | $(SRCDIR)/json_diff.c \ |
| @@ -223,16 +224,19 @@ | ||
| 223 | 224 | $(SRCDIR)/default.css \ |
| 224 | 225 | $(SRCDIR)/diff.tcl \ |
| 225 | 226 | $(SRCDIR)/forum.js \ |
| 226 | 227 | $(SRCDIR)/fossil.bootstrap.js \ |
| 227 | 228 | $(SRCDIR)/fossil.confirmer.js \ |
| 229 | + $(SRCDIR)/fossil.copybutton.js \ | |
| 228 | 230 | $(SRCDIR)/fossil.dom.js \ |
| 229 | 231 | $(SRCDIR)/fossil.fetch.js \ |
| 232 | + $(SRCDIR)/fossil.numbered-lines.js \ | |
| 230 | 233 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 231 | 234 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 232 | 235 | $(SRCDIR)/fossil.page.whistory.js \ |
| 233 | 236 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 237 | + $(SRCDIR)/fossil.popupwidget.js \ | |
| 234 | 238 | $(SRCDIR)/fossil.storage.js \ |
| 235 | 239 | $(SRCDIR)/fossil.tabs.js \ |
| 236 | 240 | $(SRCDIR)/graph.js \ |
| 237 | 241 | $(SRCDIR)/href.js \ |
| 238 | 242 | $(SRCDIR)/login.js \ |
| @@ -323,10 +327,11 @@ | ||
| 323 | 327 | $(OBJDIR)/http_socket_.c \ |
| 324 | 328 | $(OBJDIR)/http_ssl_.c \ |
| 325 | 329 | $(OBJDIR)/http_transport_.c \ |
| 326 | 330 | $(OBJDIR)/import_.c \ |
| 327 | 331 | $(OBJDIR)/info_.c \ |
| 332 | + $(OBJDIR)/interwiki_.c \ | |
| 328 | 333 | $(OBJDIR)/json_.c \ |
| 329 | 334 | $(OBJDIR)/json_artifact_.c \ |
| 330 | 335 | $(OBJDIR)/json_branch_.c \ |
| 331 | 336 | $(OBJDIR)/json_config_.c \ |
| 332 | 337 | $(OBJDIR)/json_diff_.c \ |
| @@ -468,10 +473,11 @@ | ||
| 468 | 473 | $(OBJDIR)/http_socket.o \ |
| 469 | 474 | $(OBJDIR)/http_ssl.o \ |
| 470 | 475 | $(OBJDIR)/http_transport.o \ |
| 471 | 476 | $(OBJDIR)/import.o \ |
| 472 | 477 | $(OBJDIR)/info.o \ |
| 478 | + $(OBJDIR)/interwiki.o \ | |
| 473 | 479 | $(OBJDIR)/json.o \ |
| 474 | 480 | $(OBJDIR)/json_artifact.o \ |
| 475 | 481 | $(OBJDIR)/json_branch.o \ |
| 476 | 482 | $(OBJDIR)/json_config.o \ |
| 477 | 483 | $(OBJDIR)/json_diff.o \ |
| @@ -803,10 +809,11 @@ | ||
| 803 | 809 | $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ |
| 804 | 810 | $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| 805 | 811 | $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ |
| 806 | 812 | $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ |
| 807 | 813 | $(OBJDIR)/info_.c:$(OBJDIR)/info.h \ |
| 814 | + $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \ | |
| 808 | 815 | $(OBJDIR)/json_.c:$(OBJDIR)/json.h \ |
| 809 | 816 | $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \ |
| 810 | 817 | $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \ |
| 811 | 818 | $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \ |
| 812 | 819 | $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \ |
| @@ -1365,10 +1372,18 @@ | ||
| 1365 | 1372 | |
| 1366 | 1373 | $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h |
| 1367 | 1374 | $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c |
| 1368 | 1375 | |
| 1369 | 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 | |
| 1370 | 1385 | |
| 1371 | 1386 | $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate |
| 1372 | 1387 | $(OBJDIR)/translate $(SRCDIR)/json.c >$@ |
| 1373 | 1388 | |
| 1374 | 1389 | $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h |
| 1375 | 1390 |
| --- 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 |
+1
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -83,10 +83,11 @@ | ||
| 83 | 83 | http |
| 84 | 84 | http_socket |
| 85 | 85 | http_transport |
| 86 | 86 | import |
| 87 | 87 | info |
| 88 | + interwiki | |
| 88 | 89 | json |
| 89 | 90 | json_artifact |
| 90 | 91 | json_branch |
| 91 | 92 | json_config |
| 92 | 93 | json_diff |
| 93 | 94 |
| --- 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 @@ | ||
| 1814 | 1814 | if( !hname_validate(z, j) ) goto reparent_abort; |
| 1815 | 1815 | if( z[j]==0 ) break; |
| 1816 | 1816 | z[j] = 0; |
| 1817 | 1817 | i += j; |
| 1818 | 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 | - } | |
| 1819 | + p = manifest_get(rid, CFTYPE_MANIFEST, 0); | |
| 1824 | 1820 | if( p!=0 ){ |
| 1825 | 1821 | db_multi_exec( |
| 1826 | 1822 | "DELETE FROM plink WHERE cid=%d;" |
| 1827 | 1823 | "DELETE FROM mlink WHERE mid=%d;", |
| 1828 | 1824 | rid, rid |
| 1829 | 1825 | ); |
| 1830 | 1826 | manifest_add_checkin_linkages(rid,p,nParent,azParent); |
| 1827 | + manifest_destroy(p); | |
| 1831 | 1828 | } |
| 1832 | - manifest_destroy(p); | |
| 1833 | 1829 | reparent_abort: |
| 1834 | 1830 | fossil_free(azParent); |
| 1835 | 1831 | fossil_free(zCopy); |
| 1836 | 1832 | } |
| 1837 | 1833 | |
| @@ -2680,10 +2676,13 @@ | ||
| 2680 | 2676 | "VALUES('f',%.17g,%d,%Q,'%q: %q')", |
| 2681 | 2677 | p->rDate, rid, p->zUser, zFType, zTitle |
| 2682 | 2678 | ); |
| 2683 | 2679 | fossil_free(zTitle); |
| 2684 | 2680 | } |
| 2681 | + if( p->zWiki[0] ){ | |
| 2682 | + backlink_extract(p->zWiki, p->zMimetype, rid, BKLNK_FORUM, p->rDate, 1); | |
| 2683 | + } | |
| 2685 | 2684 | } |
| 2686 | 2685 | db_end_transaction(0); |
| 2687 | 2686 | if( permitHooks ){ |
| 2688 | 2687 | rc = xfer_run_common_script(); |
| 2689 | 2688 | if( rc==TH_OK ){ |
| 2690 | 2689 |
| --- 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 @@ | ||
| 1919 | 1919 | |
| 1920 | 1920 | /* fallback on default alignment if not explicit */ |
| 1921 | 1921 | if( align==0 && aligns && col<align_size ) align = aligns[col]; |
| 1922 | 1922 | |
| 1923 | 1923 | /* render cells */ |
| 1924 | - if( cells && end>beg ){ | |
| 1924 | + if( cells && end>=beg ){ | |
| 1925 | 1925 | parse_table_cell(cells, rndr, data+beg, end-beg, align|flags); |
| 1926 | 1926 | } |
| 1927 | 1927 | |
| 1928 | 1928 | col++; |
| 1929 | 1929 | } |
| 1930 | 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 |
| --- 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 @@ | ||
| 44 | 44 | > it may optionally be written **\<URL\>** (format 4). |
| 45 | 45 | > Other **URL** formats include: |
| 46 | 46 | > <ul> |
| 47 | 47 | > <li> A relative pathname. |
| 48 | 48 | > <li> A pathname starting with "/" in which case the Fossil server |
| 49 | -> URL prefix is prepended | |
| 49 | +> URL prefix is prepended | |
| 50 | 50 | > <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 | |
| 52 | 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> | |
| 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> | |
| 54 | 55 | |
| 55 | 56 | > In format 8, then the URL becomes the display text. This is useful for |
| 56 | 57 | > hyperlinks that refer to wiki pages and check-in and ticket hashes. |
| 57 | 58 | |
| 58 | 59 | ## Fonts ## |
| @@ -152,5 +153,8 @@ | ||
| 152 | 153 | > * For documents that begin with a top-level heading (ex: **# heading #**), |
| 153 | 154 | > the heading is omitted from the body of the document and becomes the |
| 154 | 155 | > document title displayed at the top of the Fossil page. |
| 155 | 156 | |
| 156 | 157 | [daringfireball.net]: http://daringfireball.net/projects/markdown/syntax |
| 158 | + | |
| 159 | +<a name="intermap"></a> | |
| 160 | +## Interwiki Tag Map | |
| 157 | 161 |
| --- 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 |
+6
| --- src/mkbuiltin.c | ||
| +++ src/mkbuiltin.c | ||
| @@ -62,10 +62,11 @@ | ||
| 62 | 62 | fclose(in); |
| 63 | 63 | z[got] = 0; |
| 64 | 64 | return z; |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | +#ifndef FOSSIL_DEBUG | |
| 67 | 68 | /* |
| 68 | 69 | ** Try to compress a javascript file by removing unnecessary whitespace. |
| 69 | 70 | ** |
| 70 | 71 | ** Warning: This compression routine does not necessarily work for any |
| 71 | 72 | ** arbitrary Javascript source file. But it should work ok for the |
| @@ -99,10 +100,11 @@ | ||
| 99 | 100 | z[j++] = c; |
| 100 | 101 | } |
| 101 | 102 | z[j] = 0; |
| 102 | 103 | *pn = j; |
| 103 | 104 | } |
| 105 | +#endif /* FOSSIL_DEBUG */ | |
| 104 | 106 | |
| 105 | 107 | /* |
| 106 | 108 | ** There is an instance of the following for each file translated. |
| 107 | 109 | */ |
| 108 | 110 | typedef struct Resource Resource; |
| @@ -279,11 +281,13 @@ | ||
| 279 | 281 | int nRes; |
| 280 | 282 | unsigned char *pData; |
| 281 | 283 | int nErr = 0; |
| 282 | 284 | int nSkip; |
| 283 | 285 | int nPrefix = 0; |
| 286 | +#ifndef FOSSIL_DEBUG | |
| 284 | 287 | int nName; |
| 288 | +#endif | |
| 285 | 289 | |
| 286 | 290 | if( argc==1 ){ |
| 287 | 291 | fprintf(stderr, "usage\t:%s " |
| 288 | 292 | "[--prefix path] [--reslist file] [resource-file1 ...]\n", |
| 289 | 293 | argv[0] |
| @@ -350,19 +354,21 @@ | ||
| 350 | 354 | while( pData[nSkip]=='#' ){ |
| 351 | 355 | while( pData[nSkip]!=0 && pData[nSkip]!='\n' ){ nSkip++; } |
| 352 | 356 | if( pData[nSkip]=='\n' ) nSkip++; |
| 353 | 357 | } |
| 354 | 358 | |
| 359 | +#ifndef FOSSIL_DEBUG | |
| 355 | 360 | /* Compress javascript source files */ |
| 356 | 361 | nName = (int)strlen(aRes[i].zName); |
| 357 | 362 | if( (nName>3 && strcmp(&aRes[i].zName[nName-3],".js")==0) |
| 358 | 363 | || (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0) |
| 359 | 364 | ){ |
| 360 | 365 | int x = sz-nSkip; |
| 361 | 366 | compressJavascript(pData+nSkip, &x); |
| 362 | 367 | sz = x + nSkip; |
| 363 | 368 | } |
| 369 | +#endif | |
| 364 | 370 | |
| 365 | 371 | aRes[i].nByte = sz - nSkip; |
| 366 | 372 | aRes[i].idx = i; |
| 367 | 373 | printf("/* Content of file %s */\n", aRes[i].zName); |
| 368 | 374 | printf("static const unsigned char bidata%d[%d] = {\n ", |
| 369 | 375 |
| --- 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 @@ | ||
| 90 | 90 | #define CMDFLAG_SETTING 0x0020 /* A setting */ |
| 91 | 91 | #define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */ |
| 92 | 92 | #define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */ |
| 93 | 93 | #define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */ |
| 94 | 94 | #define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */ |
| 95 | +#define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */ | |
| 95 | 96 | /**************************************************************************/ |
| 96 | 97 | |
| 97 | 98 | /* |
| 98 | 99 | ** Each entry looks like this: |
| 99 | 100 | */ |
| @@ -248,10 +249,12 @@ | ||
| 248 | 249 | }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){ |
| 249 | 250 | aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN); |
| 250 | 251 | aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT; |
| 251 | 252 | }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){ |
| 252 | 253 | aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE; |
| 254 | + }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){ | |
| 255 | + aEntry[nUsed].eType |= CMDFLAG_SENSITIVE; | |
| 253 | 256 | }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){ |
| 254 | 257 | aEntry[nUsed].iWidth = atoi(&zLine[i+6]); |
| 255 | 258 | }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){ |
| 256 | 259 | aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8); |
| 257 | 260 | }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){ |
| @@ -341,10 +344,12 @@ | ||
| 341 | 344 | i += 4; |
| 342 | 345 | if( !fossil_isspace(zLine[i]) ) goto page_skip; |
| 343 | 346 | while( fossil_isspace(zLine[i]) ){ i++; } |
| 344 | 347 | for(j=0; fossil_isident(zLine[i+j]); j++){} |
| 345 | 348 | if( j==0 ) goto page_skip; |
| 349 | + }else{ | |
| 350 | + j = 0; | |
| 346 | 351 | } |
| 347 | 352 | for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){} |
| 348 | 353 | nHelp = k+1; |
| 349 | 354 | zHelp[nHelp] = 0; |
| 350 | 355 | for(k=0; k<nHelp && fossil_isspace(zHelp[k]); k++){} |
| @@ -477,14 +482,15 @@ | ||
| 477 | 482 | if( zVar ){ |
| 478 | 483 | printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), ""); |
| 479 | 484 | }else{ |
| 480 | 485 | printf(" 0,%*s", 16, ""); |
| 481 | 486 | } |
| 482 | - printf(" %3d, %d, %d, \"%s\"%*s },\n", | |
| 487 | + printf(" %3d, %d, %d, %d, \"%s\"%*s },\n", | |
| 483 | 488 | aEntry[i].iWidth, |
| 484 | 489 | (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0, |
| 485 | 490 | (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0, |
| 491 | + (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0, | |
| 486 | 492 | zDef, (int)(10-strlen(zDef)), "" |
| 487 | 493 | ); |
| 488 | 494 | if( aEntry[i].zIf ){ |
| 489 | 495 | printf("#endif\n"); |
| 490 | 496 | } |
| 491 | 497 |
| --- 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 |
+2
| --- src/moderate.c | ||
| +++ src/moderate.c | ||
| @@ -159,10 +159,11 @@ | ||
| 159 | 159 | rid, rid, rid |
| 160 | 160 | ); |
| 161 | 161 | db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid); |
| 162 | 162 | admin_log("Approved moderation of rid %c-%d.", class, rid); |
| 163 | 163 | if( class!='a' ) search_doc_touch(class, rid, 0); |
| 164 | + setup_incr_cfgcnt(); | |
| 164 | 165 | db_end_transaction(0); |
| 165 | 166 | } |
| 166 | 167 | |
| 167 | 168 | /* |
| 168 | 169 | ** WEBPAGE: modreq |
| @@ -221,7 +222,8 @@ | ||
| 221 | 222 | while( db_step(&q)==SQLITE_ROW ){ |
| 222 | 223 | int const objid = db_column_int(&q, 0); |
| 223 | 224 | moderation_disapprove(objid); |
| 224 | 225 | } |
| 225 | 226 | db_finalize(&q); |
| 227 | + setup_incr_cfgcnt(); | |
| 226 | 228 | db_end_transaction(0); |
| 227 | 229 | } |
| 228 | 230 |
| --- 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 |
+3
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -1148,12 +1148,15 @@ | ||
| 1148 | 1148 | rc = fossil_print_error(rc, z); |
| 1149 | 1149 | abort(); |
| 1150 | 1150 | exit(rc); |
| 1151 | 1151 | } |
| 1152 | 1152 | NORETURN void fossil_fatal(const char *zFormat, ...){ |
| 1153 | + static int once = 0; | |
| 1153 | 1154 | char *z; |
| 1154 | 1155 | int rc = 1; |
| 1156 | + if( once ) exit(1); | |
| 1157 | + once = 1; | |
| 1155 | 1158 | va_list ap; |
| 1156 | 1159 | mainInFatalError = 1; |
| 1157 | 1160 | va_start(ap, zFormat); |
| 1158 | 1161 | z = vmprintf(zFormat, ap); |
| 1159 | 1162 | va_end(ap); |
| 1160 | 1163 |
| --- 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 |
+14
| --- src/rebuild.c | ||
| +++ src/rebuild.c | ||
| @@ -52,10 +52,11 @@ | ||
| 52 | 52 | } |
| 53 | 53 | |
| 54 | 54 | /* Add the user.mtime column if it is missing. (2011-04-27) |
| 55 | 55 | */ |
| 56 | 56 | if( !db_table_has_column("repository", "user", "mtime") ){ |
| 57 | + db_unprotect(PROTECT_ALL); | |
| 57 | 58 | db_multi_exec( |
| 58 | 59 | "CREATE TEMP TABLE temp_user AS SELECT * FROM user;" |
| 59 | 60 | "DROP TABLE user;" |
| 60 | 61 | "CREATE TABLE user(\n" |
| 61 | 62 | " uid INTEGER PRIMARY KEY,\n" |
| @@ -72,19 +73,22 @@ | ||
| 72 | 73 | "INSERT OR IGNORE INTO user" |
| 73 | 74 | " SELECT uid, login, pw, cap, cookie," |
| 74 | 75 | " ipaddr, cexpire, info, now(), photo FROM temp_user;" |
| 75 | 76 | "DROP TABLE temp_user;" |
| 76 | 77 | ); |
| 78 | + db_protect_pop(); | |
| 77 | 79 | } |
| 78 | 80 | |
| 79 | 81 | /* Add the config.mtime column if it is missing. (2011-04-27) |
| 80 | 82 | */ |
| 81 | 83 | if( !db_table_has_column("repository", "config", "mtime") ){ |
| 84 | + db_unprotect(PROTECT_CONFIG); | |
| 82 | 85 | db_multi_exec( |
| 83 | 86 | "ALTER TABLE config ADD COLUMN mtime INTEGER;" |
| 84 | 87 | "UPDATE config SET mtime=now();" |
| 85 | 88 | ); |
| 89 | + db_protect_pop(); | |
| 86 | 90 | } |
| 87 | 91 | |
| 88 | 92 | /* Add the shun.mtime and shun.scom columns if they are missing. |
| 89 | 93 | ** (2011-04-27) |
| 90 | 94 | */ |
| @@ -382,10 +386,11 @@ | ||
| 382 | 386 | percent_complete(0); |
| 383 | 387 | } |
| 384 | 388 | alert_triggers_disable(); |
| 385 | 389 | rebuild_update_schema(); |
| 386 | 390 | blob_init(&sql, 0, 0); |
| 391 | + db_unprotect(PROTECT_ALL); | |
| 387 | 392 | db_prepare(&q, |
| 388 | 393 | "SELECT name FROM sqlite_schema /*scan*/" |
| 389 | 394 | " WHERE type='table'" |
| 390 | 395 | " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias'," |
| 391 | 396 | "'config','shun','private','reportfmt'," |
| @@ -475,10 +480,11 @@ | ||
| 475 | 480 | alert_triggers_enable(); |
| 476 | 481 | if(!g.fQuiet && ttyOutput ){ |
| 477 | 482 | percent_complete(1000); |
| 478 | 483 | fossil_print("\n"); |
| 479 | 484 | } |
| 485 | + db_protect_pop(); | |
| 480 | 486 | return errCnt; |
| 481 | 487 | } |
| 482 | 488 | |
| 483 | 489 | /* |
| 484 | 490 | ** Number of neighbors to search |
| @@ -667,10 +673,11 @@ | ||
| 667 | 673 | |
| 668 | 674 | /* We should be done with options.. */ |
| 669 | 675 | verify_all_options(); |
| 670 | 676 | |
| 671 | 677 | db_begin_transaction(); |
| 678 | + db_unprotect(PROTECT_ALL); | |
| 672 | 679 | if( !compressOnlyFlag ){ |
| 673 | 680 | search_drop_index(); |
| 674 | 681 | ttyOutput = 1; |
| 675 | 682 | errCnt = rebuild_db(randomizeFlag, 1, doClustering); |
| 676 | 683 | reconstruct_private_table(); |
| @@ -720,10 +727,11 @@ | ||
| 720 | 727 | if( activateWal ){ |
| 721 | 728 | db_multi_exec("PRAGMA journal_mode=WAL;"); |
| 722 | 729 | } |
| 723 | 730 | } |
| 724 | 731 | if( runReindex ) search_rebuild_index(); |
| 732 | + db_protect_pop(); | |
| 725 | 733 | if( showStats ){ |
| 726 | 734 | static const struct { int idx; const char *zLabel; } aStat[] = { |
| 727 | 735 | { CFTYPE_ANY, "Artifacts:" }, |
| 728 | 736 | { CFTYPE_MANIFEST, "Manifests:" }, |
| 729 | 737 | { CFTYPE_CLUSTER, "Clusters:" }, |
| @@ -755,18 +763,20 @@ | ||
| 755 | 763 | ** testing by cloning a working project repository. |
| 756 | 764 | */ |
| 757 | 765 | void test_detach_cmd(void){ |
| 758 | 766 | db_find_and_open_repository(0, 2); |
| 759 | 767 | db_begin_transaction(); |
| 768 | + db_unprotect(PROTECT_CONFIG); | |
| 760 | 769 | db_multi_exec( |
| 761 | 770 | "DELETE FROM config WHERE name GLOB 'last-sync-*';" |
| 762 | 771 | "DELETE FROM config WHERE name GLOB 'sync-*:*';" |
| 763 | 772 | "UPDATE config SET value=lower(hex(randomblob(20)))" |
| 764 | 773 | " WHERE name='project-code';" |
| 765 | 774 | "UPDATE config SET value='detached-' || value" |
| 766 | 775 | " WHERE name='project-name' AND value NOT GLOB 'detached-*';" |
| 767 | 776 | ); |
| 777 | + db_protect_pop(); | |
| 768 | 778 | db_end_transaction(0); |
| 769 | 779 | } |
| 770 | 780 | |
| 771 | 781 | /* |
| 772 | 782 | ** COMMAND: test-create-clusters |
| @@ -910,10 +920,11 @@ | ||
| 910 | 920 | if( privateOnly || bVerily ){ |
| 911 | 921 | bNeedRebuild = db_exists("SELECT 1 FROM private"); |
| 912 | 922 | delete_private_content(); |
| 913 | 923 | } |
| 914 | 924 | if( !privateOnly ){ |
| 925 | + db_unprotect(PROTECT_ALL); | |
| 915 | 926 | db_multi_exec( |
| 916 | 927 | "UPDATE user SET pw='';" |
| 917 | 928 | "DELETE FROM config WHERE name GLOB 'last-sync-*';" |
| 918 | 929 | "DELETE FROM config WHERE name GLOB 'sync-*:*';" |
| 919 | 930 | "DELETE FROM config WHERE name GLOB 'peer-*';" |
| @@ -933,14 +944,17 @@ | ||
| 933 | 944 | "DROP TABLE IF EXISTS purgeitem;\n" |
| 934 | 945 | "DROP TABLE IF EXISTS admin_log;\n" |
| 935 | 946 | "DROP TABLE IF EXISTS vcache;\n" |
| 936 | 947 | ); |
| 937 | 948 | } |
| 949 | + db_protect_pop(); | |
| 938 | 950 | } |
| 939 | 951 | if( !bNeedRebuild ){ |
| 940 | 952 | db_end_transaction(0); |
| 953 | + db_unprotect(PROTECT_ALL); | |
| 941 | 954 | db_multi_exec("VACUUM;"); |
| 955 | + db_protect_pop(); | |
| 942 | 956 | }else{ |
| 943 | 957 | rebuild_db(0, 1, 0); |
| 944 | 958 | db_end_transaction(0); |
| 945 | 959 | } |
| 946 | 960 | } |
| 947 | 961 |
| --- 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 @@ | ||
| 1 | 1 | /* 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); | |
| 3 | 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').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 |
+10
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -281,10 +281,18 @@ | ||
| 281 | 281 | @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum" |
| 282 | 282 | @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5") |
| 283 | 283 | @ from users "anonymous" and "nobody" |
| 284 | 284 | @ on the <a href="setup_ulist">User Configuration</a> page. |
| 285 | 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 | + } | |
| 286 | 294 | |
| 287 | 295 | /* Obsolete: */ |
| 288 | 296 | if( hasAnyCap(zAnonCap, "d") || |
| 289 | 297 | hasAnyCap(zDevCap, "d") || |
| 290 | 298 | hasAnyCap(zReadCap, "d") ){ |
| @@ -596,15 +604,17 @@ | ||
| 596 | 604 | if( P("cancel") ){ |
| 597 | 605 | /* User pressed the cancel button. Go back */ |
| 598 | 606 | cgi_redirect("secaudit0"); |
| 599 | 607 | } |
| 600 | 608 | if( P("apply") ){ |
| 609 | + db_unprotect(PROTECT_USER); | |
| 601 | 610 | db_multi_exec( |
| 602 | 611 | "UPDATE user SET cap=''" |
| 603 | 612 | " WHERE login IN ('nobody','anonymous');" |
| 604 | 613 | "DELETE FROM config WHERE name='public-pages';" |
| 605 | 614 | ); |
| 615 | + db_protect_pop(); | |
| 606 | 616 | db_set("self-register","0",0); |
| 607 | 617 | cgi_redirect("secaudit0"); |
| 608 | 618 | } |
| 609 | 619 | style_header("Make This Website Private"); |
| 610 | 620 | @ <p>Click the "Make It Private" button below to disable all |
| 611 | 621 |
| --- 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 @@ | ||
| 27 | 27 | */ |
| 28 | 28 | void setup_incr_cfgcnt(void){ |
| 29 | 29 | static int once = 1; |
| 30 | 30 | if( once ){ |
| 31 | 31 | once = 0; |
| 32 | + db_unprotect(PROTECT_CONFIG); | |
| 32 | 33 | db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'"); |
| 33 | 34 | if( db_changes()==0 ){ |
| 34 | 35 | db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)"); |
| 35 | 36 | } |
| 37 | + db_protect_pop(); | |
| 36 | 38 | } |
| 37 | 39 | } |
| 38 | 40 | |
| 39 | 41 | /* |
| 40 | 42 | ** Output a single entry for a menu generated using an HTML table. |
| @@ -195,11 +197,13 @@ | ||
| 195 | 197 | } |
| 196 | 198 | if( zQ ){ |
| 197 | 199 | int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); |
| 198 | 200 | if( iQ!=iVal ){ |
| 199 | 201 | login_verify_csrf_secret(); |
| 202 | + db_protect_only(PROTECT_NONE); | |
| 200 | 203 | db_set(zVar, iQ ? "1" : "0", 0); |
| 204 | + db_protect_pop(); | |
| 201 | 205 | setup_incr_cfgcnt(); |
| 202 | 206 | admin_log("Set option [%q] to [%q].", |
| 203 | 207 | zVar, iQ ? "on" : "off"); |
| 204 | 208 | iVal = iQ; |
| 205 | 209 | } |
| @@ -230,11 +234,13 @@ | ||
| 230 | 234 | const char *zQ = P(zQParm); |
| 231 | 235 | if( zQ && fossil_strcmp(zQ,zVal)!=0 ){ |
| 232 | 236 | const int nZQ = (int)strlen(zQ); |
| 233 | 237 | login_verify_csrf_secret(); |
| 234 | 238 | setup_incr_cfgcnt(); |
| 239 | + db_protect_only(PROTECT_NONE); | |
| 235 | 240 | db_set(zVar, zQ, 0); |
| 241 | + db_protect_pop(); | |
| 236 | 242 | admin_log("Set entry_attribute %Q to: %.*s%s", |
| 237 | 243 | zVar, 20, zQ, (nZQ>20 ? "..." : "")); |
| 238 | 244 | zVal = zQ; |
| 239 | 245 | } |
| 240 | 246 | @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \ |
| @@ -260,11 +266,13 @@ | ||
| 260 | 266 | const char *z = db_get(zVar, zDflt); |
| 261 | 267 | const char *zQ = P(zQP); |
| 262 | 268 | if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){ |
| 263 | 269 | const int nZQ = (int)strlen(zQ); |
| 264 | 270 | login_verify_csrf_secret(); |
| 271 | + db_protect_only(PROTECT_NONE); | |
| 265 | 272 | db_set(zVar, zQ, 0); |
| 273 | + db_protect_pop(); | |
| 266 | 274 | setup_incr_cfgcnt(); |
| 267 | 275 | admin_log("Set textarea_attribute %Q to: %.*s%s", |
| 268 | 276 | zVar, 20, zQ, (nZQ>20 ? "..." : "")); |
| 269 | 277 | z = zQ; |
| 270 | 278 | } |
| @@ -1076,10 +1084,15 @@ | ||
| 1076 | 1084 | @ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except |
| 1077 | 1085 | @ in forum posts, make this setting be "<b>btw</b>". The default is an |
| 1078 | 1086 | @ empty string which means that Fossil never allows Markdown documents |
| 1079 | 1087 | @ to generate unsafe HTML. |
| 1080 | 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. | |
| 1081 | 1094 | @ <hr /> |
| 1082 | 1095 | onoff_attribute("Use HTML as wiki markup language", |
| 1083 | 1096 | "wiki-use-html", "wiki-use-html", 0, 0); |
| 1084 | 1097 | @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed |
| 1085 | 1098 | @ but all other wiki formatting will be ignored.</p> |
| @@ -1157,11 +1170,13 @@ | ||
| 1157 | 1170 | login_needed(0); |
| 1158 | 1171 | return; |
| 1159 | 1172 | } |
| 1160 | 1173 | db_begin_transaction(); |
| 1161 | 1174 | if( P("clear")!=0 && cgi_csrf_safe(1) ){ |
| 1175 | + db_unprotect(PROTECT_CONFIG); | |
| 1162 | 1176 | db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); |
| 1177 | + db_protect_pop(); | |
| 1163 | 1178 | cgi_replace_parameter("adunit",""); |
| 1164 | 1179 | cgi_replace_parameter("adright",""); |
| 1165 | 1180 | setup_incr_cfgcnt(); |
| 1166 | 1181 | } |
| 1167 | 1182 | |
| @@ -1255,10 +1270,11 @@ | ||
| 1255 | 1270 | if( !g.perm.Admin ){ |
| 1256 | 1271 | login_needed(0); |
| 1257 | 1272 | return; |
| 1258 | 1273 | } |
| 1259 | 1274 | db_begin_transaction(); |
| 1275 | + db_unprotect(PROTECT_CONFIG); | |
| 1260 | 1276 | if( !cgi_csrf_safe(1) ){ |
| 1261 | 1277 | /* Allow no state changes if not safe from CSRF */ |
| 1262 | 1278 | }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ |
| 1263 | 1279 | Blob img; |
| 1264 | 1280 | Stmt ins; |
| @@ -1285,10 +1301,11 @@ | ||
| 1285 | 1301 | cgi_redirect("setup_logo"); |
| 1286 | 1302 | }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){ |
| 1287 | 1303 | Blob img; |
| 1288 | 1304 | Stmt ins; |
| 1289 | 1305 | blob_init(&img, aBgImg, szBgImg); |
| 1306 | + db_unprotect(PROTECT_CONFIG); | |
| 1290 | 1307 | db_prepare(&ins, |
| 1291 | 1308 | "REPLACE INTO config(name,value,mtime)" |
| 1292 | 1309 | " VALUES('background-image',:bytes,now())" |
| 1293 | 1310 | ); |
| 1294 | 1311 | db_bind_blob(&ins, ":bytes", &img); |
| @@ -1297,13 +1314,15 @@ | ||
| 1297 | 1314 | db_multi_exec( |
| 1298 | 1315 | "REPLACE INTO config(name,value,mtime)" |
| 1299 | 1316 | " VALUES('background-mimetype',%Q,now())", |
| 1300 | 1317 | zBgMime |
| 1301 | 1318 | ); |
| 1319 | + db_protect_pop(); | |
| 1302 | 1320 | db_end_transaction(0); |
| 1303 | 1321 | cgi_redirect("setup_logo"); |
| 1304 | 1322 | }else if( P("clrbg")!=0 ){ |
| 1323 | + db_unprotect(PROTECT_CONFIG); | |
| 1305 | 1324 | db_multi_exec( |
| 1306 | 1325 | "DELETE FROM config WHERE name IN " |
| 1307 | 1326 | "('background-image','background-mimetype')" |
| 1308 | 1327 | ); |
| 1309 | 1328 | db_end_transaction(0); |
| @@ -1310,10 +1329,11 @@ | ||
| 1310 | 1329 | cgi_redirect("setup_logo"); |
| 1311 | 1330 | }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){ |
| 1312 | 1331 | Blob img; |
| 1313 | 1332 | Stmt ins; |
| 1314 | 1333 | blob_init(&img, aIconImg, szIconImg); |
| 1334 | + db_unprotect(PROTECT_CONFIG); | |
| 1315 | 1335 | db_prepare(&ins, |
| 1316 | 1336 | "REPLACE INTO config(name,value,mtime)" |
| 1317 | 1337 | " VALUES('icon-image',:bytes,now())" |
| 1318 | 1338 | ); |
| 1319 | 1339 | db_bind_blob(&ins, ":bytes", &img); |
| @@ -1322,10 +1342,11 @@ | ||
| 1322 | 1342 | db_multi_exec( |
| 1323 | 1343 | "REPLACE INTO config(name,value,mtime)" |
| 1324 | 1344 | " VALUES('icon-mimetype',%Q,now())", |
| 1325 | 1345 | zIconMime |
| 1326 | 1346 | ); |
| 1347 | + db_protect_pop(); | |
| 1327 | 1348 | db_end_transaction(0); |
| 1328 | 1349 | cgi_redirect("setup_logo"); |
| 1329 | 1350 | }else if( P("clricon")!=0 ){ |
| 1330 | 1351 | db_multi_exec( |
| 1331 | 1352 | "DELETE FROM config WHERE name IN " |
| @@ -1781,22 +1802,27 @@ | ||
| 1781 | 1802 | const char *zValue |
| 1782 | 1803 | ){ |
| 1783 | 1804 | if( !cgi_csrf_safe(1) ) return; |
| 1784 | 1805 | if( zNewName[0]==0 || zValue[0]==0 ){ |
| 1785 | 1806 | if( zOldName[0] ){ |
| 1807 | + db_unprotect(PROTECT_CONFIG); | |
| 1786 | 1808 | blob_append_sql(pSql, |
| 1787 | 1809 | "DELETE FROM config WHERE name='walias:%q';\n", |
| 1788 | 1810 | zOldName); |
| 1811 | + db_protect_pop(); | |
| 1789 | 1812 | } |
| 1790 | 1813 | return; |
| 1791 | 1814 | } |
| 1792 | 1815 | if( zOldName[0]==0 ){ |
| 1816 | + db_unprotect(PROTECT_CONFIG); | |
| 1793 | 1817 | blob_append_sql(pSql, |
| 1794 | 1818 | "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n", |
| 1795 | 1819 | zNewName, zValue); |
| 1820 | + db_protect_pop(); | |
| 1796 | 1821 | return; |
| 1797 | 1822 | } |
| 1823 | + db_unprotect(PROTECT_CONFIG); | |
| 1798 | 1824 | if( strcmp(zOldName, zNewName)!=0 ){ |
| 1799 | 1825 | blob_append_sql(pSql, |
| 1800 | 1826 | "UPDATE config SET name='walias:%q', value=%Q, mtime=now()" |
| 1801 | 1827 | " WHERE name='walias:%q';\n", |
| 1802 | 1828 | zNewName, zValue, zOldName); |
| @@ -1804,10 +1830,11 @@ | ||
| 1804 | 1830 | blob_append_sql(pSql, |
| 1805 | 1831 | "UPDATE config SET value=%Q, mtime=now()" |
| 1806 | 1832 | " WHERE name='walias:%q' AND value<>%Q;\n", |
| 1807 | 1833 | zValue, zOldName, zValue); |
| 1808 | 1834 | } |
| 1835 | + db_protect_pop(); | |
| 1809 | 1836 | } |
| 1810 | 1837 | |
| 1811 | 1838 | /* |
| 1812 | 1839 | ** WEBPAGE: waliassetup |
| 1813 | 1840 | ** |
| 1814 | 1841 |
| --- 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 |
+6
| --- src/setupuser.c | ||
| +++ src/setupuser.c | ||
| @@ -315,11 +315,13 @@ | ||
| 315 | 315 | /* Check for requests to delete the user */ |
| 316 | 316 | if( P("delete") && cgi_csrf_safe(1) ){ |
| 317 | 317 | int n; |
| 318 | 318 | if( P("verifydelete") ){ |
| 319 | 319 | /* Verified delete user request */ |
| 320 | + db_unprotect(PROTECT_USER); | |
| 320 | 321 | db_multi_exec("DELETE FROM user WHERE uid=%d", uid); |
| 322 | + db_protect_pop(); | |
| 321 | 323 | moderation_disapprove_for_missing_users(); |
| 322 | 324 | admin_log("Deleted user [%s] (uid %d).", |
| 323 | 325 | PD("login","???")/*safe-for-%s*/, uid); |
| 324 | 326 | cgi_redirect(cgi_referer("setup_ulist")); |
| 325 | 327 | return; |
| @@ -401,15 +403,17 @@ | ||
| 401 | 403 | @ [Bummer]</a></p> |
| 402 | 404 | style_footer(); |
| 403 | 405 | return; |
| 404 | 406 | } |
| 405 | 407 | login_verify_csrf_secret(); |
| 408 | + db_unprotect(PROTECT_USER); | |
| 406 | 409 | db_multi_exec( |
| 407 | 410 | "REPLACE INTO user(uid,login,info,pw,cap,mtime) " |
| 408 | 411 | "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", |
| 409 | 412 | uid, zLogin, P("info"), zPw, zCap |
| 410 | 413 | ); |
| 414 | + db_protect_pop(); | |
| 411 | 415 | setup_incr_cfgcnt(); |
| 412 | 416 | admin_log( "Updated user [%q] with capabilities [%q].", |
| 413 | 417 | zLogin, zCap ); |
| 414 | 418 | if( atoi(PD("all","0"))>0 ){ |
| 415 | 419 | Blob sql; |
| @@ -432,11 +436,13 @@ | ||
| 432 | 436 | " mtime=now()" |
| 433 | 437 | " WHERE login=%Q;", |
| 434 | 438 | zLogin, P("pw"), zLogin, P("info"), zCap, |
| 435 | 439 | zOldLogin |
| 436 | 440 | ); |
| 441 | + db_unprotect(PROTECT_USER); | |
| 437 | 442 | login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr); |
| 443 | + db_protect_pop(); | |
| 438 | 444 | blob_reset(&sql); |
| 439 | 445 | admin_log( "Updated user [%q] in all login groups " |
| 440 | 446 | "with capabilities [%q].", |
| 441 | 447 | zLogin, zCap ); |
| 442 | 448 | if( zErr ){ |
| 443 | 449 |
| --- 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 @@ | ||
| 360 | 360 | zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]); |
| 361 | 361 | z = builtin_text(zLabel); |
| 362 | 362 | fossil_free(zLabel); |
| 363 | 363 | } |
| 364 | 364 | } |
| 365 | + db_unprotect(PROTECT_CONFIG); | |
| 365 | 366 | blob_appendf(&val, |
| 366 | 367 | "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n", |
| 367 | 368 | azSkinFile[i], z |
| 368 | 369 | ); |
| 370 | + db_protect_pop(); | |
| 369 | 371 | } |
| 370 | 372 | return blob_str(&val); |
| 371 | 373 | } |
| 372 | 374 | |
| 373 | 375 | /* |
| @@ -402,14 +404,16 @@ | ||
| 402 | 404 | login_insert_csrf_secret(); |
| 403 | 405 | @ </div></form> |
| 404 | 406 | style_footer(); |
| 405 | 407 | return 1; |
| 406 | 408 | } |
| 409 | + db_unprotect(PROTECT_CONFIG); | |
| 407 | 410 | db_multi_exec( |
| 408 | 411 | "UPDATE config SET name='skin:%q' WHERE name='skin:%q';", |
| 409 | 412 | zNewName, zOldName |
| 410 | 413 | ); |
| 414 | + db_protect_pop(); | |
| 411 | 415 | return 0; |
| 412 | 416 | } |
| 413 | 417 | |
| 414 | 418 | /* |
| 415 | 419 | ** Respond to a Save button press. Return TRUE if a dialog was painted. |
| @@ -440,15 +444,17 @@ | ||
| 440 | 444 | login_insert_csrf_secret(); |
| 441 | 445 | @ </div></form> |
| 442 | 446 | style_footer(); |
| 443 | 447 | return 1; |
| 444 | 448 | } |
| 449 | + db_unprotect(PROTECT_CONFIG); | |
| 445 | 450 | db_multi_exec( |
| 446 | 451 | "INSERT OR IGNORE INTO config(name, value, mtime)" |
| 447 | 452 | "VALUES('skin:%q',%Q,now())", |
| 448 | 453 | zNewName, zCurrent |
| 449 | 454 | ); |
| 455 | + db_protect_pop(); | |
| 450 | 456 | return 0; |
| 451 | 457 | } |
| 452 | 458 | |
| 453 | 459 | /* |
| 454 | 460 | ** WEBPAGE: setup_skin_admin |
| @@ -491,16 +497,20 @@ | ||
| 491 | 497 | style_footer(); |
| 492 | 498 | db_end_transaction(1); |
| 493 | 499 | return; |
| 494 | 500 | } |
| 495 | 501 | if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){ |
| 502 | + db_unprotect(PROTECT_CONFIG); | |
| 496 | 503 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 504 | + db_protect_pop(); | |
| 497 | 505 | } |
| 498 | 506 | if( P("draftdel")!=0 ){ |
| 499 | 507 | const char *zDraft = P("name"); |
| 500 | 508 | if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){ |
| 509 | + db_unprotect(PROTECT_CONFIG); | |
| 501 | 510 | db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft); |
| 511 | + db_protect_pop(); | |
| 502 | 512 | } |
| 503 | 513 | } |
| 504 | 514 | if( skinRename() || skinSave(zCurrent) ){ |
| 505 | 515 | db_end_transaction(0); |
| 506 | 516 | return; |
| @@ -521,15 +531,17 @@ | ||
| 521 | 531 | } |
| 522 | 532 | if( !seen ){ |
| 523 | 533 | seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'" |
| 524 | 534 | " AND value=%Q", zCurrent); |
| 525 | 535 | if( !seen ){ |
| 536 | + db_unprotect(PROTECT_CONFIG); | |
| 526 | 537 | db_multi_exec( |
| 527 | 538 | "INSERT INTO config(name,value,mtime) VALUES(" |
| 528 | 539 | " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," |
| 529 | 540 | " %Q,now())", zCurrent |
| 530 | 541 | ); |
| 542 | + db_protect_pop(); | |
| 531 | 543 | } |
| 532 | 544 | } |
| 533 | 545 | seen = 0; |
| 534 | 546 | for(i=0; i<count(aBuiltinSkin); i++){ |
| 535 | 547 | if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){ |
| @@ -867,15 +879,17 @@ | ||
| 867 | 879 | if( !seen ){ |
| 868 | 880 | seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'" |
| 869 | 881 | " AND value=%Q", zCurrent); |
| 870 | 882 | } |
| 871 | 883 | if( !seen ){ |
| 884 | + db_unprotect(PROTECT_CONFIG); | |
| 872 | 885 | db_multi_exec( |
| 873 | 886 | "INSERT INTO config(name,value,mtime) VALUES(" |
| 874 | 887 | " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S')," |
| 875 | 888 | " %Q,now())", zCurrent |
| 876 | 889 | ); |
| 890 | + db_protect_pop(); | |
| 877 | 891 | } |
| 878 | 892 | |
| 879 | 893 | /* Publish draft iSkin */ |
| 880 | 894 | for(i=0; i<count(azSkinFile); i++){ |
| 881 | 895 | char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]); |
| 882 | 896 |
| --- 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 |
+1
-1
| --- src/sounds/README.md | ||
| +++ src/sounds/README.md | ||
| @@ -3,11 +3,11 @@ | ||
| 3 | 3 | hexadecimal digits (as is the case for captchas generated by the |
| 4 | 4 | [../captcha.c module](../captcha.c)) then these WAV files can be |
| 5 | 5 | concatenated together to generate an audio reading of the captcha, which |
| 6 | 6 | enables visually impaired users to complete the captcha. |
| 7 | 7 | |
| 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 | |
| 9 | 9 | and are 6000 samples in length. |
| 10 | 10 | |
| 11 | 11 | The recordings are made by Philip Bennefall and are of his own voice. |
| 12 | 12 | Mr. Bennefall is himself blind and uses this system implemented with these |
| 13 | 13 | recordings to complete captchas for Fossil. |
| 14 | 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 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 @@ | ||
| 153 | 153 | sqlcmd_decompress, 0, 0); |
| 154 | 154 | sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0, |
| 155 | 155 | sqlcmd_gather_artifact_stats, 0, 0); |
| 156 | 156 | return SQLITE_OK; |
| 157 | 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 | + | |
| 158 | 192 | |
| 159 | 193 | /* |
| 160 | 194 | ** This is the "automatic extension" initializer that runs right after |
| 161 | 195 | ** the connection to the repository database is opened. Set up the |
| 162 | 196 | ** database connection to be more useful to the human operator. |
| @@ -193,10 +227,16 @@ | ||
| 193 | 227 | } |
| 194 | 228 | /* Arrange to trace close operations so that static prepared statements |
| 195 | 229 | ** will get cleaned up when the shell closes the database connection */ |
| 196 | 230 | if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE; |
| 197 | 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); | |
| 198 | 238 | return SQLITE_OK; |
| 199 | 239 | } |
| 200 | 240 | |
| 201 | 241 | /* |
| 202 | 242 | ** atexit() handler that cleans up global state modified by this module. |
| 203 | 243 |
| --- 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 @@ | ||
| 153 | 153 | fsize = file_size(g.zRepositoryName, ExtFILE); |
| 154 | 154 | @ <tr><th>Repository Size:</th><td>%,lld(fsize) bytes</td> |
| 155 | 155 | @ </td></tr> |
| 156 | 156 | if( !brief ){ |
| 157 | 157 | @ <tr><th>Number Of 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"); | |
| 159 | 159 | m = db_int(0, "SELECT count(*) FROM delta"); |
| 160 | 160 | @ %.d(n) (%,d(n-m) fulltext and %,d(m) deltas) |
| 161 | 161 | if( g.perm.Write ){ |
| 162 | 162 | @ <a href='%R/artifact_stats'>Details</a> |
| 163 | 163 | } |
| @@ -165,11 +165,11 @@ | ||
| 165 | 165 | if( n>0 ){ |
| 166 | 166 | int a, b; |
| 167 | 167 | Stmt q; |
| 168 | 168 | @ <tr><th>Uncompressed Artifact Size:</th><td> |
| 169 | 169 | 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*/"); | |
| 171 | 171 | db_step(&q); |
| 172 | 172 | t = db_column_int64(&q, 0); |
| 173 | 173 | szAvg = db_column_int(&q, 1); |
| 174 | 174 | szMax = db_column_int(&q, 2); |
| 175 | 175 | db_finalize(&q); |
| 176 | 176 |
| --- src/stat.c | |
| +++ src/stat.c | |
| @@ -153,11 +153,11 @@ | |
| 153 | fsize = file_size(g.zRepositoryName, ExtFILE); |
| 154 | @ <tr><th>Repository Size:</th><td>%,lld(fsize) bytes</td> |
| 155 | @ </td></tr> |
| 156 | if( !brief ){ |
| 157 | @ <tr><th>Number Of 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 Artifact 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 Size:</th><td>%,lld(fsize) bytes</td> |
| 155 | @ </td></tr> |
| 156 | if( !brief ){ |
| 157 | @ <tr><th>Number Of 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 Artifact 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 @@ | ||
| 425 | 425 | /* fossil remote off |
| 426 | 426 | ** Forget the last-sync-URL and its password |
| 427 | 427 | */ |
| 428 | 428 | if( g.argc!=3 ) usage("off"); |
| 429 | 429 | remote_delete_default: |
| 430 | + db_unprotect(PROTECT_CONFIG); | |
| 430 | 431 | db_multi_exec( |
| 431 | 432 | "DELETE FROM config WHERE name GLOB 'last-sync-*';" |
| 432 | 433 | ); |
| 434 | + db_protect_pop(); | |
| 433 | 435 | return; |
| 434 | 436 | } |
| 435 | 437 | if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){ |
| 436 | 438 | Stmt q; |
| 437 | 439 | if( g.argc!=3 ) usage("list"); |
| @@ -457,10 +459,11 @@ | ||
| 457 | 459 | zName = g.argv[3]; |
| 458 | 460 | zUrl = g.argv[4]; |
| 459 | 461 | if( strcmp(zName,"default")==0 ) goto remote_add_default; |
| 460 | 462 | url_parse_local(zUrl, URL_PROMPT_PW, &x); |
| 461 | 463 | db_begin_write(); |
| 464 | + db_unprotect(PROTECT_CONFIG); | |
| 462 | 465 | db_multi_exec( |
| 463 | 466 | "REPLACE INTO config(name, value, mtime)" |
| 464 | 467 | " VALUES('sync-url:%q',%Q,now())", |
| 465 | 468 | zName, x.canonical |
| 466 | 469 | ); |
| @@ -467,21 +470,24 @@ | ||
| 467 | 470 | db_multi_exec( |
| 468 | 471 | "REPLACE INTO config(name, value, mtime)" |
| 469 | 472 | " VALUES('sync-pw:%q',obscure(%Q),now())", |
| 470 | 473 | zName, x.passwd |
| 471 | 474 | ); |
| 475 | + db_protect_pop(); | |
| 472 | 476 | db_commit_transaction(); |
| 473 | 477 | return; |
| 474 | 478 | } |
| 475 | 479 | if( strncmp(zArg, "delete", nArg)==0 ){ |
| 476 | 480 | char *zName; |
| 477 | 481 | if( g.argc!=4 ) usage("delete NAME"); |
| 478 | 482 | zName = g.argv[3]; |
| 479 | 483 | if( strcmp(zName,"default")==0 ) goto remote_delete_default; |
| 480 | 484 | db_begin_write(); |
| 485 | + db_unprotect(PROTECT_CONFIG); | |
| 481 | 486 | db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName); |
| 482 | 487 | db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName); |
| 488 | + db_protect_pop(); | |
| 483 | 489 | db_commit_transaction(); |
| 484 | 490 | return; |
| 485 | 491 | } |
| 486 | 492 | if( sqlite3_strlike("http://%",zArg,0)==0 |
| 487 | 493 | || sqlite3_strlike("https://%",zArg,0)==0 |
| @@ -505,20 +511,20 @@ | ||
| 505 | 511 | ** |
| 506 | 512 | ** Usage: %fossil backup ?OPTIONS? FILE|DIRECTORY |
| 507 | 513 | ** |
| 508 | 514 | ** Make a backup of the repository into the named file or into the named |
| 509 | 515 | ** 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 | |
| 511 | 517 | ** is safe to run "fossil backup" on a repository that is in active use. |
| 512 | 518 | ** |
| 513 | 519 | ** Only the main repository database is backed up by this command. The |
| 514 | 520 | ** open checkout file (if any) is not saved. Nor is the global configuration |
| 515 | 521 | ** database. |
| 516 | 522 | ** |
| 517 | 523 | ** Options: |
| 518 | 524 | ** |
| 519 | -** --overwrite OK to overwrite an existing file. | |
| 525 | +** --overwrite OK to overwrite an existing file | |
| 520 | 526 | ** -R NAME Filename of the repository to backup |
| 521 | 527 | */ |
| 522 | 528 | void backup_cmd(void){ |
| 523 | 529 | char *zDest; |
| 524 | 530 | int bOverwrite = 0; |
| @@ -539,7 +545,8 @@ | ||
| 539 | 545 | } |
| 540 | 546 | }else{ |
| 541 | 547 | fossil_fatal("backup \"%s\" already exists", zDest); |
| 542 | 548 | } |
| 543 | 549 | } |
| 550 | + db_unprotect(PROTECT_ALL); | |
| 544 | 551 | db_multi_exec("VACUUM repository INTO %Q", zDest); |
| 545 | 552 | } |
| 546 | 553 |
| --- 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 @@ | ||
| 451 | 451 | db_multi_exec( |
| 452 | 452 | "DROP TABLE IF EXISTS ticket;" |
| 453 | 453 | "DROP TABLE IF EXISTS ticketchng;" |
| 454 | 454 | ); |
| 455 | 455 | zSql = ticket_table_schema(); |
| 456 | + db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema"); | |
| 456 | 457 | if( separateConnection ){ |
| 457 | 458 | if( db_transaction_nesting_depth() ) db_end_transaction(0); |
| 458 | - db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema"); | |
| 459 | 459 | db_init_database(g.zRepositoryName, zSql, 0); |
| 460 | 460 | }else{ |
| 461 | - db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema"); | |
| 462 | 461 | db_multi_exec("%s", zSql/*safe-for-%s*/); |
| 463 | 462 | } |
| 464 | 463 | db_clear_authorizer(); |
| 465 | 464 | fossil_free(zSql); |
| 466 | 465 | } |
| 467 | 466 |
| --- 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 @@ | ||
| 155 | 155 | fprintf(out,"\n"); |
| 156 | 156 | }else{ |
| 157 | 157 | fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline); |
| 158 | 158 | } |
| 159 | 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. | |
| 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". | |
| 165 | 169 | */ |
| 166 | 170 | const char *zNewline = "\\n"; |
| 167 | 171 | int indent; |
| 168 | 172 | int nC; |
| 173 | + int nParam; | |
| 169 | 174 | char c; |
| 170 | 175 | i++; |
| 171 | 176 | if( isspace(zLine[i]) ){ i++; } |
| 172 | 177 | indent = i; |
| 173 | 178 | for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){ |
| @@ -177,25 +182,31 @@ | ||
| 177 | 182 | break; |
| 178 | 183 | } |
| 179 | 184 | if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; } |
| 180 | 185 | zOut[j++] = zLine[i]; |
| 181 | 186 | 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 | + } | |
| 183 | 191 | if( zLine[i+nC]!='(' || !isalpha(zLine[i+nC-1]) ) continue; |
| 184 | 192 | 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 ); | |
| 197 | 208 | } |
| 198 | 209 | zOut[j] = 0; |
| 199 | 210 | if( !inPrint ){ |
| 200 | 211 | fprintf(out,"%*scgi_printf(\"%s%s\"",indent-2,"", zOut, zNewline); |
| 201 | 212 | inPrint = 1; |
| 202 | 213 |
| --- 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 @@ | ||
| 50 | 50 | int isHttps; /* True if a "https:" url */ |
| 51 | 51 | int isSsh; /* True if an "ssh:" url */ |
| 52 | 52 | int isAlias; /* Input URL was an alias */ |
| 53 | 53 | char *name; /* Hostname for http: or filename for file: */ |
| 54 | 54 | 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" */ | |
| 56 | 56 | int port; /* TCP port number for http: or https: */ |
| 57 | 57 | int dfltPort; /* The default port for the given protocol */ |
| 58 | 58 | char *path; /* Pathname for http: */ |
| 59 | 59 | char *user; /* User id for http: */ |
| 60 | 60 | char *passwd; /* Password for http: */ |
| @@ -76,11 +76,11 @@ | ||
| 76 | 76 | ** as follows: |
| 77 | 77 | ** |
| 78 | 78 | ** isFile True if FILE: |
| 79 | 79 | ** isHttps True if HTTPS: |
| 80 | 80 | ** isSsh True if SSH: |
| 81 | -** protocol "http" or "https" or "file" | |
| 81 | +** protocol "http" or "https" or "file" or "ssh" | |
| 82 | 82 | ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: |
| 83 | 83 | ** port TCP port number for HTTP or HTTPS. |
| 84 | 84 | ** dfltPort Default TCP port number (80 or 443). |
| 85 | 85 | ** path Path name for HTTP or HTTPS. |
| 86 | 86 | ** user Userid. |
| @@ -305,11 +305,11 @@ | ||
| 305 | 305 | ** form last-sync-pw. |
| 306 | 306 | ** |
| 307 | 307 | ** g.url.isFile True if FILE: |
| 308 | 308 | ** g.url.isHttps True if HTTPS: |
| 309 | 309 | ** 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" | |
| 311 | 311 | ** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: |
| 312 | 312 | ** g.url.port TCP port number for HTTP or HTTPS. |
| 313 | 313 | ** g.url.dfltPort Default TCP port number (80 or 443). |
| 314 | 314 | ** g.url.path Path name for HTTP or HTTPS. |
| 315 | 315 | ** g.url.user Userid. |
| 316 | 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" */ |
| 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 @@ | ||
| 432 | 432 | } |
| 433 | 433 | if( blob_size(&pw)==0 ){ |
| 434 | 434 | fossil_print("password unchanged\n"); |
| 435 | 435 | }else{ |
| 436 | 436 | char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0); |
| 437 | + db_unprotect(PROTECT_USER); | |
| 437 | 438 | db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d", |
| 438 | 439 | zSecret, uid); |
| 440 | + db_protect_pop(); | |
| 439 | 441 | free(zSecret); |
| 440 | 442 | } |
| 441 | 443 | }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){ |
| 442 | 444 | int uid; |
| 443 | 445 | if( g.argc!=4 && g.argc!=5 ){ |
| @@ -446,14 +448,16 @@ | ||
| 446 | 448 | uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]); |
| 447 | 449 | if( uid==0 ){ |
| 448 | 450 | fossil_fatal("no such user: %s", g.argv[3]); |
| 449 | 451 | } |
| 450 | 452 | if( g.argc==5 ){ |
| 453 | + db_unprotect(PROTECT_USER); | |
| 451 | 454 | db_multi_exec( |
| 452 | 455 | "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d", |
| 453 | 456 | g.argv[4], uid |
| 454 | 457 | ); |
| 458 | + db_protect_pop(); | |
| 455 | 459 | } |
| 456 | 460 | fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid)); |
| 457 | 461 | }else{ |
| 458 | 462 | fossil_fatal("user subcommand should be one of: " |
| 459 | 463 | "capabilities default list new password"); |
| @@ -573,10 +577,11 @@ | ||
| 573 | 577 | void user_hash_passwords_cmd(void){ |
| 574 | 578 | if( g.argc!=3 ) usage("REPOSITORY"); |
| 575 | 579 | db_open_repository(g.argv[2]); |
| 576 | 580 | sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0, |
| 577 | 581 | sha1_shared_secret_sql_function, 0, 0); |
| 582 | + db_unprotect(PROTECT_ALL); | |
| 578 | 583 | db_multi_exec( |
| 579 | 584 | "UPDATE user SET pw=shared_secret(pw,login), mtime=now()" |
| 580 | 585 | " WHERE length(pw)>0 AND length(pw)!=40" |
| 581 | 586 | ); |
| 582 | 587 | } |
| 583 | 588 |
| --- 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 @@ | ||
| 691 | 691 | int i; |
| 692 | 692 | char z[60]; |
| 693 | 693 | |
| 694 | 694 | /* Source characters for the password. Omit characters like "0", "O", |
| 695 | 695 | ** "1" and "I" that might be easily confused */ |
| 696 | - static const char zAlphabet[] = | |
| 696 | + static const char zAlphabet[] = | |
| 697 | 697 | /* 0 1 2 3 4 5 */ |
| 698 | 698 | /* 123456789 123456789 123456789 123456789 123456789 123456 */ |
| 699 | 699 | "23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; |
| 700 | 700 | |
| 701 | 701 | if( N<8 ) N = 8; |
| @@ -728,5 +728,15 @@ | ||
| 728 | 728 | if( g.argc>=3 ){ |
| 729 | 729 | N = atoi(g.argv[2]); |
| 730 | 730 | } |
| 731 | 731 | fossil_print("%s\n", fossil_random_password(N)); |
| 732 | 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 | +} | |
| 733 | 743 |
| --- 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 @@ | ||
| 220 | 220 | if( fTxt ){ |
| 221 | 221 | style_submenu_element("Formatted", "%R/md_rules"); |
| 222 | 222 | }else{ |
| 223 | 223 | style_submenu_element("Plain-Text", "%R/md_rules?txt=1"); |
| 224 | 224 | } |
| 225 | + style_submenu_element("Wiki", "%R/wiki_rules"); | |
| 225 | 226 | blob_init(&x, builtin_text("markdown.md"), -1); |
| 226 | 227 | blob_materialize(&x); |
| 228 | + interwiki_append_map_table(&x); | |
| 227 | 229 | safe_html_context(DOCSRC_TRUSTED); |
| 228 | 230 | wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown"); |
| 229 | 231 | blob_reset(&x); |
| 230 | 232 | style_footer(); |
| 231 | 233 | } |
| @@ -242,12 +244,14 @@ | ||
| 242 | 244 | if( fTxt ){ |
| 243 | 245 | style_submenu_element("Formatted", "%R/wiki_rules"); |
| 244 | 246 | }else{ |
| 245 | 247 | style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1"); |
| 246 | 248 | } |
| 249 | + style_submenu_element("Markdown","%R/md_rules"); | |
| 247 | 250 | blob_init(&x, builtin_text("wiki.wiki"), -1); |
| 248 | 251 | blob_materialize(&x); |
| 252 | + interwiki_append_map_table(&x); | |
| 249 | 253 | safe_html_context(DOCSRC_TRUSTED); |
| 250 | 254 | wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki"); |
| 251 | 255 | blob_reset(&x); |
| 252 | 256 | style_footer(); |
| 253 | 257 | } |
| @@ -978,11 +982,12 @@ | ||
| 978 | 982 | Stmt q = empty_Stmt; |
| 979 | 983 | int n = 0; |
| 980 | 984 | db_begin_transaction(); |
| 981 | 985 | db_prepare(&q, "SELECT" |
| 982 | 986 | " substr(tagname,6) AS name" |
| 983 | - " FROM tag WHERE tagname GLOB 'wiki-*'" | |
| 987 | + " FROM tag JOIN tagxref USING('tagid')" | |
| 988 | + " WHERE tagname GLOB 'wiki-*'" | |
| 984 | 989 | " UNION SELECT 'Sandbox' AS name" |
| 985 | 990 | " ORDER BY name COLLATE NOCASE"); |
| 986 | 991 | CX("["); |
| 987 | 992 | while( SQLITE_ROW==db_step(&q) ){ |
| 988 | 993 | char const * zName = db_column_text(&q,0); |
| 989 | 994 |
| --- 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 @@ | ||
| 220 | 220 | if( fTxt ){ |
| 221 | 221 | style_submenu_element("Formatted", "%R/md_rules"); |
| 222 | 222 | }else{ |
| 223 | 223 | style_submenu_element("Plain-Text", "%R/md_rules?txt=1"); |
| 224 | 224 | } |
| 225 | + style_submenu_element("Wiki", "%R/wiki_rules"); | |
| 225 | 226 | blob_init(&x, builtin_text("markdown.md"), -1); |
| 226 | 227 | blob_materialize(&x); |
| 228 | + interwiki_append_map_table(&x); | |
| 227 | 229 | safe_html_context(DOCSRC_TRUSTED); |
| 228 | 230 | wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown"); |
| 229 | 231 | blob_reset(&x); |
| 230 | 232 | style_footer(); |
| 231 | 233 | } |
| @@ -242,12 +244,14 @@ | ||
| 242 | 244 | if( fTxt ){ |
| 243 | 245 | style_submenu_element("Formatted", "%R/wiki_rules"); |
| 244 | 246 | }else{ |
| 245 | 247 | style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1"); |
| 246 | 248 | } |
| 249 | + style_submenu_element("Markdown","%R/md_rules"); | |
| 247 | 250 | blob_init(&x, builtin_text("wiki.wiki"), -1); |
| 248 | 251 | blob_materialize(&x); |
| 252 | + interwiki_append_map_table(&x); | |
| 249 | 253 | safe_html_context(DOCSRC_TRUSTED); |
| 250 | 254 | wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki"); |
| 251 | 255 | blob_reset(&x); |
| 252 | 256 | style_footer(); |
| 253 | 257 | } |
| @@ -978,11 +982,12 @@ | ||
| 978 | 982 | Stmt q = empty_Stmt; |
| 979 | 983 | int n = 0; |
| 980 | 984 | db_begin_transaction(); |
| 981 | 985 | db_prepare(&q, "SELECT" |
| 982 | 986 | " substr(tagname,6) AS name" |
| 983 | - " FROM tag WHERE tagname GLOB 'wiki-*'" | |
| 987 | + " FROM tag JOIN tagxref USING('tagid')" | |
| 988 | + " WHERE tagname GLOB 'wiki-*'" | |
| 984 | 989 | " UNION SELECT 'Sandbox' AS name" |
| 985 | 990 | " ORDER BY name COLLATE NOCASE"); |
| 986 | 991 | CX("["); |
| 987 | 992 | while( SQLITE_ROW==db_step(&q) ){ |
| 988 | 993 | char const * zName = db_column_text(&q,0); |
| 989 | 994 |
| --- 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 @@ | ||
| 4 | 4 | # Bullets are "*" surrounded by two spaces at the beginning of a line |
| 5 | 5 | # Enumeration items are "#" or a digit and a "." surrounded by two |
| 6 | 6 | spaces at the beginning of a line |
| 7 | 7 | # Indented paragraphs begin with a tab or two spaces |
| 8 | 8 | # 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> | |
| 10 | 11 | # Most ordinary HTML works |
| 11 | 12 | # <verbatim> and <nowiki> |
| 12 | 13 | |
| 13 | 14 | We call the first five rules above the "wiki" formatting rules. |
| 14 | 15 | The last two rules are the HTML formatting rules. |
| @@ -42,11 +43,13 @@ | ||
| 42 | 43 | Use HTML for deeper indentation. |
| 43 | 44 | |
| 44 | 45 | 5. <b>Hyperlinks.</b> |
| 45 | 46 | Text within square brackets <nowiki>("[...]")</nowiki> becomes a |
| 46 | 47 | 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>". | |
| 48 | 51 | By default, the target is displayed as the text of the hyperlink. |
| 49 | 52 | But you can specify alternative text after the target name |
| 50 | 53 | separated by a "|" character. |
| 51 | 54 | You can also link to internal anchor names using |
| 52 | 55 | <nowiki>[#anchor-name],</nowiki> providing you have added the necessary |
| @@ -54,12 +57,14 @@ | ||
| 54 | 57 | |
| 55 | 58 | 6. <b>HTML.</b> |
| 56 | 59 | The following standard HTML elements may be used: |
| 57 | 60 | <a> <address> <article> <aside> <b> |
| 58 | 61 | <big> <blockquote> <br> <center> <cite> |
| 59 | - <code> <col> <colgroup> <dd> <dfn> | |
| 62 | + <code> <col> <colgroup> <dd> | |
| 63 | + <del> <dfn> | |
| 60 | 64 | <div> <dl> <dt> <em> <font> <footer> |
| 65 | + <ins> | |
| 61 | 66 | <h1> <h2> <h3> <h4> <h5> <h6> |
| 62 | 67 | <header> <hr> <i> <img> <kbd> <li> |
| 63 | 68 | <nav> <nobr> <nowiki> <ol> <p> <pre> |
| 64 | 69 | <s> <samp> <section> <small> <span> |
| 65 | 70 | <strike> <strong> <sub> <sup> <table> |
| @@ -74,5 +79,8 @@ | ||
| 74 | 79 | 7. <b>Special Markup.</b> |
| 75 | 80 | The <nowiki> tag disables all wiki formatting rules through |
| 76 | 81 | the matching </nowiki> element. The <verbatim> tag works |
| 77 | 82 | like <pre> with the addition that it also disables all wiki |
| 78 | 83 | and HTML markup through the matching </verbatim>. |
| 84 | + | |
| 85 | +<a name="intermap"></a> | |
| 86 | +<h2>Interwiki Tag Map</h2> | |
| 79 | 87 |
| --- 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 | # <verbatim> and <nowiki> |
| 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 | <a> <address> <article> <aside> <b> |
| 58 | <big> <blockquote> <br> <center> <cite> |
| 59 | <code> <col> <colgroup> <dd> <dfn> |
| 60 | <div> <dl> <dt> <em> <font> <footer> |
| 61 | <h1> <h2> <h3> <h4> <h5> <h6> |
| 62 | <header> <hr> <i> <img> <kbd> <li> |
| 63 | <nav> <nobr> <nowiki> <ol> <p> <pre> |
| 64 | <s> <samp> <section> <small> <span> |
| 65 | <strike> <strong> <sub> <sup> <table> |
| @@ -74,5 +79,8 @@ | |
| 74 | 7. <b>Special Markup.</b> |
| 75 | The <nowiki> tag disables all wiki formatting rules through |
| 76 | the matching </nowiki> element. The <verbatim> tag works |
| 77 | like <pre> with the addition that it also disables all wiki |
| 78 | and HTML markup through the matching </verbatim>. |
| 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 | # <verbatim> and <nowiki> |
| 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 | <a> <address> <article> <aside> <b> |
| 61 | <big> <blockquote> <br> <center> <cite> |
| 62 | <code> <col> <colgroup> <dd> |
| 63 | <del> <dfn> |
| 64 | <div> <dl> <dt> <em> <font> <footer> |
| 65 | <ins> |
| 66 | <h1> <h2> <h3> <h4> <h5> <h6> |
| 67 | <header> <hr> <i> <img> <kbd> <li> |
| 68 | <nav> <nobr> <nowiki> <ol> <p> <pre> |
| 69 | <s> <samp> <section> <small> <span> |
| 70 | <strike> <strong> <sub> <sup> <table> |
| @@ -74,5 +79,8 @@ | |
| 79 | 7. <b>Special Markup.</b> |
| 80 | The <nowiki> tag disables all wiki formatting rules through |
| 81 | the matching </nowiki> element. The <verbatim> tag works |
| 82 | like <pre> with the addition that it also disables all wiki |
| 83 | and HTML markup through the matching </verbatim>. |
| 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 @@ | ||
| 188 | 188 | #define MARKUP_CITE 10 |
| 189 | 189 | #define MARKUP_CODE 11 |
| 190 | 190 | #define MARKUP_COL 12 |
| 191 | 191 | #define MARKUP_COLGROUP 13 |
| 192 | 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 | |
| 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 | |
| 240 | 242 | |
| 241 | 243 | /* |
| 242 | 244 | ** The various markup is divided into the following types: |
| 243 | 245 | */ |
| 244 | 246 | #define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */ |
| @@ -290,10 +292,11 @@ | ||
| 290 | 292 | { "col", MARKUP_COL, MUTYPE_SINGLE, |
| 291 | 293 | AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE }, |
| 292 | 294 | { "colgroup", MARKUP_COLGROUP, MUTYPE_BLOCK, |
| 293 | 295 | AMSK_ALIGN|AMSK_CLASS|AMSK_COLSPAN|AMSK_WIDTH|AMSK_STYLE}, |
| 294 | 296 | { "dd", MARKUP_DD, MUTYPE_LI, AMSK_STYLE }, |
| 297 | + { "del", MARKUP_DEL, MUTYPE_FONT, AMSK_STYLE }, | |
| 295 | 298 | { "dfn", MARKUP_DFN, MUTYPE_FONT, AMSK_STYLE }, |
| 296 | 299 | { "div", MARKUP_DIV, MUTYPE_BLOCK, |
| 297 | 300 | AMSK_ID|AMSK_CLASS|AMSK_STYLE }, |
| 298 | 301 | { "dl", MARKUP_DL, MUTYPE_LIST, |
| 299 | 302 | AMSK_COMPACT|AMSK_STYLE }, |
| @@ -325,10 +328,11 @@ | ||
| 325 | 328 | AMSK_STYLE|AMSK_CLASS }, |
| 326 | 329 | { "i", MARKUP_I, MUTYPE_FONT, AMSK_STYLE }, |
| 327 | 330 | { "img", MARKUP_IMG, MUTYPE_SINGLE, |
| 328 | 331 | AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT| |
| 329 | 332 | AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE }, |
| 333 | + { "ins", MARKUP_INS, MUTYPE_FONT, AMSK_STYLE }, | |
| 330 | 334 | { "kbd", MARKUP_KBD, MUTYPE_FONT, AMSK_STYLE }, |
| 331 | 335 | { "li", MARKUP_LI, MUTYPE_LI, |
| 332 | 336 | AMSK_TYPE|AMSK_VALUE|AMSK_STYLE }, |
| 333 | 337 | { "nav", MARKUP_HTML5_NAV, MUTYPE_BLOCK, |
| 334 | 338 | AMSK_ID|AMSK_CLASS|AMSK_STYLE }, |
| @@ -1226,10 +1230,12 @@ | ||
| 1226 | 1230 | ** |
| 1227 | 1231 | ** [WikiPageName] |
| 1228 | 1232 | ** [wiki:WikiPageName] |
| 1229 | 1233 | ** |
| 1230 | 1234 | ** [2010-02-27 07:13] |
| 1235 | +** | |
| 1236 | +** [InterMap:Link] -> Interwiki link | |
| 1231 | 1237 | */ |
| 1232 | 1238 | void wiki_resolve_hyperlink( |
| 1233 | 1239 | Blob *pOut, /* Write the HTML output here */ |
| 1234 | 1240 | int mFlags, /* Rendering option flags */ |
| 1235 | 1241 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| @@ -1240,10 +1246,11 @@ | ||
| 1240 | 1246 | ){ |
| 1241 | 1247 | const char *zTerm = "</a>"; |
| 1242 | 1248 | const char *z; |
| 1243 | 1249 | char *zExtra = 0; |
| 1244 | 1250 | const char *zExtraNS = 0; |
| 1251 | + char *zRemote = 0; | |
| 1245 | 1252 | |
| 1246 | 1253 | if( zTitle ){ |
| 1247 | 1254 | zExtra = mprintf(" title='%h'", zTitle); |
| 1248 | 1255 | zExtraNS = zExtra+1; |
| 1249 | 1256 | } |
| @@ -1299,10 +1306,13 @@ | ||
| 1299 | 1306 | blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget)); |
| 1300 | 1307 | zTerm = "]</a>"; |
| 1301 | 1308 | }else{ |
| 1302 | 1309 | zTerm = ""; |
| 1303 | 1310 | } |
| 1311 | + }else if( (zRemote = interwiki_url(zTarget))!=0 ){ | |
| 1312 | + blob_appendf(pOut, "<a href=\"%z\"%s>", zRemote, zExtra); | |
| 1313 | + zTerm = "</a>"; | |
| 1304 | 1314 | }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){ |
| 1305 | 1315 | /* The link is to a valid wiki page name */ |
| 1306 | 1316 | const char *zOverride = wiki_is_overridden(zTarget); |
| 1307 | 1317 | if( zOverride ){ |
| 1308 | 1318 | blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra); |
| @@ -1510,11 +1520,11 @@ | ||
| 1510 | 1520 | z[j] = 0; |
| 1511 | 1521 | } |
| 1512 | 1522 | } |
| 1513 | 1523 | z[i] = 0; |
| 1514 | 1524 | if( zDisplay==0 ){ |
| 1515 | - zDisplay = zTarget; | |
| 1525 | + zDisplay = zTarget + interwiki_removable_prefix(zTarget); | |
| 1516 | 1526 | }else{ |
| 1517 | 1527 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1518 | 1528 | } |
| 1519 | 1529 | wiki_resolve_hyperlink(p->pOut, p->state, |
| 1520 | 1530 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1521 | 1531 |
| --- 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 @@ | ||
| 1657 | 1657 | int x = db_column_int(&q,3); |
| 1658 | 1658 | const char *zName = db_column_text(&q,4); |
| 1659 | 1659 | if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){ |
| 1660 | 1660 | /* check-in locks expire after maxAge seconds, or when the |
| 1661 | 1661 | ** check-in is no longer a leaf */ |
| 1662 | + db_unprotect(PROTECT_CONFIG); | |
| 1662 | 1663 | db_multi_exec("DELETE FROM config WHERE name=%Q", zName); |
| 1664 | + db_protect_pop(); | |
| 1663 | 1665 | continue; |
| 1664 | 1666 | } |
| 1665 | 1667 | if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){ |
| 1666 | 1668 | const char *zClientId = db_column_text(&q, 2); |
| 1667 | 1669 | const char *zLogin = db_column_text(&q,0); |
| @@ -1672,16 +1674,18 @@ | ||
| 1672 | 1674 | seenFault = 1; |
| 1673 | 1675 | } |
| 1674 | 1676 | } |
| 1675 | 1677 | db_finalize(&q); |
| 1676 | 1678 | if( !seenFault ){ |
| 1679 | + db_unprotect(PROTECT_CONFIG); | |
| 1677 | 1680 | db_multi_exec( |
| 1678 | 1681 | "REPLACE INTO config(name,value,mtime)" |
| 1679 | 1682 | "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())", |
| 1680 | 1683 | blob_str(&xfer.aToken[2]), g.zLogin, |
| 1681 | 1684 | blob_str(&xfer.aToken[3]) |
| 1682 | 1685 | ); |
| 1686 | + db_protect_pop(); | |
| 1683 | 1687 | } |
| 1684 | 1688 | if( db_get_boolean("forbid-delta-manifests",0) ){ |
| 1685 | 1689 | @ pragma avoid-delta-manifests |
| 1686 | 1690 | } |
| 1687 | 1691 | } |
| @@ -1694,16 +1698,18 @@ | ||
| 1694 | 1698 | */ |
| 1695 | 1699 | if( blob_eq(&xfer.aToken[1], "ci-unlock") |
| 1696 | 1700 | && xfer.nToken==3 |
| 1697 | 1701 | && blob_is_hname(&xfer.aToken[2]) |
| 1698 | 1702 | ){ |
| 1703 | + db_unprotect(PROTECT_CONFIG); | |
| 1699 | 1704 | db_multi_exec( |
| 1700 | 1705 | "DELETE FROM config" |
| 1701 | 1706 | " WHERE name GLOB 'ci-lock-*'" |
| 1702 | 1707 | " AND json_extract(value,'$.clientid')=%Q", |
| 1703 | 1708 | blob_str(&xfer.aToken[2]) |
| 1704 | 1709 | ); |
| 1710 | + db_protect_pop(); | |
| 1705 | 1711 | } |
| 1706 | 1712 | |
| 1707 | 1713 | }else |
| 1708 | 1714 | |
| 1709 | 1715 | /* Unknown message |
| 1710 | 1716 | |
| 1711 | 1717 | 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 |
+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 |
| --- 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 @@ | ||
| 28 | 28 | |
| 29 | 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 | 30 | |
| 31 | 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 | 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 | |
| 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 | 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 | |
| 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 | 36 | |
| 37 | 37 | |
| 38 | 38 | RC=$(DMDIR)\bin\rcc |
| 39 | 39 | RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ |
| 40 | 40 | |
| @@ -49,11 +49,11 @@ | ||
| 49 | 49 | |
| 50 | 50 | $(OBJDIR)\fossil.res: $B\win\fossil.rc |
| 51 | 51 | $(RC) $(RCFLAGS) -o$@ $** |
| 52 | 52 | |
| 53 | 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 > $@ | |
| 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 | 55 | +echo fossil >> $@ |
| 56 | 56 | +echo fossil >> $@ |
| 57 | 57 | +echo $(LIBS) >> $@ |
| 58 | 58 | +echo. >> $@ |
| 59 | 59 | +echo fossil >> $@ |
| @@ -475,10 +475,16 @@ | ||
| 475 | 475 | $(OBJDIR)\info$O : info_.c info.h |
| 476 | 476 | $(TCC) -o$@ -c info_.c |
| 477 | 477 | |
| 478 | 478 | info_.c : $(SRCDIR)\info.c |
| 479 | 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 $** > $@ | |
| 480 | 486 | |
| 481 | 487 | $(OBJDIR)\json$O : json_.c json.h |
| 482 | 488 | $(TCC) -o$@ -c json_.c |
| 483 | 489 | |
| 484 | 490 | json_.c : $(SRCDIR)\json.c |
| @@ -981,7 +987,7 @@ | ||
| 981 | 987 | |
| 982 | 988 | zip_.c : $(SRCDIR)\zip.c |
| 983 | 989 | +translate$E $** > $@ |
| 984 | 990 | |
| 985 | 991 | 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 | |
| 987 | 993 | @copy /Y nul: headers |
| 988 | 994 |
| --- 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 |
+15
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -485,10 +485,11 @@ | ||
| 485 | 485 | $(SRCDIR)/http_socket.c \ |
| 486 | 486 | $(SRCDIR)/http_ssl.c \ |
| 487 | 487 | $(SRCDIR)/http_transport.c \ |
| 488 | 488 | $(SRCDIR)/import.c \ |
| 489 | 489 | $(SRCDIR)/info.c \ |
| 490 | + $(SRCDIR)/interwiki.c \ | |
| 490 | 491 | $(SRCDIR)/json.c \ |
| 491 | 492 | $(SRCDIR)/json_artifact.c \ |
| 492 | 493 | $(SRCDIR)/json_branch.c \ |
| 493 | 494 | $(SRCDIR)/json_config.c \ |
| 494 | 495 | $(SRCDIR)/json_diff.c \ |
| @@ -635,15 +636,18 @@ | ||
| 635 | 636 | $(SRCDIR)/default.css \ |
| 636 | 637 | $(SRCDIR)/diff.tcl \ |
| 637 | 638 | $(SRCDIR)/forum.js \ |
| 638 | 639 | $(SRCDIR)/fossil.bootstrap.js \ |
| 639 | 640 | $(SRCDIR)/fossil.confirmer.js \ |
| 641 | + $(SRCDIR)/fossil.copybutton.js \ | |
| 640 | 642 | $(SRCDIR)/fossil.dom.js \ |
| 641 | 643 | $(SRCDIR)/fossil.fetch.js \ |
| 644 | + $(SRCDIR)/fossil.numbered-lines.js \ | |
| 642 | 645 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 643 | 646 | $(SRCDIR)/fossil.page.forumpost.js \ |
| 644 | 647 | $(SRCDIR)/fossil.page.wikiedit.js \ |
| 648 | + $(SRCDIR)/fossil.popupwidget.js \ | |
| 645 | 649 | $(SRCDIR)/fossil.storage.js \ |
| 646 | 650 | $(SRCDIR)/fossil.tabs.js \ |
| 647 | 651 | $(SRCDIR)/graph.js \ |
| 648 | 652 | $(SRCDIR)/href.js \ |
| 649 | 653 | $(SRCDIR)/login.js \ |
| @@ -734,10 +738,11 @@ | ||
| 734 | 738 | $(OBJDIR)/http_socket_.c \ |
| 735 | 739 | $(OBJDIR)/http_ssl_.c \ |
| 736 | 740 | $(OBJDIR)/http_transport_.c \ |
| 737 | 741 | $(OBJDIR)/import_.c \ |
| 738 | 742 | $(OBJDIR)/info_.c \ |
| 743 | + $(OBJDIR)/interwiki_.c \ | |
| 739 | 744 | $(OBJDIR)/json_.c \ |
| 740 | 745 | $(OBJDIR)/json_artifact_.c \ |
| 741 | 746 | $(OBJDIR)/json_branch_.c \ |
| 742 | 747 | $(OBJDIR)/json_config_.c \ |
| 743 | 748 | $(OBJDIR)/json_diff_.c \ |
| @@ -879,10 +884,11 @@ | ||
| 879 | 884 | $(OBJDIR)/http_socket.o \ |
| 880 | 885 | $(OBJDIR)/http_ssl.o \ |
| 881 | 886 | $(OBJDIR)/http_transport.o \ |
| 882 | 887 | $(OBJDIR)/import.o \ |
| 883 | 888 | $(OBJDIR)/info.o \ |
| 889 | + $(OBJDIR)/interwiki.o \ | |
| 884 | 890 | $(OBJDIR)/json.o \ |
| 885 | 891 | $(OBJDIR)/json_artifact.o \ |
| 886 | 892 | $(OBJDIR)/json_branch.o \ |
| 887 | 893 | $(OBJDIR)/json_config.o \ |
| 888 | 894 | $(OBJDIR)/json_diff.o \ |
| @@ -1239,10 +1245,11 @@ | ||
| 1239 | 1245 | $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ |
| 1240 | 1246 | $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| 1241 | 1247 | $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ |
| 1242 | 1248 | $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ |
| 1243 | 1249 | $(OBJDIR)/info_.c:$(OBJDIR)/info.h \ |
| 1250 | + $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \ | |
| 1244 | 1251 | $(OBJDIR)/json_.c:$(OBJDIR)/json.h \ |
| 1245 | 1252 | $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \ |
| 1246 | 1253 | $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \ |
| 1247 | 1254 | $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \ |
| 1248 | 1255 | $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \ |
| @@ -1803,10 +1810,18 @@ | ||
| 1803 | 1810 | |
| 1804 | 1811 | $(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h |
| 1805 | 1812 | $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c |
| 1806 | 1813 | |
| 1807 | 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 | |
| 1808 | 1823 | |
| 1809 | 1824 | $(OBJDIR)/json_.c: $(SRCDIR)/json.c $(TRANSLATE) |
| 1810 | 1825 | $(TRANSLATE) $(SRCDIR)/json.c >$@ |
| 1811 | 1826 | |
| 1812 | 1827 | $(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h |
| 1813 | 1828 |
| --- 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 |
+22
-13
| --- win/Makefile.mingw.mistachkin | ||
| +++ win/Makefile.mingw.mistachkin | ||
| @@ -478,10 +478,11 @@ | ||
| 478 | 478 | $(SRCDIR)/fuzz.c \ |
| 479 | 479 | $(SRCDIR)/glob.c \ |
| 480 | 480 | $(SRCDIR)/graph.c \ |
| 481 | 481 | $(SRCDIR)/gzip.c \ |
| 482 | 482 | $(SRCDIR)/hname.c \ |
| 483 | + $(SRCDIR)/hook.c \ | |
| 483 | 484 | $(SRCDIR)/http.c \ |
| 484 | 485 | $(SRCDIR)/http_socket.c \ |
| 485 | 486 | $(SRCDIR)/http_ssl.c \ |
| 486 | 487 | $(SRCDIR)/http_transport.c \ |
| 487 | 488 | $(SRCDIR)/import.c \ |
| @@ -565,11 +566,10 @@ | ||
| 565 | 566 | $(SRCDIR)/webmail.c \ |
| 566 | 567 | $(SRCDIR)/wiki.c \ |
| 567 | 568 | $(SRCDIR)/wikiformat.c \ |
| 568 | 569 | $(SRCDIR)/winfile.c \ |
| 569 | 570 | $(SRCDIR)/winhttp.c \ |
| 570 | - $(SRCDIR)/wysiwyg.c \ | |
| 571 | 571 | $(SRCDIR)/xfer.c \ |
| 572 | 572 | $(SRCDIR)/xfersetup.c \ |
| 573 | 573 | $(SRCDIR)/zip.c |
| 574 | 574 | |
| 575 | 575 | EXTRA_FILES = \ |
| @@ -635,13 +635,18 @@ | ||
| 635 | 635 | $(SRCDIR)/default.css \ |
| 636 | 636 | $(SRCDIR)/diff.tcl \ |
| 637 | 637 | $(SRCDIR)/forum.js \ |
| 638 | 638 | $(SRCDIR)/fossil.bootstrap.js \ |
| 639 | 639 | $(SRCDIR)/fossil.confirmer.js \ |
| 640 | + $(SRCDIR)/fossil.copybutton.js \ | |
| 640 | 641 | $(SRCDIR)/fossil.dom.js \ |
| 641 | 642 | $(SRCDIR)/fossil.fetch.js \ |
| 643 | + $(SRCDIR)/fossil.numbered-lines.js \ | |
| 642 | 644 | $(SRCDIR)/fossil.page.fileedit.js \ |
| 645 | + $(SRCDIR)/fossil.page.forumpost.js \ | |
| 646 | + $(SRCDIR)/fossil.page.wikiedit.js \ | |
| 647 | + $(SRCDIR)/fossil.popupwidget.js \ | |
| 643 | 648 | $(SRCDIR)/fossil.storage.js \ |
| 644 | 649 | $(SRCDIR)/fossil.tabs.js \ |
| 645 | 650 | $(SRCDIR)/graph.js \ |
| 646 | 651 | $(SRCDIR)/href.js \ |
| 647 | 652 | $(SRCDIR)/login.js \ |
| @@ -667,10 +672,11 @@ | ||
| 667 | 672 | $(SRCDIR)/sounds/d.wav \ |
| 668 | 673 | $(SRCDIR)/sounds/e.wav \ |
| 669 | 674 | $(SRCDIR)/sounds/f.wav \ |
| 670 | 675 | $(SRCDIR)/style.admin_log.css \ |
| 671 | 676 | $(SRCDIR)/style.fileedit.css \ |
| 677 | + $(SRCDIR)/style.wikiedit.css \ | |
| 672 | 678 | $(SRCDIR)/tree.js \ |
| 673 | 679 | $(SRCDIR)/useredit.js \ |
| 674 | 680 | $(SRCDIR)/wiki.wiki |
| 675 | 681 | |
| 676 | 682 | TRANS_SRC = \ |
| @@ -724,10 +730,11 @@ | ||
| 724 | 730 | $(OBJDIR)/fuzz_.c \ |
| 725 | 731 | $(OBJDIR)/glob_.c \ |
| 726 | 732 | $(OBJDIR)/graph_.c \ |
| 727 | 733 | $(OBJDIR)/gzip_.c \ |
| 728 | 734 | $(OBJDIR)/hname_.c \ |
| 735 | + $(OBJDIR)/hook_.c \ | |
| 729 | 736 | $(OBJDIR)/http_.c \ |
| 730 | 737 | $(OBJDIR)/http_socket_.c \ |
| 731 | 738 | $(OBJDIR)/http_ssl_.c \ |
| 732 | 739 | $(OBJDIR)/http_transport_.c \ |
| 733 | 740 | $(OBJDIR)/import_.c \ |
| @@ -811,11 +818,10 @@ | ||
| 811 | 818 | $(OBJDIR)/webmail_.c \ |
| 812 | 819 | $(OBJDIR)/wiki_.c \ |
| 813 | 820 | $(OBJDIR)/wikiformat_.c \ |
| 814 | 821 | $(OBJDIR)/winfile_.c \ |
| 815 | 822 | $(OBJDIR)/winhttp_.c \ |
| 816 | - $(OBJDIR)/wysiwyg_.c \ | |
| 817 | 823 | $(OBJDIR)/xfer_.c \ |
| 818 | 824 | $(OBJDIR)/xfersetup_.c \ |
| 819 | 825 | $(OBJDIR)/zip_.c |
| 820 | 826 | |
| 821 | 827 | OBJ = \ |
| @@ -869,10 +875,11 @@ | ||
| 869 | 875 | $(OBJDIR)/fuzz.o \ |
| 870 | 876 | $(OBJDIR)/glob.o \ |
| 871 | 877 | $(OBJDIR)/graph.o \ |
| 872 | 878 | $(OBJDIR)/gzip.o \ |
| 873 | 879 | $(OBJDIR)/hname.o \ |
| 880 | + $(OBJDIR)/hook.o \ | |
| 874 | 881 | $(OBJDIR)/http.o \ |
| 875 | 882 | $(OBJDIR)/http_socket.o \ |
| 876 | 883 | $(OBJDIR)/http_ssl.o \ |
| 877 | 884 | $(OBJDIR)/http_transport.o \ |
| 878 | 885 | $(OBJDIR)/import.o \ |
| @@ -956,11 +963,10 @@ | ||
| 956 | 963 | $(OBJDIR)/webmail.o \ |
| 957 | 964 | $(OBJDIR)/wiki.o \ |
| 958 | 965 | $(OBJDIR)/wikiformat.o \ |
| 959 | 966 | $(OBJDIR)/winfile.o \ |
| 960 | 967 | $(OBJDIR)/winhttp.o \ |
| 961 | - $(OBJDIR)/wysiwyg.o \ | |
| 962 | 968 | $(OBJDIR)/xfer.o \ |
| 963 | 969 | $(OBJDIR)/xfersetup.o \ |
| 964 | 970 | $(OBJDIR)/zip.o |
| 965 | 971 | |
| 966 | 972 | APPNAME = fossil.exe |
| @@ -1057,13 +1063,16 @@ | ||
| 1057 | 1063 | # build is done from, i.e. the checkout belongs to. Do not sync/push |
| 1058 | 1064 | # the repository after running the tests. |
| 1059 | 1065 | test: $(OBJDIR) $(APPNAME) |
| 1060 | 1066 | $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) |
| 1061 | 1067 | |
| 1062 | -$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) | |
| 1068 | +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(OBJDIR)/phony.h | |
| 1063 | 1069 | $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ |
| 1064 | 1070 | |
| 1071 | +$(OBJDIR)/phony.h: | |
| 1072 | + # Force rebuild of VERSION.h every time "make" is run | |
| 1073 | + | |
| 1065 | 1074 | # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set |
| 1066 | 1075 | # to 1. If it is set to 1, then there is no need to build or link |
| 1067 | 1076 | # the sqlite3.o object. Instead, the system SQLite will be linked |
| 1068 | 1077 | # using -lsqlite3. |
| 1069 | 1078 | SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o |
| @@ -1226,10 +1235,11 @@ | ||
| 1226 | 1235 | $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \ |
| 1227 | 1236 | $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ |
| 1228 | 1237 | $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ |
| 1229 | 1238 | $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ |
| 1230 | 1239 | $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ |
| 1240 | + $(OBJDIR)/hook_.c:$(OBJDIR)/hook.h \ | |
| 1231 | 1241 | $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ |
| 1232 | 1242 | $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ |
| 1233 | 1243 | $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| 1234 | 1244 | $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ |
| 1235 | 1245 | $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ |
| @@ -1313,11 +1323,10 @@ | ||
| 1313 | 1323 | $(OBJDIR)/webmail_.c:$(OBJDIR)/webmail.h \ |
| 1314 | 1324 | $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ |
| 1315 | 1325 | $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ |
| 1316 | 1326 | $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ |
| 1317 | 1327 | $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ |
| 1318 | - $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h \ | |
| 1319 | 1328 | $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \ |
| 1320 | 1329 | $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \ |
| 1321 | 1330 | $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \ |
| 1322 | 1331 | $(SRCDIR)/sqlite3.h \ |
| 1323 | 1332 | $(SRCDIR)/th.h \ |
| @@ -1741,10 +1750,18 @@ | ||
| 1741 | 1750 | |
| 1742 | 1751 | $(OBJDIR)/hname.o: $(OBJDIR)/hname_.c $(OBJDIR)/hname.h $(SRCDIR)/config.h |
| 1743 | 1752 | $(XTCC) -o $(OBJDIR)/hname.o -c $(OBJDIR)/hname_.c |
| 1744 | 1753 | |
| 1745 | 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 | |
| 1746 | 1763 | |
| 1747 | 1764 | $(OBJDIR)/http_.c: $(SRCDIR)/http.c $(TRANSLATE) |
| 1748 | 1765 | $(TRANSLATE) $(SRCDIR)/http.c >$@ |
| 1749 | 1766 | |
| 1750 | 1767 | $(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h |
| @@ -2438,18 +2455,10 @@ | ||
| 2438 | 2455 | $(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h |
| 2439 | 2456 | $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c |
| 2440 | 2457 | |
| 2441 | 2458 | $(OBJDIR)/winhttp.h: $(OBJDIR)/headers |
| 2442 | 2459 | |
| 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 | 2460 | $(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(TRANSLATE) |
| 2452 | 2461 | $(TRANSLATE) $(SRCDIR)/xfer.c >$@ |
| 2453 | 2462 | |
| 2454 | 2463 | $(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h |
| 2455 | 2464 | $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c |
| 2456 | 2465 |
| --- 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 |
+16
| --- win/Makefile.msc | ||
| +++ win/Makefile.msc | ||
| @@ -407,10 +407,11 @@ | ||
| 407 | 407 | "$(OX)\http_socket_.c" \ |
| 408 | 408 | "$(OX)\http_ssl_.c" \ |
| 409 | 409 | "$(OX)\http_transport_.c" \ |
| 410 | 410 | "$(OX)\import_.c" \ |
| 411 | 411 | "$(OX)\info_.c" \ |
| 412 | + "$(OX)\interwiki_.c" \ | |
| 412 | 413 | "$(OX)\json_.c" \ |
| 413 | 414 | "$(OX)\json_artifact_.c" \ |
| 414 | 415 | "$(OX)\json_branch_.c" \ |
| 415 | 416 | "$(OX)\json_config_.c" \ |
| 416 | 417 | "$(OX)\json_diff_.c" \ |
| @@ -556,15 +557,18 @@ | ||
| 556 | 557 | "$(SRCDIR)\default.css" \ |
| 557 | 558 | "$(SRCDIR)\diff.tcl" \ |
| 558 | 559 | "$(SRCDIR)\forum.js" \ |
| 559 | 560 | "$(SRCDIR)\fossil.bootstrap.js" \ |
| 560 | 561 | "$(SRCDIR)\fossil.confirmer.js" \ |
| 562 | + "$(SRCDIR)\fossil.copybutton.js" \ | |
| 561 | 563 | "$(SRCDIR)\fossil.dom.js" \ |
| 562 | 564 | "$(SRCDIR)\fossil.fetch.js" \ |
| 565 | + "$(SRCDIR)\fossil.numbered-lines.js" \ | |
| 563 | 566 | "$(SRCDIR)\fossil.page.fileedit.js" \ |
| 564 | 567 | "$(SRCDIR)\fossil.page.forumpost.js" \ |
| 565 | 568 | "$(SRCDIR)\fossil.page.wikiedit.js" \ |
| 569 | + "$(SRCDIR)\fossil.popupwidget.js" \ | |
| 566 | 570 | "$(SRCDIR)\fossil.storage.js" \ |
| 567 | 571 | "$(SRCDIR)\fossil.tabs.js" \ |
| 568 | 572 | "$(SRCDIR)\graph.js" \ |
| 569 | 573 | "$(SRCDIR)\href.js" \ |
| 570 | 574 | "$(SRCDIR)\login.js" \ |
| @@ -655,10 +659,11 @@ | ||
| 655 | 659 | "$(OX)\http_socket$O" \ |
| 656 | 660 | "$(OX)\http_ssl$O" \ |
| 657 | 661 | "$(OX)\http_transport$O" \ |
| 658 | 662 | "$(OX)\import$O" \ |
| 659 | 663 | "$(OX)\info$O" \ |
| 664 | + "$(OX)\interwiki$O" \ | |
| 660 | 665 | "$(OX)\json$O" \ |
| 661 | 666 | "$(OX)\json_artifact$O" \ |
| 662 | 667 | "$(OX)\json_branch$O" \ |
| 663 | 668 | "$(OX)\json_config$O" \ |
| 664 | 669 | "$(OX)\json_diff$O" \ |
| @@ -881,10 +886,11 @@ | ||
| 881 | 886 | echo "$(OX)\http_socket.obj" >> $@ |
| 882 | 887 | echo "$(OX)\http_ssl.obj" >> $@ |
| 883 | 888 | echo "$(OX)\http_transport.obj" >> $@ |
| 884 | 889 | echo "$(OX)\import.obj" >> $@ |
| 885 | 890 | echo "$(OX)\info.obj" >> $@ |
| 891 | + echo "$(OX)\interwiki.obj" >> $@ | |
| 886 | 892 | echo "$(OX)\json.obj" >> $@ |
| 887 | 893 | echo "$(OX)\json_artifact.obj" >> $@ |
| 888 | 894 | echo "$(OX)\json_branch.obj" >> $@ |
| 889 | 895 | echo "$(OX)\json_config.obj" >> $@ |
| 890 | 896 | echo "$(OX)\json_diff.obj" >> $@ |
| @@ -1150,15 +1156,18 @@ | ||
| 1150 | 1156 | echo "$(SRCDIR)\default.css" >> $@ |
| 1151 | 1157 | echo "$(SRCDIR)\diff.tcl" >> $@ |
| 1152 | 1158 | echo "$(SRCDIR)\forum.js" >> $@ |
| 1153 | 1159 | echo "$(SRCDIR)\fossil.bootstrap.js" >> $@ |
| 1154 | 1160 | echo "$(SRCDIR)\fossil.confirmer.js" >> $@ |
| 1161 | + echo "$(SRCDIR)\fossil.copybutton.js" >> $@ | |
| 1155 | 1162 | echo "$(SRCDIR)\fossil.dom.js" >> $@ |
| 1156 | 1163 | echo "$(SRCDIR)\fossil.fetch.js" >> $@ |
| 1164 | + echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@ | |
| 1157 | 1165 | echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@ |
| 1158 | 1166 | echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@ |
| 1159 | 1167 | echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@ |
| 1168 | + echo "$(SRCDIR)\fossil.popupwidget.js" >> $@ | |
| 1160 | 1169 | echo "$(SRCDIR)\fossil.storage.js" >> $@ |
| 1161 | 1170 | echo "$(SRCDIR)\fossil.tabs.js" >> $@ |
| 1162 | 1171 | echo "$(SRCDIR)\graph.js" >> $@ |
| 1163 | 1172 | echo "$(SRCDIR)\href.js" >> $@ |
| 1164 | 1173 | echo "$(SRCDIR)\login.js" >> $@ |
| @@ -1542,10 +1551,16 @@ | ||
| 1542 | 1551 | "$(OX)\info$O" : "$(OX)\info_.c" "$(OX)\info.h" |
| 1543 | 1552 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\info_.c" |
| 1544 | 1553 | |
| 1545 | 1554 | "$(OX)\info_.c" : "$(SRCDIR)\info.c" |
| 1546 | 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" $** > $@ | |
| 1547 | 1562 | |
| 1548 | 1563 | "$(OX)\json$O" : "$(OX)\json_.c" "$(OX)\json.h" |
| 1549 | 1564 | $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_.c" |
| 1550 | 1565 | |
| 1551 | 1566 | "$(OX)\json_.c" : "$(SRCDIR)\json.c" |
| @@ -2110,10 +2125,11 @@ | ||
| 2110 | 2125 | "$(OX)\http_socket_.c":"$(OX)\http_socket.h" \ |
| 2111 | 2126 | "$(OX)\http_ssl_.c":"$(OX)\http_ssl.h" \ |
| 2112 | 2127 | "$(OX)\http_transport_.c":"$(OX)\http_transport.h" \ |
| 2113 | 2128 | "$(OX)\import_.c":"$(OX)\import.h" \ |
| 2114 | 2129 | "$(OX)\info_.c":"$(OX)\info.h" \ |
| 2130 | + "$(OX)\interwiki_.c":"$(OX)\interwiki.h" \ | |
| 2115 | 2131 | "$(OX)\json_.c":"$(OX)\json.h" \ |
| 2116 | 2132 | "$(OX)\json_artifact_.c":"$(OX)\json_artifact.h" \ |
| 2117 | 2133 | "$(OX)\json_branch_.c":"$(OX)\json_branch.h" \ |
| 2118 | 2134 | "$(OX)\json_config_.c":"$(OX)\json_config.h" \ |
| 2119 | 2135 | "$(OX)\json_diff_.c":"$(OX)\json_diff.h" \ |
| 2120 | 2136 |
| --- 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 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 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. | |
| 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 <del> and <ins> markup in wiki. | |
| 8 | + * Improvements to the Forum threading display. | |
| 13 | 9 | |
| 14 | 10 | <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> | |
| 16 | 12 | |
| 13 | + * (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz. | |
| 17 | 14 | * Security fix in the "[/help?cmd=git|fossil git export]" command. |
| 18 | 15 | The same fix is also backported to version 2.10.1 and 2.11.1. |
| 19 | 16 | New "safety-net" features were added to prevent similar problems |
| 20 | 17 | in the future. |
| 21 | 18 | * Enhancements to the graph display for cases when there are |
| @@ -48,10 +45,11 @@ | ||
| 48 | 45 | * Added the experimental "[/help?cmd=hook|fossil hook]" command for |
| 49 | 46 | managing "hook scripts" that run before checkin or after a push. |
| 50 | 47 | * Enhance the [/help?cmd=revert|fossil revert] command so that it |
| 51 | 48 | is able to revert all files beneath a directory. |
| 52 | 49 | * Add the [/help?cmd=bisect|fossil bisect skip] command. |
| 50 | + * Add the [/help?cmd=backup|fossil backup] command. | |
| 53 | 51 | * Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked |
| 54 | 52 | check-ins in between the innermost "good" and "bad" check-ins. |
| 55 | 53 | * Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]", |
| 56 | 54 | "[/help?cmd=rm|fossil rm]", and |
| 57 | 55 | "[/help?cmd=addremove|fossil addremove]" commands. |
| @@ -90,10 +88,12 @@ | ||
| 90 | 88 | * Countless documentation enhancements. |
| 91 | 89 | |
| 92 | 90 | <a name='v2_11'></a> |
| 93 | 91 | <h2>Changes for Version 2.11 (2020-05-25)</h2> |
| 94 | 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 | 95 | * Support [/md_rules|Markdown] in the default ticket configuration. |
| 96 | 96 | * Timestamp strings in [./checkin_names.wiki|object names] |
| 97 | 97 | can now omit punctation. So, for example, "202004181942" and |
| 98 | 98 | "2020-04-18 19:42" mean the same thing. |
| 99 | 99 | * Enhance backlink processing so that it works with Markdown-formatted |
| @@ -179,10 +179,12 @@ | ||
| 179 | 179 | * Many minor enhancements to existing features. |
| 180 | 180 | |
| 181 | 181 | <a name='v2_10'></a> |
| 182 | 182 | <h2>Changes for Version 2.10 (2019-10-04)</h2> |
| 183 | 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. | |
| 184 | 186 | * Added support for [./serverext.wiki|CGI-based Server Extensions]. |
| 185 | 187 | * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to |
| 186 | 188 | add style to repository list pages. |
| 187 | 189 | * Enhance the hierarchical display of Forum threads to do less |
| 188 | 190 | indentation and to provide links back to the previous message |
| 189 | 191 |
| --- 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 <del> and <ins> 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 @@ | ||
| 37 | 37 | [bld]: https://www.fossil-scm.org/fossil/doc/trunk/www/build.wiki |
| 38 | 38 | [cj]: https://en.wikipedia.org/wiki/Chroot |
| 39 | 39 | [fls]: ./loadmgmt.md |
| 40 | 40 | [mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb |
| 41 | 41 | [srv]: ./server/ |
| 42 | -[obsd]: ./server/openbsd/httpd.md#chroot | |
| 42 | +[obsd]: ./server/openbsd/fastcgi.md#chroot | |
| 43 | 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/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 |
+12
-9
| --- www/customskin.md | ||
| +++ www/customskin.md | ||
| @@ -68,12 +68,13 @@ | ||
| 68 | 68 | <tr><td style='background-color:lightblue;text-align:center;'>Content Footer</td></tr> |
| 69 | 69 | <tr><td style='background-color:lightgreen;text-align:center;'> |
| 70 | 70 | Fossil-Generated HTML Footer</td></tr> |
| 71 | 71 | </tbody></table></blockquote> |
| 72 | 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. | |
| 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. | |
| 75 | 76 | |
| 76 | 77 | Fossil *usually* (but not always - [see below](#override)) |
| 77 | 78 | generates the initial HTML Header section of a page. The |
| 78 | 79 | generated HTML Header will look something like this: |
| 79 | 80 | |
| @@ -85,31 +86,33 @@ | ||
| 85 | 86 | <title>....</title> |
| 86 | 87 | <link rel="stylesheet" href="..." type="text/css" /> |
| 87 | 88 | </head> |
| 88 | 89 | <body> |
| 89 | 90 | |
| 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: | |
| 93 | 95 | |
| 94 | 96 | <div class="header"> |
| 95 | 97 | ... top banner and menu bar ... |
| 96 | 98 | </div> |
| 97 | 99 | |
| 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. | |
| 101 | 104 | |
| 102 | 105 | The Fossil-generated Content section immediately follows the Content Header. |
| 103 | 106 | The Content section will looks like this: |
| 104 | 107 | |
| 105 | 108 | <div class="content"> |
| 106 | 109 | ... Fossil-generated content here ... |
| 107 | 110 | </div> |
| 108 | 111 | |
| 109 | 112 | After the Content is the custom Content Footer section which should |
| 110 | -following this template: | |
| 113 | +follow this template: | |
| 111 | 114 | |
| 112 | 115 | <div class="footer"> |
| 113 | 116 | ... skin-specific stuff here ... |
| 114 | 117 | </div> |
| 115 | 118 | <script nonce="$nonce"> |
| 116 | 119 |
| --- 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 |
+1
-1
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -563,11 +563,11 @@ | ||
| 563 | 563 | subject for that thread. The argument to the <b>H</b> card is a string |
| 564 | 564 | in the same format as a comment string in a <b>C</b> card. |
| 565 | 565 | All follow-up posts have an <b>I</b> card that |
| 566 | 566 | indicates which prior post in the same thread the current forum |
| 567 | 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 | |
| 568 | +the entire thread. The argument to <b>G</b> and <b>I</b> cards is the | |
| 569 | 569 | artifact hash for the prior forum post to which the card refers. |
| 570 | 570 | |
| 571 | 571 | In theory, it is sufficient for follow-up posts to have only an |
| 572 | 572 | <b>I</b> card, since the <b>G</b> card value could be computed by following a |
| 573 | 573 | chain of <b>I</b> cards. However, the <b>G</b> card is required in order to |
| 574 | 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 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 @@ | ||
| 84 | 84 | the repository are consistent prior to each commit. |
| 85 | 85 | |
| 86 | 86 | 8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | 87 | |
| 88 | 88 | <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> | |
| 90 | 90 | |
| 91 | 91 | * [/uv/download.html|Download] |
| 92 | - * [./changes.wiki#v2_12_1|Change Summary] | |
| 92 | + * [./changes.wiki#v2_12|Change Summary] | |
| 93 | 93 | |
| 94 | 94 | <hr> |
| 95 | 95 | <h3>Quick Start</h3> |
| 96 | 96 | |
| 97 | 97 | 1. [/uv/download.html|Download] or install using a package manager or |
| 98 | 98 | |
| 99 | 99 | 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 |
+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> → the PageName begins with the "/" character | |
| 43 | + or is an empty string. | |
| 44 | + | |
| 45 | + 2. <b>Hash Links</b> → the PageName is a hexadecimal number with | |
| 46 | + at least four digits. | |
| 47 | + | |
| 48 | + n 3. <b>Wiki Links</b> → 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> → the PageName begins with the "/" character | |
| 80 | + or is an empty string. | |
| 81 | + | |
| 82 | + 2. <b>Hash Links</b> → the PageName is a hexadecimal number with | |
| 83 | + at least four digits. | |
| 84 | + | |
| 85 | + n 3. <b>Wiki Links</b> → 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> → the PageName begins with the "/" character |
| 43 | or is an empty string. |
| 44 | |
| 45 | 2. <b>Hash Links</b> → the PageName is a hexadecimal number with |
| 46 | at least four digits. |
| 47 | |
| 48 | n 3. <b>Wiki Links</b> → 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> → the PageName begins with the "/" character |
| 80 | or is an empty string. |
| 81 | |
| 82 | 2. <b>Hash Links</b> → the PageName is a hexadecimal number with |
| 83 | at least four digits. |
| 84 | |
| 85 | n 3. <b>Wiki Links</b> → 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 @@ | ||
| 1 | 1 | # Use of JavaScript in Fossil |
| 2 | 2 | |
| 3 | -## Philosophy | |
| 3 | +## Philosophy & Policy | |
| 4 | 4 | |
| 5 | 5 | The Fossil development project’s policy is to use JavaScript where it |
| 6 | 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. | |
| 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 | 11 | |
| 12 | 12 | This is not to say that Fossil’s fall-backs for such cases are always as |
| 13 | 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 | |
| 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 | 17 | norm enough that we have many no-JS purists among us, so the no-JS case |
| 18 | 18 | doesn’t get as much attention as some might want. We do [accept code |
| 19 | 19 | contributions][cg], and we are philosophically in favor of graceful |
| 20 | 20 | fall-backs, so you are welcome to appoint yourself the position of no-JS |
| 21 | 21 | czar for the Fossil project! |
| 22 | 22 | |
| 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* | |
| 24 | 24 | be evil. It is an active technology, but the actions that matter here |
| 25 | 25 | are those of writing the code and checking it into the Fossil project |
| 26 | 26 | repository. None of the JavaScript code in Fossil is evil, a fact we |
| 27 | 27 | enforce by being careful about who we give check-in rights on the |
| 28 | 28 | repository to and by policing what code does get contributed. The Fossil |
| @@ -30,98 +30,264 @@ | ||
| 30 | 30 | |
| 31 | 31 | We think it’s better to ask not whether Fossil requires JavaScript but |
| 32 | 32 | whether Fossil uses JavaScript *well*, so that [you can decide](#block) |
| 33 | 33 | to block or allow Fossil’s use of JavaScript. |
| 34 | 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 | + | |
| 35 | 46 | [cg]: ./contribute.wiki |
| 36 | 47 | |
| 37 | 48 | |
| 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 | +---- | |
| 118 | 283 | |
| 119 | 284 | ## <a id="uses"></a>Places Where Fossil’s Web UI Uses JavaScript |
| 120 | 285 | |
| 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. | |
| 123 | 289 | |
| 124 | 290 | |
| 125 | 291 | ### <a id="timeline"></a>Timeline Graph |
| 126 | 292 | |
| 127 | 293 | Fossil’s [web timeline][wt] uses JavaScript to render the graph |
| @@ -140,45 +306,144 @@ | ||
| 140 | 306 | command, by clicking around within the web UI, etc. |
| 141 | 307 | |
| 142 | 308 | _Potential Workaround:_ The timeline could be enhanced with `<noscript>` |
| 143 | 309 | tags that replace the graph with a column of checkboxes that control |
| 144 | 310 | 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. | |
| 146 | 312 | For example, you could click two of those checkboxes and then a button |
| 147 | 313 | labeled “Diff Selected” to replicate the current “click two nodes to |
| 148 | 314 | diff them” feature. |
| 149 | 315 | |
| 150 | 316 | [wt]: https://fossil-scm.org/fossil/timeline |
| 151 | 317 | |
| 152 | 318 | |
| 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 | |
| 168 | 421 | |
| 169 | 422 | |
| 170 | 423 | ### <a id="ln"></a>Line Numbering |
| 171 | 424 | |
| 172 | 425 | 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. | |
| 175 | 441 | |
| 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. | |
| 180 | 445 | |
| 181 | 446 | [mainc]: https://fossil-scm.org/fossil/artifact?ln&name=87d67e745 |
| 182 | 447 | |
| 183 | 448 | |
| 184 | 449 | ### <a id="sxsdiff"></a>Side-by-Side Diff Mode |
| @@ -224,12 +489,12 @@ | ||
| 224 | 489 | similar, hovering over that check-in shows a tooltip with details about |
| 225 | 490 | the type of artifact the hash refers to and allows you to click to copy |
| 226 | 491 | the hash to the clipboard. |
| 227 | 492 | |
| 228 | 493 | _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. | |
| 231 | 496 | |
| 232 | 497 | |
| 233 | 498 | ### <a id="bots"></a>Anti-Bot Defenses |
| 234 | 499 | |
| 235 | 500 | Fossil has [anti-bot defenses][abd], and it has some JavaScript code |
| @@ -254,26 +519,75 @@ | ||
| 254 | 519 | |
| 255 | 520 | _Graceful Fallback:_ Clicking the hamburger menu button with JavaScript |
| 256 | 521 | disabled will take you to the `/sitemap` page instead of showing a |
| 257 | 522 | simplified version of that page’s content in a drop-down. |
| 258 | 523 | |
| 259 | -_Workaround:_ You can remove this button by [editing the skin][cs] | |
| 524 | +_Workaround:_ You can remove this button by [editing the skin][cskin] | |
| 260 | 525 | header. |
| 261 | 526 | |
| 262 | 527 | |
| 263 | 528 | ### <a id="clock"></a>Clock |
| 264 | 529 | |
| 265 | 530 | Some stock Fossil skins include JavaScript-based features such as the |
| 266 | 531 | 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. | |
| 280 | 594 |
| --- 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 |
+1
| --- www/mkindex.tcl | ||
| +++ www/mkindex.tcl | ||
| @@ -61,10 +61,11 @@ | ||
| 61 | 61 | /help {Lists of Commands and Webpages} |
| 62 | 62 | hints.wiki {Fossil Tips And Usage Hints} |
| 63 | 63 | history.md {The Purpose And History Of Fossil} |
| 64 | 64 | index.wiki {Home Page} |
| 65 | 65 | inout.wiki {Import And Export To And From Git} |
| 66 | + interwiki.md {Interwiki Links} | |
| 66 | 67 | image-format-vs-repo-size.md {Image Format vs Fossil Repo Size} |
| 67 | 68 | javascript.md {Use of JavaScript in Fossil} |
| 68 | 69 | makefile.wiki {The Fossil Build Process} |
| 69 | 70 | mirrorlimitations.md {Limitations On Git Mirrors} |
| 70 | 71 | mirrortogithub.md {How To Mirror A Fossil Repository On GitHub} |
| 71 | 72 |
| --- 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 @@ | ||
| 184 | 184 | <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li> |
| 185 | 185 | <li><a href="build.wiki">Installing Fossil — Compiling and</a></li> |
| 186 | 186 | <li><a href="fossil-from-msvc.wiki"><b>Integrating Fossil in the Microsoft Express 2010 IDE</b></a></li> |
| 187 | 187 | <li><a href="selfcheck.wiki">Integrity Self Checks — Fossil Repository</a></li> |
| 188 | 188 | <li><a href="webui.wiki">Interface — The Fossil Web</a></li> |
| 189 | +<li><a href="interwiki.md"><b>Interwiki Links</b></a></li> | |
| 189 | 190 | <li><a href="javascript.md">JavaScript in Fossil — Use of</a></li> |
| 190 | 191 | <li><a href="th1.md">Language — The TH1 Scripting</a></li> |
| 191 | 192 | <li><a href="copyright-release.html">License Agreement — Contributor</a></li> |
| 192 | 193 | <li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li> |
| 194 | +<li><a href="interwiki.md">Links — Interwiki</a></li> | |
| 193 | 195 | <li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li> |
| 194 | 196 | <li><a href="password.wiki">Management And Authentication — Password</a></li> |
| 195 | 197 | <li><a href="../../../sitemap">Map — Site</a></li> |
| 196 | 198 | <li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li> |
| 197 | 199 | <li><a href="backoffice.md">mechanism of Fossil — The Backoffice</a></li> |
| 198 | 200 |
| --- 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 — 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 — Fossil Repository</a></li> |
| 188 | <li><a href="webui.wiki">Interface — The Fossil Web</a></li> |
| 189 | <li><a href="javascript.md">JavaScript in Fossil — Use of</a></li> |
| 190 | <li><a href="th1.md">Language — The TH1 Scripting</a></li> |
| 191 | <li><a href="copyright-release.html">License Agreement — 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 — Password</a></li> |
| 195 | <li><a href="../../../sitemap">Map — 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 — 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 — 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 — Fossil Repository</a></li> |
| 188 | <li><a href="webui.wiki">Interface — 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 — Use of</a></li> |
| 191 | <li><a href="th1.md">Language — The TH1 Scripting</a></li> |
| 192 | <li><a href="copyright-release.html">License Agreement — Contributor</a></li> |
| 193 | <li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li> |
| 194 | <li><a href="interwiki.md">Links — 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 — Password</a></li> |
| 197 | <li><a href="../../../sitemap">Map — 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 — The Backoffice</a></li> |
| 200 |
+7
-18
| --- www/quotes.wiki | ||
| +++ www/quotes.wiki | ||
| @@ -19,11 +19,13 @@ | ||
| 19 | 19 | represented as n-dimensional membranes, mapping the spatial loci of |
| 20 | 20 | successive commits onto the projected manifold of each cloned |
| 21 | 21 | repository.</nowiki> |
| 22 | 22 | |
| 23 | 23 | <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> | |
| 25 | 27 | </blockquote> |
| 26 | 28 | |
| 27 | 29 | <li>Git is not a Prius. Git is a Model T. |
| 28 | 30 | Its plumbing and wiring sticks out all over the place. |
| 29 | 31 | You have to be a mechanic to operate it successfully or you'll be |
| @@ -50,16 +52,10 @@ | ||
| 50 | 52 | <p>* dkf really wishes he could use fossil instead</p> |
| 51 | 53 | <blockquote> |
| 52 | 54 | <i>by Donal K. Fellow (dkf) on the Tcl/Tk chatroom, 2013-04-09.</i> |
| 53 | 55 | </blockquote> |
| 54 | 56 | |
| 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 | 57 | <li>[G]it is <i>designed</i> to forget things. |
| 62 | 58 | |
| 63 | 59 | <blockquote> |
| 64 | 60 | <i>[http://www.cs.cmu.edu/~davide/howto/git_lose.html] |
| 65 | 61 | </blockquote> |
| @@ -66,11 +62,11 @@ | ||
| 66 | 62 | |
| 67 | 63 | <li>[I]n nearly 31 years of using a computer i have, in total, lost more data to git |
| 68 | 64 | (while following the instructions!!!) than any other single piece of software. |
| 69 | 65 | |
| 70 | 66 | <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] | |
| 72 | 68 | 2014-09-01.</i> |
| 73 | 69 | </blockquote> |
| 74 | 70 | |
| 75 | 71 | <li>If programmers _really_ wanted to help scientists, they'd build a version control |
| 76 | 72 | system that was more usable than Git. |
| @@ -91,11 +87,11 @@ | ||
| 91 | 87 | <li value=11> |
| 92 | 88 | Fossil mesmerizes me with simplicity especially after I struggled to |
| 93 | 89 | get a bug-tracking system to work with mercurial. |
| 94 | 90 | |
| 95 | 91 | <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> | |
| 97 | 93 | </blockquote> |
| 98 | 94 | |
| 99 | 95 | <li>Fossil is the best thing to happen |
| 100 | 96 | to my development workflow this year, as I am pretty sure that using |
| 101 | 97 | Git has resulted in the premature death of too many of my brain cells. |
| @@ -104,17 +100,10 @@ | ||
| 104 | 100 | |
| 105 | 101 | <blockquote> |
| 106 | 102 | <i>Joe Prostko at [http://www.mail-archive.com/[email protected]/msg16716.html] |
| 107 | 103 | </blockquote> |
| 108 | 104 | |
| 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 | 105 | <li>This is my favourite VCS. I can carry it on a USB. And it's a complete system, with it's own |
| 117 | 106 | server, ticketing system, Wiki pages, and a very, very helpful timeline visualization. And |
| 118 | 107 | the entire program in a single file! |
| 119 | 108 | |
| 120 | 109 | <blockquote> |
| @@ -126,11 +115,11 @@ | ||
| 126 | 115 | |
| 127 | 116 | |
| 128 | 117 | <h2>On Git Versus Fossil</h2> |
| 129 | 118 | |
| 130 | 119 | <ol> |
| 131 | -<li value=15> | |
| 120 | +<li value=14> | |
| 132 | 121 | After prolonged exposure to fossil, i tend to get the jitters when I work with git... |
| 133 | 122 | |
| 134 | 123 | <blockquote> |
| 135 | 124 | <i>sriku - at [https://news.ycombinator.com/item?id=16104427]</i> |
| 136 | 125 | </blockquote> |
| @@ -150,11 +139,11 @@ | ||
| 150 | 139 | sometimes very restrictive firewalls, OSX/Win/Linux). We are happy with it |
| 151 | 140 | and teaching a Msc/Phd student (read complete novice) fossil has just |
| 152 | 141 | been a smoother ride than Git was. |
| 153 | 142 | |
| 154 | 143 | <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> | |
| 156 | 145 | </blockquote> |
| 157 | 146 | |
| 158 | 147 | <li>In the fossil community - and hence in fossil itself - development history |
| 159 | 148 | is pretty much sacrosanct. The very name "fossil" was to chosen to |
| 160 | 149 | reflect the unchanging nature of things in that history. |
| 161 | 150 |
| --- 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>[G]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>[I]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>[G]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>[I]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 |
+1
-1
| --- www/server/any/scgi.md | ||
| +++ www/server/any/scgi.md | ||
| @@ -63,11 +63,11 @@ | ||
| 63 | 63 | We go into more detail on nginx service setup with Fossil in our |
| 64 | 64 | [Debian/Ubuntu specific guide](../debian/nginx.md). Then in [a later |
| 65 | 65 | article](../../tls-nginx.md) that builds upon that, we show how to add |
| 66 | 66 | TLS encryption to this basic SCGI + nginx setup on Debian type OSes. |
| 67 | 67 | |
| 68 | -Similarly, our [OpenBSD specific guide](../openbsd/httpd.md) details how | |
| 68 | +Similarly, our [OpenBSD specific guide](../openbsd/fastcgi.md) details how | |
| 69 | 69 | to setup a Fossil server using httpd and FastCGI on OpenBSD. |
| 70 | 70 | |
| 71 | 71 | *[Return to the top-level Fossil server article.](../)* |
| 72 | 72 | |
| 73 | 73 | [404]: https://en.wikipedia.org/wiki/HTTP_404 |
| 74 | 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/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 |