Check-in [f59c9ecb5e]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge updates from trunk.
Timelines: family | ancestors | descendants | both | cmake-ide
Files: files | file ages | folders
SHA3-256: f59c9ecb5eb25a149dbf07fc872f4c1b974425e2b835ea29f4c3d0e9fdf3a5a6
User & Date: ashepilko 2018-09-14 19:13:10
Context
2018-09-14
21:15
Describe the debugging options. check-in: d364933abc user: ashepilko tags: cmake-ide
19:13
Merge updates from trunk. check-in: f59c9ecb5e user: ashepilko tags: cmake-ide
12:53
Update the change log. check-in: d78f1f3c59 user: drh tags: trunk
2018-08-08
01:27
Fix the MSVC specific link flags. check-in: 27c4e7b02d user: ashepilko tags: cmake-ide
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.in.

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
E = @EXEEXT@

TCC = @CC@

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = tclsh

CFLAGS = @CFLAGS@
LIB =	@LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
BCCFLAGS =	@CPPFLAGS@ $(CFLAGS)
TCCFLAGS =	@EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
INSTALLDIR = $(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@







|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
E = @EXEEXT@

TCC = @CC@

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = @TCLSH@

CFLAGS = @CFLAGS@
LIB =	@LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
BCCFLAGS =	@CPPFLAGS@ $(CFLAGS)
TCCFLAGS =	@EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
INSTALLDIR = $(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@

Changes to auto.def.

31
32
33
34
35
36
37
38










39

























40
41
42
43
44
45
46
cc-with {-includes {stdint.h inttypes.h}} {
    cc-check-types uint32_t uint16_t int16_t uint8_t
}

# Use pread/pwrite system calls in place of seek + read/write if possible
define USE_PREAD [cc-check-functions pread]

# Find tclsh for the test suite. Can't yet use jimsh for this.










cc-check-progs tclsh


























define EXTRA_CFLAGS "-Wall"
define EXTRA_LDFLAGS ""
define USE_SYSTEM_SQLITE 0
define USE_LINENOISE 0
define FOSSIL_ENABLE_MINIZ 0
define USE_MMAN_H 0







|
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
cc-with {-includes {stdint.h inttypes.h}} {
    cc-check-types uint32_t uint16_t int16_t uint8_t
}

# Use pread/pwrite system calls in place of seek + read/write if possible
define USE_PREAD [cc-check-functions pread]

# Find tclsh for the test suite.
#
# We can't use jimsh for this: the test suite uses features of Tcl that
# Jim doesn't support, either statically or due to the way it's built by
# autosetup.  For example, Jim supports `file normalize`, but only if
# you build it with HAVE_REALPATH, which won't ever be defined in this
# context because autosetup doesn't try to discover platform-specific
# details like that before it decides to build jimsh0.  Besides which,
# autosetup won't build jimsh0 at all if it can find tclsh itself.
# Ironically, this means we may right now be running under either jimsh0
# or a version of tclsh that we find unsuitable below!
cc-check-progs tclsh
set hbtd /usr/local/Cellar/tcl-tk
if {[string equal false [get-define TCLSH]]} {
    msg-result "WARNING: 'make test' will not run here."
} else {
    set v [exec /bin/sh -c "echo 'puts \$tcl_version' | tclsh"]
    if {[expr $v >= 8.6]} {
        msg-result "Found Tclsh version $v in the PATH."
        define TCLSH tclsh
    } elseif {[file isdirectory $hbtd]} {
        # This is a macOS system with the Homebrew version of Tcl/Tk
        # installed.  Select the newest version.  It won't normally be
        # in the PATH to avoid shadowing /usr/bin/tclsh, and even if it
        # were in the PATH, it's bad practice to put /usr/local/bin (the
        # Homebrew default) ahead of /usr/bin, especially given that
        # it's user-writeable by default with Homebrew.  Thus, we can be
        # pretty sure the only way to call it is with an absolute path.
        set v [exec ls -tr $hbtd | tail -1]
        set path "$hbtd/$v/bin/tclsh"
        define TCLSH $path
        msg-result "Using Homebrew Tcl/Tk version $path."
    } else {
        msg-result "WARNING: tclsh $v found; need >= 8.6 for 'make test'."
        define TCLSH false     ;# force "make test" failure via /usr/bin/false
    }
}

define EXTRA_CFLAGS "-Wall"
define EXTRA_LDFLAGS ""
define USE_SYSTEM_SQLITE 0
define USE_LINENOISE 0
define FOSSIL_ENABLE_MINIZ 0
define USE_MMAN_H 0

Changes to skins/ardoise/header.txt.

39
40
41
42
43
44
45



46
47
48
49
50
51
52
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags



}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}







>
>
>







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}

Changes to skins/black_and_white/header.txt.

20
21
22
23
24
25
26



27
28
29
30
31
32
33
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"



}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}







>
>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  html "<a href='$home/forum'>Forum</a>\n"
}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}

Changes to skins/blitz/header.txt.

43
44
45
46
47
48
49



50
51
52
53
54
55
56
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags



}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}







>
>
>







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}

Changes to skins/blitz_no_logo/header.txt.

40
41
42
43
44
45
46



47
48
49
50
51
52
53
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags



}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}







>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}

Changes to skins/bootstrap/header.txt.

75
76
77
78
79
80
81







82
83
84
85
86
87
88
                    html "<li><a href='$home/brlist'>Branches</a></li>\n"
                  }
                  if {[string compare $current_page "taglist"] == 0} {
                    html "<li class='active'><a href='$home/taglist'>Tags</a></li>\n"
                  } else {
                    html "<li><a href='$home/taglist'>Tags</a></li>\n"
                  }







                }
                if {[hascap r]} {
                  if {[string compare $current_page "reportlist"] == 0} {
                    html "<li class='active'><a href='$home/reportlist'>Tickets</a></li>\n"
                  } else {
                    html "<li><a href='$home/reportlist'>Tickets</a></li>\n"
                  }







>
>
>
>
>
>
>







75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
                    html "<li><a href='$home/brlist'>Branches</a></li>\n"
                  }
                  if {[string compare $current_page "taglist"] == 0} {
                    html "<li class='active'><a href='$home/taglist'>Tags</a></li>\n"
                  } else {
                    html "<li><a href='$home/taglist'>Tags</a></li>\n"
                  }
                }
                if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
                  if {[string compare $current_page "forum"] == 0} {
                    html "<li class='active'><a href='$home/forum'>Forum</a></li>\n"
                  } else {
                    html "<li><a href='$home/forum'>Forum</a></li>\n"
                  }
                }
                if {[hascap r]} {
                  if {[string compare $current_page "reportlist"] == 0} {
                    html "<li class='active'><a href='$home/reportlist'>Tickets</a></li>\n"
                  } else {
                    html "<li><a href='$home/reportlist'>Tickets</a></li>\n"
                  }

Changes to skins/default/css.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
body {
    margin: 0 auto;
    min-width: 800px;
    padding: 0px 20px;
    background-color: white;
    font-family: sans-serif;
    font-size:14pt;
    -moz-text-size-adjust: none;
    -webkit-text-size-adjust: none;
    -mx-text-size-adjust: none;
}

a {
    color: #4183C4;
    text-decoration: none;
}
a:hover {
    color: #4183C4;
    text-decoration: underline;
}




hr {
    color: #eee;
}

.title {
    color: #4183C4;
    float:left;
    padding-top: 30px;
    padding-bottom: 10px;
}
.title h1 {
    display:inline;
}
.title h1:after {
    content: " / ";
    color: #777;


<
<
















>
>
>








<
<







1
2


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


30
31
32
33
34
35
36
body {
    margin: 0 auto;


    background-color: white;
    font-family: sans-serif;
    font-size:14pt;
    -moz-text-size-adjust: none;
    -webkit-text-size-adjust: none;
    -mx-text-size-adjust: none;
}

a {
    color: #4183C4;
    text-decoration: none;
}
a:hover {
    color: #4183C4;
    text-decoration: underline;
}
div.forumPosts a:visited {
    color: #6A7F94;
}

hr {
    color: #eee;
}

.title {
    color: #4183C4;
    float:left;


}
.title h1 {
    display:inline;
}
.title h1:after {
    content: " / ";
    color: #777;
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87




88
89
90
91
92
93
94
95
96
97
98
99
100














101
102
103
104
105
106
107
108
109
110
111
    display: inline-block;
    margin-right: 1em;
}

.status {
    float:right;
    font-size:.7em;
    padding-top:50px;
}

.mainmenu {
    font-size:.8em;
    clear:both;
    padding:10px;
    background:#eaeaea linear-gradient(#fafafa, #eaeaea) repeat-x;
    border:1px solid #eaeaea;
    border-radius:5px;




}

.mainmenu a {
    padding: 10px 20px;
    text-decoration:none;
    color: #777;
    border-right:1px solid #eaeaea;
}
.mainmenu a.active,
.mainmenu a:hover {
    color: #000;
    border-bottom:2px solid #D26911;
}















.submenu {
    font-size: .7em;
    margin-top: 10px;
    padding: 10px;
    border-bottom: 1px solid #ccc;
}

.submenu a, .submenu label {
    padding: 10px 11px;
    text-decoration:none;







<





<



>
>
>
>



<









>
>
>
>
>
>
>
>
>
>
>
>
>
>



<







70
71
72
73
74
75
76

77
78
79
80
81

82
83
84
85
86
87
88
89
90
91

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

118
119
120
121
122
123
124
    display: inline-block;
    margin-right: 1em;
}

.status {
    float:right;
    font-size:.7em;

}

.mainmenu {
    font-size:.8em;
    clear:both;

    background:#eaeaea linear-gradient(#fafafa, #eaeaea) repeat-x;
    border:1px solid #eaeaea;
    border-radius:5px;
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
    z-index: 21;  /* just above hbdrop */
}

.mainmenu a {

    text-decoration:none;
    color: #777;
    border-right:1px solid #eaeaea;
}
.mainmenu a.active,
.mainmenu a:hover {
    color: #000;
    border-bottom:2px solid #D26911;
}

div#hbdrop {
    background-color: white;
    border: 1px solid black;
    border-top: white;
    border-radius: 0 0 0.5em 0.5em;
    display: none;
    font-size: 80%;
    left: 2em;
    width: 90%;
    padding-right: 1em;
    position: absolute;
    z-index: 20;  /* just below mainmenu, but above timeline bubbles */
}

.submenu {
    font-size: .7em;

    padding: 10px;
    border-bottom: 1px solid #ccc;
}

.submenu a, .submenu label {
    padding: 10px 11px;
    text-decoration:none;
197
198
199
200
201
202
203
204




















































}
div.timelineDate {
    font-weight: bold;
    white-space: nowrap;
}
span.submenuctrl, span.submenuctrl input, select.submenuctrl {
  color: #777;
}




























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
}
div.timelineDate {
    font-weight: bold;
    white-space: nowrap;
}
span.submenuctrl, span.submenuctrl input, select.submenuctrl {
  color: #777;
}
span.submenuctrl {
  white-space: nowrap;
}
div.submenu label {
  white-space: nowrap;
}

@media screen and (max-width: 600px) {
  /* Spacing for mobile */
  body {
    padding-left: 4px;
    padding-right: 4px;
  }
  .title {
    padding-top: 0px;
    padding-bottom: 0px;
  }
  .status {padding-top: 0px;}
  .mainmenu a {
    padding: 10px 10px;
  }
  .mainmenu {
    padding: 10px;
  }
  .desktoponly {
    display: none;
  }
}
@media screen and (min-width: 600px) {
  /* Spacing for desktop */
  body {
    padding-left: 20px;
    padding-right: 20px;
  }
  .title {
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .status {padding-top: 30px;}
  .mainmenu a {
    padding: 10px 20px;
  }
  .mainmenu {
    padding: 10px;
  }
}
@media screen and (max-width: 1200px) {
  /* Special declarations for narrow desktop or wide mobile */
  .wideonly {
    display: none;
  }
}

Changes to skins/default/footer.txt.

1
2
3
4
5



<div class="footer">
This page was generated in about
<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
Fossil $release_version $manifest_version $manifest_date
</div>








>
>
>
1
2
3
4
5
6
7
8
<div class="footer">
This page was generated in about
<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
Fossil $release_version $manifest_version $manifest_date
</div>
<script nonce="$nonce">
<th1>styleScript</th1>
</script>

Changes to skins/default/header.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
30
31
32



33
34
35
36
37
38
39
40
41
42
43
44

<div class="header">
  <div class="title"><h1>$<project_name></h1>$<title></div>
    <div class="status"><th1>
 if {[info exists login]} {
   html "$login — <a href='$home/login'>Logout</a>\n"
 } else {
   html "<a href='$home/login'>Login</a>\n"
 }
    </th1></div>
</div>
<div class="mainmenu">
<th1>
proc menulink {url name} {
  upvar current_page current
  upvar home home
  if {[string range $url 0 [string length $current]] eq "/$current"} {
    html "<a href='$home$url' class='active'>$name</a>\n"
  } else {
    html "<a href='$home$url'>$name</a>\n"
  }
}

menulink $index_page Home
if {[anycap jor]} {
  menulink /timeline Timeline
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files
}
if {[hascap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags
}



if {[hascap r]} {
  menulink /ticket Tickets
}
if {[hascap j]} {
  menulink /wiki Wiki
}
if {[hascap s]} {
  menulink /setup Admin
} elseif {[hascap a]} {
  menulink /setup_ulist Users
}
</th1></div>













|



|

|


>
|

|


|


|
|

>
>
>

|


|


|

|


>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<div class="header">
  <div class="title"><h1>$<project_name></h1>$<title></div>
    <div class="status"><th1>
 if {[info exists login]} {
   html "$login — <a href='$home/login'>Logout</a>\n"
 } else {
   html "<a href='$home/login'>Login</a>\n"
 }
    </th1></div>
</div>
<div class="mainmenu">
<th1>
proc menulink {url name cls} {
  upvar current_page current
  upvar home home
  if {[string range $url 0 [string length $current]] eq "/$current"} {
    html "<a href='$home$url' class='active $cls'>$name</a>\n"
  } else {
    html "<a href='$home$url' class='$cls'>$name</a>\n"
  }
}
html "<a href='#'>&#9776;</a>"
menulink $index_page Home {}
if {[anycap jor]} {
  menulink /timeline Timeline {}
}
if {[hascap oh]} {
  menulink /dir?ci=tip Files desktoponly
}
if {[hascap o]} {
  menulink  /brlist Branches desktoponly
  menulink  /taglist Tags wideonly
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum wideonly
}
if {[hascap r]} {
  menulink /ticket Tickets wideonly
}
if {[hascap j]} {
  menulink /wiki Wiki wideonly
}
if {[hascap s]} {
  menulink /setup Admin {}
} elseif {[hascap a]} {
  menulink /setup_ulist Users {}
}
</th1></div>
<div id='hbdrop'></div>

Added skins/default/js.txt.



















































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
** Copyright © 2018 Warren Young
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Contact: wyoung on the Fossil forum, https://fossil-scm.org/forum/
**
*******************************************************************************
**
** This file contains the JS code specific to the Fossil default skin.
** Currently, the only thing this does is handle clicks on its hamburger
** menu button.
*/
(function() {
  var panel = document.getElementById("hbdrop");
  if (!panel) return;   // site admin might've nuked it
  var panelBorder = panel.style.border;
  var animate = panel.style.hasOwnProperty('transition');
  var animMS = 400;

  // Calculate panel height despite its being hidden at call time.
  // Based on https://stackoverflow.com/a/29047447/142454
  var panelHeight;  // computed on sitemap load
  function calculatePanelHeight() {
    // Get initial panel styles so we can restore them below.
    var es   = window.getComputedStyle(panel),
        edis = es.display,
        epos = es.position,
        evis = es.visibility;

    // Restyle the panel so we can measure its height while invisible.
    panel.style.visibility = 'hidden';
    panel.style.position   = 'absolute';
    panel.style.display    = 'block';
    panelHeight = panel.offsetHeight + 'px';

    // Revert styles now that job is done.
    panel.style.display    = edis;
    panel.style.position   = epos;
    panel.style.visibility = evis;
  }

  // Show the panel by changing the panel height, which kicks off the
  // slide-open/closed transition set up in the XHR onload handler.
  //
  // Schedule the change for a near-future time in case this is the
  // first call, where the div was initially invisible.  If we were
  // to change the panel's visibility and height at the same time
  // instead, that would prevent the browser from seeing the height
  // change as a state transition, so it'd skip the CSS transition:
  //
  // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions#JavaScript_examples
  function showPanel() {
    if (animate) {
      setTimeout(function() {
        panel.style.maxHeight = panelHeight;
        panel.style.border    = panelBorder;
      }, 40);   // 25ms is insufficient with Firefox 62
    }
    else {
      panel.style.display = 'block';
    }
  }

  // Return true if the panel is showing.
  function panelShowing() {
    if (animate) {
      return panel.style.maxHeight == panelHeight;
    }
    else {
      return panel.style.display == 'block';
    }
  }

  // Click handler for the hamburger button.
  var needSitemapHTML = true;
  document.querySelector("div.mainmenu > a").onclick = function() {
    if (panelShowing()) {
      // Transition back to hidden state.
      if (animate) {
        panel.style.maxHeight = '0';
        setTimeout(function() {
          // Browsers show a 1px high border line when maxHeight == 0,
          // our "hidden" state, so hide the borders in that state, too.
          panel.style.border = 'none';
        }, animMS);
      }
      else {
        panel.style.display = 'none';
      }
    }
    else if (needSitemapHTML) {
      // Only get it once per page load: it isn't likely to
      // change on us.
      var xhr = new XMLHttpRequest();
      xhr.onload = function() {
        var doc = xhr.responseXML;
        if (doc) {
          var sm = doc.querySelector("ul#sitemap");
          if (sm && xhr.status == 200) {
            // Got sitemap.  Insert it into the drop-down panel.
            needSitemapHTML = false;
            panel.innerHTML = sm.outerHTML;

            // Display the panel
            if (animate) {
              // Set up a CSS transition to animate the panel open and
              // closed.  Only needs to be done once per page load.
              // Based on https://stackoverflow.com/a/29047447/142454
              calculatePanelHeight();
              panel.style.transition = 'max-height ' + animMS +
                  'ms ease-in-out';
              panel.style.overflowY  = 'hidden';
              panel.style.maxHeight  = '0';
              showPanel();
            }
            panel.style.display = 'block';
          }
        }
        // else, can't parse response as HTML or XML
      }
      xhr.open("POST", "$home/sitemap");   // note the TH1 substitution!
      xhr.responseType = "document";
      xhr.send("popup=1");
    }
    else {
      showPanel();   // just show what we built above
    }
    return false;  // prevent browser from acting on <a> click
  }
})();

Changes to skins/eagle/header.txt.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<script>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout("updateClock();",(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {







|












|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<th1>html "<script nonce='$nonce'>"</th1>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout(updateClock,(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {
104
105
106
107
108
109
110



111
112
113
114
115
116
117
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink /brlist Branches
  menulink /taglist Tags



}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}







>
>
>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink /brlist Branches
  menulink /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}

Changes to skins/enhanced1/header.txt.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<script>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout("updateClock();",(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {







|












|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<th1>html "<script nonce='$nonce'>"</th1>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout(updateClock,(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {
104
105
106
107
108
109
110



111
112
113
114
115
116
117
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink /brlist Branches
  menulink /taglist Tags



}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}







>
>
>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink /brlist Branches
  menulink /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}

Changes to skins/khaki/header.txt.

18
19
20
21
22
23
24



25
26
27
28
29
30
31
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"



}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}







>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  html "<a href='$home/forum'>Forum</a>\n"
}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}

Changes to skins/original/header.txt.

19
20
21
22
23
24
25



26
27
28
29
30
31
32
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"



}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}







>
>
>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  html "<a href='$home/forum'>Forum</a>\n"
}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}

Changes to skins/plain_gray/details.txt.

1
2
3
4
timeline-arrowheads:        1
timeline-circle-nodes:      0
timeline-color-graph-lines: 0
white-foreground:           0

|


1
2
3
4
timeline-arrowheads:        1
timeline-circle-nodes:      1
timeline-color-graph-lines: 0
white-foreground:           0

Changes to skins/plain_gray/header.txt.

16
17
18
19
20
21
22



23
24
25
26
27
28
29
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"



}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}







>
>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  html "<a href='$home/forum'>Forum</a>\n"
}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}

Changes to skins/rounded1/header.txt.

20
21
22
23
24
25
26



27
28
29
30
31
32
33
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"



}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}







>
>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
}
if {[anoncap oh]} {
  html "<a href='$home/tree?ci=tip'>Files</a>\n"
}
if {[anoncap o]} {
  html "<a href='$home/brlist'>Branches</a>\n"
  html "<a href='$home/taglist'>Tags</a>\n"
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  html "<a href='$home/forum'>Forum</a>\n"
}
if {[anoncap r]} {
  html "<a href='$home/ticket'>Tickets</a>\n"
}
if {[anoncap j]} {
  html "<a href='$home/wiki'>Wiki</a>\n"
}

Changes to skins/xekri/header.txt.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<script>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout("updateClock();",(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {







|












|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
       puts "Not logged in"
     }
  </th1></nobr><small><div id="clock"></div></small></div>
</div>
<th1>html "<script nonce='$nonce'>"</th1>
function updateClock(){
  var e = document.getElementById("clock");
  if(e){
    var d = new Date();
    function f(n) {
      return n < 10 ? '0' + n : n;
    }
    e.innerHTML = d.getUTCFullYear()+ '-' +
      f(d.getUTCMonth() + 1) + '-' +
      f(d.getUTCDate())      + ' ' +
      f(d.getUTCHours())     + ':' +
      f(d.getUTCMinutes());
    setTimeout(updateClock,(60-d.getUTCSeconds())*1000);
  }
}
updateClock();
</script>
<div class="mainmenu">
<th1>
proc menulink {url name} {
108
109
110
111
112
113
114



115
116
117
118
119
120
121
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags



}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}







>
>
>







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
}
if {[anoncap oh]} {
  menulink /dir?ci=tip Files
}
if {[anoncap o]} {
  menulink  /brlist Branches
  menulink  /taglist Tags
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
  menulink /forum Forum
}
if {[anoncap r]} {
  menulink /ticket Tickets
}
if {[anoncap j]} {
  menulink /wiki Wiki
}

Name change from src/email.c to src/alerts.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18





19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Logic for email notification, also known as "alerts".





*/
#include "config.h"
#include "email.h"
#include <assert.h>
#include <time.h>

/*
** Maximum size of the subscriberCode blob, in bytes
*/
#define SUBSCRIBER_CODE_SZ 32

/*
** SQL code to implement the tables needed by the email notification
** system.
*/
static const char zEmailInit[] =
@ DROP TABLE IF EXISTS repository.subscriber;
@ -- Subscribers are distinct from users.  A person can have a log-in in
@ -- the USER table without being a subscriber.  Or a person can be a
@ -- subscriber without having a USER table entry.  Or they can have both.
@ -- In the last case the suname column points from the subscriber entry
@ -- to the USER entry.
@ --

|
















>
>
>
>
>
|

|












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
** Copyright (c) 2018 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Logic for email notification, also known as "alerts".
**
** Are you looking for the code that reads and writes the internet
** email protocol?  That is not here.  See the "smtp.c" file instead.
** Yes, the choice of source code filenames is not the greatest, but
** it is not so bad that changing them seems justified.
*/ 
#include "config.h"
#include "alerts.h"
#include <assert.h>
#include <time.h>

/*
** Maximum size of the subscriberCode blob, in bytes
*/
#define SUBSCRIBER_CODE_SZ 32

/*
** SQL code to implement the tables needed by the email notification
** system.
*/
static const char zAlertInit[] =
@ DROP TABLE IF EXISTS repository.subscriber;
@ -- Subscribers are distinct from users.  A person can have a log-in in
@ -- the USER table without being a subscriber.  Or a person can be a
@ -- subscriber without having a USER table entry.  Or they can have both.
@ -- In the last case the suname column points from the subscriber entry
@ -- to the USER entry.
@ --
70
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114





115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@ --
@ -- The first character of the eventid determines the event type.
@ -- Remaining characters determine the specific event.  For example,
@ -- 'c4413' means check-in with rid=4413.
@ --
@ CREATE TABLE repository.pending_alert(
@   eventid TEXT PRIMARY KEY,         -- Object that changed
@   sentSep BOOLEAN DEFAULT false,    -- individual emails sent
@   sentDigest BOOLEAN DEFAULT false  -- digest emails sent

@ ) WITHOUT ROWID;
@ 
@ DROP TABLE IF EXISTS repository.email_bounce;
@ -- Record bounced emails.  If too many bounces are received within
@ -- some defined time range, then cancel the subscription.  Older
@ -- entries are periodically purged.
@ --
@ CREATE TABLE repository.email_bounce(
@   subscriberId INTEGER, -- to whom the email was sent.
@   sendTime INTEGER,     -- seconds since 1970 when email was sent
@   rcvdTime INTEGER      -- seconds since 1970 when bounce was received
@ );
;

/*
** Return true if the email notification tables exist.
*/
int email_tables_exist(void){
  return db_table_exists("repository", "subscriber");
}

/*
** Make sure the table needed for email notification exist in the repository.
**
** If the bOnlyIfEnabled option is true, then tables are only created
** if the email-send-method is something other than "off".
*/
void email_schema(int bOnlyIfEnabled){
  if( !email_tables_exist() ){
    if( bOnlyIfEnabled
     && fossil_strcmp(db_get("email-send-method","off"),"off")==0
    ){
      return;  /* Don't create table for disabled email */
    }
    db_multi_exec(zEmailInit/*works-like:""*/);
    email_triggers_enable();





  }
}

/*
** Enable triggers that automatically populate the pending_alert
** table.
*/
void email_triggers_enable(void){
  if( !db_table_exists("repository","pending_alert") ) return;
  db_multi_exec(
    "CREATE TRIGGER IF NOT EXISTS repository.email_trigger1\n"
    "AFTER INSERT ON event BEGIN\n"
    "  INSERT INTO pending_alert(eventid)\n"
    "    SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
    "    ON CONFLICT(eventId) DO NOTHING;\n"
    "END;"
  );
}

/*
** Disable triggers the event_pending triggers.
**
** This must be called before rebuilding the EVENT table, for example
** via the "fossil rebuild" command.
*/
void email_triggers_disable(void){
  db_multi_exec(
    "DROP TRIGGER IF EXISTS repository.email_trigger1;\n"

  );
}

/*
** Return true if email alerts are active.
*/
int email_enabled(void){
  if( !email_tables_exist() ) return 0;
  if( fossil_strcmp(db_get("email-send-method","off"),"off")==0 ) return 0;
  return 1;
}

/*
** If the subscriber table does not exist, then paint an error message
** web page and return true.
**
** If the subscriber table does exist, return 0 without doing anything.
*/
static int email_webpages_disabled(void){
  if( email_tables_exist() ) return 0;
  style_header("Email Alerts Are Disabled");
  @ <p>Email alerts are disabled on this server</p>
  style_footer();
  return 1;
}

/*
** Insert a "Subscriber List" submenu link if the current user
** is an administrator.
*/
void email_submenu_common(void){
  if( g.perm.Admin ){
    if( fossil_strcmp(g.zPath,"subscribers") ){
      style_submenu_element("List Subscribers","%R/subscribers");
    }
    if( fossil_strcmp(g.zPath,"subscribe") ){
      style_submenu_element("Add New Subscriber","%R/subscribe");
    }







|
|
>


|




|









|









|
|





|
|
>
>
>
>
>







|


|














|

|
>






|
|










|
|










|







75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@ --
@ -- The first character of the eventid determines the event type.
@ -- Remaining characters determine the specific event.  For example,
@ -- 'c4413' means check-in with rid=4413.
@ --
@ CREATE TABLE repository.pending_alert(
@   eventid TEXT PRIMARY KEY,         -- Object that changed
@   sentSep BOOLEAN DEFAULT false,    -- individual alert sent
@   sentDigest BOOLEAN DEFAULT false, -- digest alert sent
@   sentMod BOOLEAN DEFAULT false     -- pending moderation alert sent
@ ) WITHOUT ROWID;
@ 
@ DROP TABLE IF EXISTS repository.alert_bounce;
@ -- Record bounced emails.  If too many bounces are received within
@ -- some defined time range, then cancel the subscription.  Older
@ -- entries are periodically purged.
@ --
@ CREATE TABLE repository.alert_bounce(
@   subscriberId INTEGER, -- to whom the email was sent.
@   sendTime INTEGER,     -- seconds since 1970 when email was sent
@   rcvdTime INTEGER      -- seconds since 1970 when bounce was received
@ );
;

/*
** Return true if the email notification tables exist.
*/
int alert_tables_exist(void){
  return db_table_exists("repository", "subscriber");
}

/*
** Make sure the table needed for email notification exist in the repository.
**
** If the bOnlyIfEnabled option is true, then tables are only created
** if the email-send-method is something other than "off".
*/
void alert_schema(int bOnlyIfEnabled){
  if( !alert_tables_exist() ){
    if( bOnlyIfEnabled
     && fossil_strcmp(db_get("email-send-method","off"),"off")==0
    ){
      return;  /* Don't create table for disabled email */
    }
    db_multi_exec(zAlertInit/*works-like:""*/);
    alert_triggers_enable();
  }else if( !db_table_has_column("repository","pending_alert","sentMod") ){
    db_multi_exec(
      "ALTER TABLE repository.pending_alert"
      " ADD COLUMN sentMod BOOLEAN DEFAULT false;"
    );
  }
}

/*
** Enable triggers that automatically populate the pending_alert
** table.
*/
void alert_triggers_enable(void){
  if( !db_table_exists("repository","pending_alert") ) return;
  db_multi_exec(
    "CREATE TRIGGER IF NOT EXISTS repository.alert_trigger1\n"
    "AFTER INSERT ON event BEGIN\n"
    "  INSERT INTO pending_alert(eventid)\n"
    "    SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
    "    ON CONFLICT(eventId) DO NOTHING;\n"
    "END;"
  );
}

/*
** Disable triggers the event_pending triggers.
**
** This must be called before rebuilding the EVENT table, for example
** via the "fossil rebuild" command.
*/
void alert_triggers_disable(void){
  db_multi_exec(
    "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n"
    "DROP TRIGGER IF EXISTS repository.email_trigger1;\n" // Legacy
  );
}

/*
** Return true if email alerts are active.
*/
int alert_enabled(void){
  if( !alert_tables_exist() ) return 0;
  if( fossil_strcmp(db_get("email-send-method","off"),"off")==0 ) return 0;
  return 1;
}

/*
** If the subscriber table does not exist, then paint an error message
** web page and return true.
**
** If the subscriber table does exist, return 0 without doing anything.
*/
static int alert_webpages_disabled(void){
  if( alert_tables_exist() ) return 0;
  style_header("Email Alerts Are Disabled");
  @ <p>Email alerts are disabled on this server</p>
  style_footer();
  return 1;
}

/*
** Insert a "Subscriber List" submenu link if the current user
** is an administrator.
*/
void alert_submenu_common(void){
  if( g.perm.Admin ){
    if( fossil_strcmp(g.zPath,"subscribers") ){
      style_submenu_element("List Subscribers","%R/subscribers");
    }
    if( fossil_strcmp(g.zPath,"subscribe") ){
      style_submenu_element("Add New Subscriber","%R/subscribe");
    }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();

  email_submenu_common();
  style_submenu_element("Send Announcement","%R/announce");
  style_header("Email Notification Setup");
  @ <h1>Status</h1>
  @ <table class="label-value">
  if( email_enabled() ){
    stats_for_email();
  }else{
    @ <th>Disabled</th>
  }
  @ </table>
  @ <hr>
  @ <h1> Configuration </h1>
  @ <form action="%R/setup_notification" method="post"><div>
  @ <input type="submit"  name="submit" value="Apply Changes" /><hr>
  login_insert_csrf_secret();

  entry_attribute("Canonical Server URL", 40, "email-url",
                   "eurl", "", 0);
  @ <p><b>Required.</b>
  @ This is URL used as the basename for hyperlinks included in
  @ email alert text.  Omit the trailing "/".
  @ Suggested value: "%h(g.zBaseURL)"
  @ (Property: "email-url")</p>
  @ <hr>

  entry_attribute("\"From\" email address", 20, "email-self",
                   "eself", "", 0);
  @ <p><b>Required.</b>
  @ This is the email from which email notifications are sent.  The



  @ system administrator should arrange for emails sent to this address
  @ to be handed off to the "fossil email incoming" command so that Fossil
  @ can handle bounces. (Property: "email-self")</p>
  @ <hr>

  entry_attribute("Repository Nickname", 16, "email-subname",
                   "enn", "", 0);
  @ <p><b>Required.</b>
  @ This is short name used to identifies the repository in the
  @ Subject: line of email alerts.  Traditionally this name is
  @ included in square brackets.  Examples: "[fossil-src]", "[sqlite-src]".
  @ (Property: "email-subname")</p>
  @ <hr>

  multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
       "off", count(azSendMethods)/2, azSendMethods);
  @ <p>How to send email.  Requires auxiliary information from the fields
  @ that follow.  Hint: Use the <a href="%R/announce">/announce</a> page
  @ to send test message to debug this setting.
  @ (Property: "email-send-method")</p>
  email_schema(1);
  entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command",
                   "ecmd", "sendmail -t", 0);
  @ <p>When the send method is "pipe to a command", this is the command
  @ that is run.  Email messages are piped into the standard input of this
  @ command.  The command is expected to extract the sender address,
  @ recepient addresses, and subject from the header of the piped email
  @ text.  (Property: "email-send-command")</p>

  entry_attribute("Store Emails In This Database", 60, "email-send-db",







|




|




















|


|
>
>
>
|



















|

|







211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();

  alert_submenu_common();
  style_submenu_element("Send Announcement","%R/announce");
  style_header("Email Notification Setup");
  @ <h1>Status</h1>
  @ <table class="label-value">
  if( alert_enabled() ){
    stats_for_email();
  }else{
    @ <th>Disabled</th>
  }
  @ </table>
  @ <hr>
  @ <h1> Configuration </h1>
  @ <form action="%R/setup_notification" method="post"><div>
  @ <input type="submit"  name="submit" value="Apply Changes" /><hr>
  login_insert_csrf_secret();

  entry_attribute("Canonical Server URL", 40, "email-url",
                   "eurl", "", 0);
  @ <p><b>Required.</b>
  @ This is URL used as the basename for hyperlinks included in
  @ email alert text.  Omit the trailing "/".
  @ Suggested value: "%h(g.zBaseURL)"
  @ (Property: "email-url")</p>
  @ <hr>

  entry_attribute("\"Return-Path\" email address", 20, "email-self",
                   "eself", "", 0);
  @ <p><b>Required.</b>
  @ This is the email to which email notification bounces should be sent.
  @ In cases where the email notification does not align with a specific
  @ Fossil login account (for example, digest messages), this is also
  @ the "From:" address of the email notification.
  @ The system administrator should arrange for emails sent to this address
  @ to be handed off to the "fossil email incoming" command so that Fossil
  @ can handle bounces. (Property: "email-self")</p>
  @ <hr>

  entry_attribute("Repository Nickname", 16, "email-subname",
                   "enn", "", 0);
  @ <p><b>Required.</b>
  @ This is short name used to identifies the repository in the
  @ Subject: line of email alerts.  Traditionally this name is
  @ included in square brackets.  Examples: "[fossil-src]", "[sqlite-src]".
  @ (Property: "email-subname")</p>
  @ <hr>

  multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
       "off", count(azSendMethods)/2, azSendMethods);
  @ <p>How to send email.  Requires auxiliary information from the fields
  @ that follow.  Hint: Use the <a href="%R/announce">/announce</a> page
  @ to send test message to debug this setting.
  @ (Property: "email-send-method")</p>
  alert_schema(1);
  entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command",
                   "ecmd", "sendmail -ti", 0);
  @ <p>When the send method is "pipe to a command", this is the command
  @ that is run.  Email messages are piped into the standard input of this
  @ command.  The command is expected to extract the sender address,
  @ recepient addresses, and subject from the header of the piped email
  @ text.  (Property: "email-send-command")</p>

  entry_attribute("Store Emails In This Database", 60, "email-send-db",
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
  entry_attribute("Administrator email address", 40, "email-admin",
                   "eadmin", "", 0);
  @ <p>This is the email for the human administrator for the system.
  @ Abuse and trouble reports are send here.
  @ (Property: "email-admin")</p>
  @ <hr>

  entry_attribute("Inbound email directory", 40, "email-receive-dir",
                   "erdir", "", 0);
  @ <p>Inbound emails can be stored in a directory for analysis as
  @ a debugging aid.  Put the name of that directory in this entry box.
  @ Disable saving of inbound email by making this an empty string.
  @ Abuse and trouble reports are send here.
  @ (Property: "email-receive-dir")</p>
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}

#if 0







<
<
<
<
<
<
<
<







302
303
304
305
306
307
308








309
310
311
312
313
314
315
  entry_attribute("Administrator email address", 40, "email-admin",
                   "eadmin", "", 0);
  @ <p>This is the email for the human administrator for the system.
  @ Abuse and trouble reports are send here.
  @ (Property: "email-admin")</p>
  @ <hr>









  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}

#if 0
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# define pclose _pclose
#endif

#if INTERFACE
/*
** An instance of the following object is used to send emails.
*/
struct EmailSender {
  sqlite3 *db;               /* Database emails are sent to */
  sqlite3_stmt *pStmt;       /* Stmt to insert into the database */
  const char *zDest;         /* How to send email. */
  const char *zDb;           /* Name of database file */
  const char *zDir;          /* Directory in which to store as email files */
  const char *zCmd;          /* Command to run for each email */
  const char *zFrom;         /* Emails come from here */
  SmtpSession *pSmtp;        /* SMTP relay connection */
  Blob out;                  /* For zDest=="blob" */
  char *zErr;                /* Error message */
  u32 mFlags;                /* Flags */
  int bImmediateFail;        /* On any error, call fossil_fatal() */
};

/* Allowed values for mFlags to email_sender_new().
*/
#define EMAIL_IMMEDIATE_FAIL   0x0001   /* Call fossil_fatal() on any error */
#define EMAIL_TRACE            0x0002   /* Log sending process on console */

#endif /* INTERFACE */

/*
** Shutdown an emailer.  Clear all information other than the error message.
*/
static void emailerShutdown(EmailSender *p){
  sqlite3_finalize(p->pStmt);
  p->pStmt = 0;
  sqlite3_close(p->db);
  p->db = 0;
  p->zDb = 0;
  p->zDir = 0;
  p->zCmd = 0;
  if( p->pSmtp ){
    smtp_client_quit(p->pSmtp);
    smtp_session_free(p->pSmtp);
    p->pSmtp = 0;
  }
  blob_reset(&p->out);
}

/*
** Put the EmailSender into an error state.
*/
static void emailerError(EmailSender *p, const char *zFormat, ...){
  va_list ap;
  fossil_free(p->zErr);
  va_start(ap, zFormat);
  p->zErr = vmprintf(zFormat, ap);
  va_end(ap);
  emailerShutdown(p);
  if( p->mFlags & EMAIL_IMMEDIATE_FAIL ){
    fossil_fatal("%s", p->zErr);
  }
}

/*
** Free an email sender object
*/
void email_sender_free(EmailSender *p){
  if( p ){
    emailerShutdown(p);
    fossil_free(p->zErr);
    fossil_free(p);
  }
}

/*
** Get an email setting value.  Report an error if not configured.
** Return 0 on success and one if there is an error.
*/
static int emailerGetSetting(
  EmailSender *p,        /* Where to report the error */
  const char **pzVal,    /* Write the setting value here */
  const char *zName      /* Name of the setting */
){
  const char *z = db_get(zName, 0);
  int rc = 0;
  if( z==0 || z[0]==0 ){
    emailerError(p, "missing \"%s\" setting", zName);
    rc = 1;
  }else{
    *pzVal = z;
  }
  return rc;
}

/*
** Create a new EmailSender object.
**
** The method used for sending email is determined by various email-*
** settings, and especially email-send-method.  The repository
** email-send-method can be overridden by the zAltDest argument to
** cause a different sending mechanism to be used.  Pass "stdout" to
** zAltDest to cause all emails to be printed to the console for
** debugging purposes.
**
** The EmailSender object returned must be freed using email_sender_free().
*/
EmailSender *email_sender_new(const char *zAltDest, u32 mFlags){
  EmailSender *p;

  p = fossil_malloc(sizeof(*p));
  memset(p, 0, sizeof(*p));
  blob_init(&p->out, 0, 0);
  p->mFlags = mFlags;
  if( zAltDest ){
    p->zDest = zAltDest;







|














|

|
|






|
















|

|






|







|












|















|








|

|
|







371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# define pclose _pclose
#endif

#if INTERFACE
/*
** An instance of the following object is used to send emails.
*/
struct AlertSender {
  sqlite3 *db;               /* Database emails are sent to */
  sqlite3_stmt *pStmt;       /* Stmt to insert into the database */
  const char *zDest;         /* How to send email. */
  const char *zDb;           /* Name of database file */
  const char *zDir;          /* Directory in which to store as email files */
  const char *zCmd;          /* Command to run for each email */
  const char *zFrom;         /* Emails come from here */
  SmtpSession *pSmtp;        /* SMTP relay connection */
  Blob out;                  /* For zDest=="blob" */
  char *zErr;                /* Error message */
  u32 mFlags;                /* Flags */
  int bImmediateFail;        /* On any error, call fossil_fatal() */
};

/* Allowed values for mFlags to alert_sender_new().
*/
#define ALERT_IMMEDIATE_FAIL   0x0001   /* Call fossil_fatal() on any error */
#define ALERT_TRACE            0x0002   /* Log sending process on console */

#endif /* INTERFACE */

/*
** Shutdown an emailer.  Clear all information other than the error message.
*/
static void emailerShutdown(AlertSender *p){
  sqlite3_finalize(p->pStmt);
  p->pStmt = 0;
  sqlite3_close(p->db);
  p->db = 0;
  p->zDb = 0;
  p->zDir = 0;
  p->zCmd = 0;
  if( p->pSmtp ){
    smtp_client_quit(p->pSmtp);
    smtp_session_free(p->pSmtp);
    p->pSmtp = 0;
  }
  blob_reset(&p->out);
}

/*
** Put the AlertSender into an error state.
*/
static void emailerError(AlertSender *p, const char *zFormat, ...){
  va_list ap;
  fossil_free(p->zErr);
  va_start(ap, zFormat);
  p->zErr = vmprintf(zFormat, ap);
  va_end(ap);
  emailerShutdown(p);
  if( p->mFlags & ALERT_IMMEDIATE_FAIL ){
    fossil_fatal("%s", p->zErr);
  }
}

/*
** Free an email sender object
*/
void alert_sender_free(AlertSender *p){
  if( p ){
    emailerShutdown(p);
    fossil_free(p->zErr);
    fossil_free(p);
  }
}

/*
** Get an email setting value.  Report an error if not configured.
** Return 0 on success and one if there is an error.
*/
static int emailerGetSetting(
  AlertSender *p,        /* Where to report the error */
  const char **pzVal,    /* Write the setting value here */
  const char *zName      /* Name of the setting */
){
  const char *z = db_get(zName, 0);
  int rc = 0;
  if( z==0 || z[0]==0 ){
    emailerError(p, "missing \"%s\" setting", zName);
    rc = 1;
  }else{
    *pzVal = z;
  }
  return rc;
}

/*
** Create a new AlertSender object.
**
** The method used for sending email is determined by various email-*
** settings, and especially email-send-method.  The repository
** email-send-method can be overridden by the zAltDest argument to
** cause a different sending mechanism to be used.  Pass "stdout" to
** zAltDest to cause all emails to be printed to the console for
** debugging purposes.
**
** The AlertSender object returned must be freed using alert_sender_free().
*/
AlertSender *alert_sender_new(const char *zAltDest, u32 mFlags){
  AlertSender *p;

  p = fossil_malloc(sizeof(*p));
  memset(p, 0, sizeof(*p));
  blob_init(&p->out, 0, 0);
  p->mFlags = mFlags;
  if( zAltDest ){
    p->zDest = zAltDest;
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
  }else if( fossil_strcmp(p->zDest, "blob")==0 ){
    blob_init(&p->out, 0, 0);
  }else if( fossil_strcmp(p->zDest, "relay")==0 ){
    const char *zRelay = 0;
    emailerGetSetting(p, &zRelay, "email-send-relayhost");
    if( zRelay ){
      u32 smtpFlags = SMTP_DIRECT;
      if( mFlags & EMAIL_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT;
      p->pSmtp = smtp_session_new(p->zFrom, zRelay, smtpFlags);
      smtp_client_startup(p->pSmtp);
    }
  }
  return p;
}








|







520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
  }else if( fossil_strcmp(p->zDest, "blob")==0 ){
    blob_init(&p->out, 0, 0);
  }else if( fossil_strcmp(p->zDest, "relay")==0 ){
    const char *zRelay = 0;
    emailerGetSetting(p, &zRelay, "email-send-relayhost");
    if( zRelay ){
      u32 smtpFlags = SMTP_DIRECT;
      if( mFlags & ALERT_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT;
      p->pSmtp = smtp_session_new(p->zFrom, zRelay, smtpFlags);
      smtp_client_startup(p->pSmtp);
    }
  }
  return p;
}

566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620

























































































621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
    }
  }
  return 0;
}

/*
** Make a copy of the input string up to but not including the
** first ">" character.
**
** Verify that the string really that is to be copied really is a
** valid email address.  If it is not, then return NULL.
**
** This routine is more restrictive than necessary.  It does not
** allow comments, IP address, quoted strings, or certain uncommon
** characters.  The only non-alphanumerics allowed in the local
** part are "_", "+", "-" and "+".
*/
char *email_copy_addr(const char *z){
  int i;
  int nAt = 0;
  int nDot = 0;
  char c;
  if( z[0]=='.' ) return 0;  /* Local part cannot begin with "." */
  for(i=0; (c = z[i])!=0 && c!='>'; i++){
    if( fossil_isalnum(c) ){
      /* Alphanumerics are always ok */
    }else if( c=='@' ){
      if( nAt ) return 0;   /* Only a single "@"  allowed */
      if( i>64 ) return 0;  /* Local part too big */
      nAt = 1;
      nDot = 0;
      if( i==0 ) return 0;  /* Disallow empty local part */
      if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
      if( z[i+1]=='.' || z[i+1]=='-' ){
        return 0; /* Domain cannot begin with "." or "-" */
      }
    }else if( c=='-' ){
      if( z[i+1]=='>' ) return 0;  /* Last character cannot be "-" */
    }else if( c=='.' ){
      if( z[i+1]=='.' ) return 0;  /* Do not allow ".." */
      if( z[i+1]=='>' ) return 0;  /* Domain may not end with . */
      nDot++;
    }else if( (c=='_' || c=='+') && nAt==0 ){
      /* _ and + are ok in the local part */
    }else{
      return 0;   /* Anything else is an error */
    }
  }
  if( c!='>' ) return 0;      /* Missing final ">" */
  if( nAt==0 ) return 0;      /* No "@" found anywhere */
  if( nDot==0 ) return 0;     /* No "." in the domain */

  /* If we reach this point, the email address is valid */
  return mprintf("%.*s", i, z);
}


























































































/*
** Extract all To: header values from the email header supplied.
** Store them in the array list.
*/
void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){
  int nTo = 0;
  char **azTo = 0;
  Blob v;
  char *z, *zAddr;
  int i;
  
  email_header_value(pMsg, "to", &v);
  z = blob_str(&v);
  for(i=0; z[i]; i++){
    if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1]))!=0 ){
      azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) );
      azTo[nTo++] = zAddr;
    }
  }
  *pnTo = nTo;
  *pazTo = azTo;
}







|









|





|













|


|







|






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>















|







573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
    }
  }
  return 0;
}

/*
** Make a copy of the input string up to but not including the
** first cTerm character.
**
** Verify that the string really that is to be copied really is a
** valid email address.  If it is not, then return NULL.
**
** This routine is more restrictive than necessary.  It does not
** allow comments, IP address, quoted strings, or certain uncommon
** characters.  The only non-alphanumerics allowed in the local
** part are "_", "+", "-" and "+".
*/
char *email_copy_addr(const char *z, char cTerm ){
  int i;
  int nAt = 0;
  int nDot = 0;
  char c;
  if( z[0]=='.' ) return 0;  /* Local part cannot begin with "." */
  for(i=0; (c = z[i])!=0 && c!=cTerm; i++){
    if( fossil_isalnum(c) ){
      /* Alphanumerics are always ok */
    }else if( c=='@' ){
      if( nAt ) return 0;   /* Only a single "@"  allowed */
      if( i>64 ) return 0;  /* Local part too big */
      nAt = 1;
      nDot = 0;
      if( i==0 ) return 0;  /* Disallow empty local part */
      if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
      if( z[i+1]=='.' || z[i+1]=='-' ){
        return 0; /* Domain cannot begin with "." or "-" */
      }
    }else if( c=='-' ){
      if( z[i+1]==cTerm ) return 0;  /* Last character cannot be "-" */
    }else if( c=='.' ){
      if( z[i+1]=='.' ) return 0;  /* Do not allow ".." */
      if( z[i+1]==cTerm ) return 0;  /* Domain may not end with . */
      nDot++;
    }else if( (c=='_' || c=='+') && nAt==0 ){
      /* _ and + are ok in the local part */
    }else{
      return 0;   /* Anything else is an error */
    }
  }
  if( c!=cTerm ) return 0;    /* Missing terminator */
  if( nAt==0 ) return 0;      /* No "@" found anywhere */
  if( nDot==0 ) return 0;     /* No "." in the domain */

  /* If we reach this point, the email address is valid */
  return mprintf("%.*s", i, z);
}

/*
** Scan the input string for a valid email address enclosed in <...>
** If the string contains one or more email addresses, extract the first
** one into memory obtained from mprintf() and return a pointer to it.
** If no valid email address can be found, return NULL.
*/
char *alert_find_emailaddr(const char *zIn){
  char *zOut = 0;
  while( zIn!=0 ){
     zIn = (const char*)strchr(zIn, '<');
     if( zIn==0 ) break;
     zIn++;
     zOut = email_copy_addr(zIn, '>');
     if( zOut!=0 ) break;
  }
  return zOut;
}

/*
** SQL function:  find_emailaddr(X)
**
** Return the first valid email address of the form <...> in input string
** X.  Or return NULL if not found.
*/
void alert_find_emailaddr_func(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zIn = (const char*)sqlite3_value_text(argv[0]);
  char *zOut = alert_find_emailaddr(zIn);
  if( zOut ){
    sqlite3_result_text(context, zOut, -1, fossil_free);
  }
}

/*
** Return the hostname portion of an email address - the part following
** the @
*/
char *alert_hostname(const char *zAddr){
  char *z = strchr(zAddr, '@');
  if( z ){
    z++;
  }else{
    z = (char*)zAddr;
  }
  return z;
}

/*
** Return a pointer to a fake email mailbox name that corresponds
** to human-readable name zFromName.  The fake mailbox name is based
** on a hash.  No huge problems arise if there is a hash collisions,
** but it is still better if collisions can be avoided.
**
** The returned string is held in a static buffer and is overwritten
** by each subsequent call to this routine.
*/
static char *alert_mailbox_name(const char *zFromName){
  static char zHash[20];
  unsigned int x = 0;
  int n = 0;
  while( zFromName[0] ){
    n++;
    x = x*1103515245 + 12345 + ((unsigned char*)zFromName)[0];
    zFromName++;
  }
  sqlite3_snprintf(sizeof(zHash), zHash,
      "noreply%x%08x", n, x);
  return zHash;
}

/*
** COMMAND: test-mailbox-hashname
**
** Usage: %fossil test-mailbox-hashname HUMAN-NAME ...
**
** Return the mailbox hash name corresponding to each human-readable
** name on the command line.  This is a test interface for the
** alert_mailbox_name() function.
*/
void alert_test_mailbox_hashname(void){
  int i;
  for(i=2; i<g.argc; i++){
    fossil_print("%30s: %s\n", g.argv[i], alert_mailbox_name(g.argv[i]));
  }
}

/*
** Extract all To: header values from the email header supplied.
** Store them in the array list.
*/
void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){
  int nTo = 0;
  char **azTo = 0;
  Blob v;
  char *z, *zAddr;
  int i;
  
  email_header_value(pMsg, "to", &v);
  z = blob_str(&v);
  for(i=0; z[i]; i++){
    if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1],'>'))!=0 ){
      azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) );
      azTo[nTo++] = zAddr;
    }
  }
  *pnTo = nTo;
  *pazTo = azTo;
}
663
664
665
666
667
668
669


670
671
672
673












674



675


676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693





694

695

696
697
698
699
700
701


702

703
704
705
706
707
708
709
710
** This routine will add fields to the header as follows:
**
**     From:
**     Date:
**     Message-Id:
**     Content-Type:
**     Content-Transfer-Encoding:


**     
** The caller maintains ownership of the input Blobs.  This routine will
** read the Blobs and send them onward to the email system, but it will
** not free them.












*/



void email_send(EmailSender *p, Blob *pHdr, Blob *pBody){


  Blob all, *pOut;
  u64 r1, r2;
  if( p->mFlags & EMAIL_TRACE ){
    fossil_print("Sending email\n");
  }
  if( fossil_strcmp(p->zDest, "off")==0 ){
    return;
  }
  if( fossil_strcmp(p->zDest, "blob")==0 ){
    pOut = &p->out;
    if( blob_size(pOut) ){
      blob_appendf(pOut, "%.72c\n", '=');
    }
  }else{
    blob_init(&all, 0, 0);
    pOut = &all;
  }
  blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));





  blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);

  blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));

  /* Message-id format:  "<$(date)x$(random).$(from)>" where $(date) is
  ** the current unix-time in hex, $(random) is a 64-bit random number,
  ** and $(from) is the sender. */
  sqlite3_randomness(sizeof(r1), &r1);
  r2 = time(0);
  blob_appendf(pOut, "Message-Id: <%llxx%016llx.%s>\r\n", r2, r1, p->zFrom);


  blob_add_final_newline(pBody);

  blob_appendf(pOut,"Content-Type: text/plain\r\n");
#if 0
  blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
  append_base64(pOut, pBody);
#else
  blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
  append_quoted(pOut, pBody);
#endif







>
>




>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
|
>
>


|















>
>
>
>
>
|
>

>
|
|
|
|
|
|
>
>

>
|







759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
** This routine will add fields to the header as follows:
**
**     From:
**     Date:
**     Message-Id:
**     Content-Type:
**     Content-Transfer-Encoding:
**     MIME-Version:
**     X-Fossil-From:
**     
** The caller maintains ownership of the input Blobs.  This routine will
** read the Blobs and send them onward to the email system, but it will
** not free them.
**
** The Message-Id: field is added if there is not already a Message-Id
** in the pHdr parameter.
**
** If the zFromName argument is not NULL, then it should be a human-readable
** name or handle for the sender.  In that case, "From:" becomes a made-up
** email address based on a hash of zFromName and the domain of email-self,
** and an additional "X-Fossil-From:" field is inserted with the email-self
** address.  Downstream software might use the X-Fossil-From header to set
** the envelope-from address of the email.  If zFromName is a NULL pointer, 
** then the "From:" is set to the email-self value and X-Fossil-From is
** omitted.
*/
void alert_send(
  AlertSender *p,           /* Emailer context */
  Blob *pHdr,               /* Email header (incomplete) */
  Blob *pBody,              /* Email body */
  const char *zFromName     /* Optional human-readable name of sender */
){
  Blob all, *pOut;
  u64 r1, r2;
  if( p->mFlags & ALERT_TRACE ){
    fossil_print("Sending email\n");
  }
  if( fossil_strcmp(p->zDest, "off")==0 ){
    return;
  }
  if( fossil_strcmp(p->zDest, "blob")==0 ){
    pOut = &p->out;
    if( blob_size(pOut) ){
      blob_appendf(pOut, "%.72c\n", '=');
    }
  }else{
    blob_init(&all, 0, 0);
    pOut = &all;
  }
  blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
  if( zFromName ){
    blob_appendf(pOut, "From: %s <%s@%s>\r\n",
       zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
    blob_appendf(pOut, "X-Fossil-From: <%s>\r\n", p->zFrom);
  }else{
    blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
  }
  blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
  if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
    /* Message-id format:  "<$(date)x$(random)@$(from-host)>" where $(date) is
    ** the current unix-time in hex, $(random) is a 64-bit random number,
    ** and $(from) is the domain part of the email-self setting. */
    sqlite3_randomness(sizeof(r1), &r1);
    r2 = time(0);
    blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n",
                 r2, r1, alert_hostname(p->zFrom));
  }
  blob_add_final_newline(pBody);
  blob_appendf(pOut, "MIME-Version: 1.0\r\n");
  blob_appendf(pOut, "Content-Type: text/plain; charset=\"UTF-8\"\r\n");
#if 0
  blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
  append_base64(pOut, pBody);
#else
  blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
  append_quoted(pOut, pBody);
#endif
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    email_header_to_free(nTo, azTo);
    blob_add_final_newline(&all);
    fossil_print("%s", blob_str(&all));
  }
  blob_reset(&all);
}

/*
** Analyze and act on a received email.
**
** This routine takes ownership of the Blob parameter and is responsible
** for freeing that blob when it is done with it.
**
** This routine acts on all email messages received from the
** "fossil email inbound" command.
*/
void email_receive(Blob *pMsg){
  /* To Do:  Look for bounce messages and possibly disable subscriptions */
  blob_reset(pMsg);
}

/*
** SETTING: email-send-method         width=5 default=off
** Determine the method used to send email.  Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout".  The "off" value
** means no email is ever sent.  The "relay" value means emails are sent
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
** The "pipe" value means email messages are piped into a command 







<
<
<
<
<
<
<
<
<
<
<
<
<
<







875
876
877
878
879
880
881














882
883
884
885
886
887
888
    email_header_to_free(nTo, azTo);
    blob_add_final_newline(&all);
    fossil_print("%s", blob_str(&all));
  }
  blob_reset(&all);
}















/*
** SETTING: email-send-method         width=5 default=off
** Determine the method used to send email.  Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout".  The "off" value
** means no email is ever sent.  The "relay" value means emails are sent
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
** The "pipe" value means email messages are piped into a command 
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827






828
829
830
831
832
833
834
835
836
837
838


839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887


888
889




890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938

939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
** if the email-send-method is set to "db".
*/
/*
** SETTING: email-self               width=40
** This is the email address for the repository.  Outbound emails add
** this email address as the "From:" field.
*/
/*
** SETTING: email-receive-dir         width=40
** Inbound email messages are saved as separate files in this directory,
** for debugging analysis.  Disable saving of inbound emails omitting
** this setting, or making it an empty string.
*/
/*
** SETTING: email-send-relayhost      width=40
** This is the hostname and TCP port to which output email messages
** are sent when email-send-method is "relay".  There should be an
** SMTP server configured as a Mail Submission Agent listening on the
** designated host and port and all times.
*/


/*
** COMMAND: email
** 
** Usage: %fossil email SUBCOMMAND ARGS...
**
** Subcommands:
**






**    exec                    Compose and send pending email alerts.
**                            Some installations may want to do this via
**                            a cron-job to make sure alerts are sent
**                            in a timely manner.
**                            Options:
**
**                               --digest     Send digests
**                               --test       Resets to standard output
**
**    inbound [FILE]          Receive an inbound email message.  This message
**                            is analyzed to see if it is a bounce, and if


**                            necessary, subscribers may be disabled.
**
**    reset                   Hard reset of all email notification tables
**                            in the repository.  This erases all subscription
**                            information.  Use with extreme care.
**
**    send TO [OPTIONS]       Send a single email message using whatever
**                            email sending mechanism is currently configured.
**                            Use this for testing the email configuration.
**                            Options:
**
**                              --body FILENAME
**                              --smtp-trace
**                              --stdout
**                              --subject|-S SUBJECT
**
**    settings [NAME VALUE]   With no arguments, list all email settings.
**                            Or change the value of a single email setting.
**
**    subscribers [PATTERN]   List all subscribers matching PATTERN.
**
**    unsubscribe EMAIL       Remove a single subscriber with the given EMAIL.
*/
void email_cmd(void){
  const char *zCmd;
  int nCmd;
  db_find_and_open_repository(0, 0);
  email_schema(0);
  zCmd = g.argc>=3 ? g.argv[2] : "x";
  nCmd = (int)strlen(zCmd);
  if( strncmp(zCmd, "exec", nCmd)==0 ){
    u32 eFlags = 0;
    if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST;
    if( find_option("test",0,0)!=0 ){
      eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT;
    }
    verify_all_options();
    email_send_alerts(eFlags);
  }else
  if( strncmp(zCmd, "inbound", nCmd)==0 ){
    Blob email;
    const char *zInboundDir = db_get("email-receive-dir","");
    verify_all_options();
    if( g.argc!=3 && g.argc!=4 ){
      usage("inbound [FILE]");
    }
    blob_read_from_file(&email, g.argc==3 ? "-" : g.argv[3], ExtFILE);
    if( zInboundDir[0] ){
      char *zFN = file_time_tempname(zInboundDir,".email");


      blob_write_to_file(&email, zFN);
      fossil_free(zFN);




    }
    email_receive(&email);
  }else
  if( strncmp(zCmd, "reset", nCmd)==0 ){
    int c;
    int bForce = find_option("force","f",0)!=0;
    verify_all_options();
    if( bForce ){
      c = 'y';
    }else{
      Blob yn;
      fossil_print(
          "This will erase all content in the repository tables, thus\n"
          "deleting all subscriber information.  The information will be\n"
          "unrecoverable.\n");
      prompt_user("Continue? (y/N) ", &yn);
      c = blob_str(&yn)[0];
      blob_reset(&yn);
    }
    if( c=='y' ){
      email_triggers_disable();
      db_multi_exec(
        "DROP TABLE IF EXISTS subscriber;\n"
        "DROP TABLE IF EXISTS pending_alert;\n"
        "DROP TABLE IF EXISTS email_bounce;\n"
        /* Legacy */
        "DROP TABLE IF EXISTS email_pending;\n"
        "DROP TABLE IF EXISTS subscription;\n"
      );
      email_schema(0);
    }
  }else
  if( strncmp(zCmd, "send", nCmd)==0 ){
    Blob prompt, body, hdr;
    const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
    int i;
    u32 mFlags = EMAIL_IMMEDIATE_FAIL;
    const char *zSubject = find_option("subject", "S", 1);
    const char *zSource = find_option("body", 0, 1);
    EmailSender *pSender;
    if( find_option("smtp-trace",0,0)!=0 ) mFlags |= EMAIL_TRACE;
    verify_all_options();
    blob_init(&prompt, 0, 0);
    blob_init(&body, 0, 0);
    blob_init(&hdr, 0, 0);
    blob_appendf(&hdr,"To: ");
    for(i=3; i<g.argc; i++){
      if( i>3 ) blob_append(&hdr, ", ", 2);
      blob_appendf(&hdr, "<%s>", g.argv[i]);

    }
    blob_append(&hdr,"\r\n",2);
    if( zSubject ){
      blob_appendf(&hdr, "Subject: %s\r\n", zSubject);
    }
    if( zSource ){
      blob_read_from_file(&body, zSource, ExtFILE);
    }else{
      prompt_for_user_comment(&body, &prompt);
    }
    blob_add_final_newline(&body);
    pSender = email_sender_new(zDest, mFlags);
    email_send(pSender, &hdr, &body);
    email_sender_free(pSender);
    blob_reset(&hdr);
    blob_reset(&body);
    blob_reset(&prompt);
  }else
  if( strncmp(zCmd, "settings", nCmd)==0 ){
    int isGlobal = find_option("global",0,0)!=0;
    int nSetting;
    const Setting *pSetting = setting_info(&nSetting);
    db_open_config(1, 0);
    verify_all_options();







<
<
<
<
<
<










|

|



>
>
>
>
>
>
|






|

|
|
>
>
|

<
|
<

|

|
|






<
<
<
<
<


|



|


|
<
<
<
<
<
<
<
<
<
|
<

|
<
<
<
<
<
>
>
|
|
>
>
>
>

|


















|



|

|


|



<
<
<
|
<
|
<
|
<
<
<
<
<
<
<
<
>

<
<
<
<
|
<
<
<
<
<
|
<
<
<
<
<







910
911
912
913
914
915
916






917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953

954

955
956
957
958
959
960
961
962
963
964
965





966
967
968
969
970
971
972
973
974
975









976

977
978





979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019



1020

1021

1022








1023
1024




1025





1026





1027
1028
1029
1030
1031
1032
1033
** if the email-send-method is set to "db".
*/
/*
** SETTING: email-self               width=40
** This is the email address for the repository.  Outbound emails add
** this email address as the "From:" field.
*/






/*
** SETTING: email-send-relayhost      width=40
** This is the hostname and TCP port to which output email messages
** are sent when email-send-method is "relay".  There should be an
** SMTP server configured as a Mail Submission Agent listening on the
** designated host and port and all times.
*/


/*
** COMMAND: alerts
** 
** Usage: %fossil alerts SUBCOMMAND ARGS...
**
** Subcommands:
**
**    pending                 Show all pending alerts.  Useful for debugging.
**
**    reset                   Hard reset of all email notification tables
**                            in the repository.  This erases all subscription
**                            information.  ** Use with extreme care **
**
**    send                    Compose and send pending email alerts.
**                            Some installations may want to do this via
**                            a cron-job to make sure alerts are sent
**                            in a timely manner.
**                            Options:
**
**                               --digest     Send digests
**                               --test       Write to standard output
**
**    settings [NAME VALUE]   With no arguments, list all email settings.
**                            Or change the value of a single email setting.
**
**    status                  Report on the status of the email alert
**                            subsystem
**

**    subscribers [PATTERN]   List all subscribers matching PATTERN.

**
**    test-message TO [OPTS]  Send a single email message using whatever
**                            email sending mechanism is currently configured.
**                            Use this for testing the email notification
**                            configuration.  Options:
**
**                              --body FILENAME
**                              --smtp-trace
**                              --stdout
**                              --subject|-S SUBJECT
**





**    unsubscribe EMAIL       Remove a single subscriber with the given EMAIL.
*/
void alert_cmd(void){
  const char *zCmd;
  int nCmd;
  db_find_and_open_repository(0, 0);
  alert_schema(0);
  zCmd = g.argc>=3 ? g.argv[2] : "x";
  nCmd = (int)strlen(zCmd);
  if( strncmp(zCmd, "pending", nCmd)==0 ){









    Stmt q;

    verify_all_options();
    if( g.argc!=3 ) usage("pending");





    db_prepare(&q,"SELECT eventid, sentSep, sentDigest, sentMod"
                  "  FROM pending_alert");
    while( db_step(&q)==SQLITE_ROW ){
      fossil_print("%10s %7s %10s %7s\n",
         db_column_text(&q,0),
         db_column_int(&q,1) ? "sentSep" : "",
         db_column_int(&q,2) ? "sentDigest" : "",
         db_column_int(&q,3) ? "sentMod" : "");
    }
    db_finalize(&q);
  }else
  if( strncmp(zCmd, "reset", nCmd)==0 ){
    int c;
    int bForce = find_option("force","f",0)!=0;
    verify_all_options();
    if( bForce ){
      c = 'y';
    }else{
      Blob yn;
      fossil_print(
          "This will erase all content in the repository tables, thus\n"
          "deleting all subscriber information.  The information will be\n"
          "unrecoverable.\n");
      prompt_user("Continue? (y/N) ", &yn);
      c = blob_str(&yn)[0];
      blob_reset(&yn);
    }
    if( c=='y' ){
      alert_triggers_disable();
      db_multi_exec(
        "DROP TABLE IF EXISTS subscriber;\n"
        "DROP TABLE IF EXISTS pending_alert;\n"
        "DROP TABLE IF EXISTS alert_bounce;\n"
        /* Legacy */
        "DROP TABLE IF EXISTS alert_pending;\n"
        "DROP TABLE IF EXISTS subscription;\n"
      );
      alert_schema(0);
    }
  }else
  if( strncmp(zCmd, "send", nCmd)==0 ){



    u32 eFlags = 0;

    if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST;

    if( find_option("test",0,0)!=0 ){








      eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT;
    }




    verify_all_options();





    alert_send_alerts(eFlags);





  }else
  if( strncmp(zCmd, "settings", nCmd)==0 ){
    int isGlobal = find_option("global",0,0)!=0;
    int nSetting;
    const Setting *pSetting = setting_info(&nSetting);
    db_open_config(1, 0);
    verify_all_options();
972
973
974
975
976
977
978






















979
980
981
982
983
984
985
    }
    pSetting = setting_info(&nSetting);
    for(; nSetting>0; nSetting--, pSetting++ ){
      if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
      print_setting(pSetting);
    }
  }else






















  if( strncmp(zCmd, "subscribers", nCmd)==0 ){
    Stmt q;
    verify_all_options();
    if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]");
    if( g.argc==4 ){
      char *zPattern = g.argv[3];
      db_prepare(&q,







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
    }
    pSetting = setting_info(&nSetting);
    for(; nSetting>0; nSetting--, pSetting++ ){
      if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
      print_setting(pSetting);
    }
  }else
  if( strncmp(zCmd, "status", nCmd)==0 ){
    int nSetting, n;
    static const char *zFmt = "%-29s %d\n";
    const Setting *pSetting = setting_info(&nSetting);
    db_open_config(1, 0);
    verify_all_options();
    if( g.argc!=3 ) usage("status");
    pSetting = setting_info(&nSetting);
    for(; nSetting>0; nSetting--, pSetting++ ){
      if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
      print_setting(pSetting);
    }
    n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep");
    fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n);
    n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest");
    fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n);
    n = db_int(0,"SELECT count(*) FROM subscriber");
    fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n);
    n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified"
                   " AND NOT sdonotcall AND length(ssub)>1");
    fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n);
  }else
  if( strncmp(zCmd, "subscribers", nCmd)==0 ){
    Stmt q;
    verify_all_options();
    if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]");
    if( g.argc==4 ){
      char *zPattern = g.argv[3];
      db_prepare(&q,
993
994
995
996
997
998
999


































1000
1001
1002
1003
1004
1005
1006
1007
1008

1009
1010
1011
1012
1013
1014
1015
        "SELECT semail FROM subscriber"
        " ORDER BY semail");
    }
    while( db_step(&q)==SQLITE_ROW ){
      fossil_print("%s\n", db_column_text(&q, 0));
    }
    db_finalize(&q);


































  }else
  if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){
    verify_all_options();
    if( g.argc!=4 ) usage("unsubscribe EMAIL");
    db_multi_exec(
      "DELETE FROM subscriber WHERE semail=%Q", g.argv[3]);
  }else
  {
    usage("exec|inbound|reset|send|setting|subscribers|unsubscribe");

  }
}

/*
** Do error checking on a submitted subscription form.  Return TRUE
** if the submission is valid.  Return false if any problems are seen.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|
>







1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
        "SELECT semail FROM subscriber"
        " ORDER BY semail");
    }
    while( db_step(&q)==SQLITE_ROW ){
      fossil_print("%s\n", db_column_text(&q, 0));
    }
    db_finalize(&q);
  }else
  if( strncmp(zCmd, "test-message", nCmd)==0 ){
    Blob prompt, body, hdr;
    const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
    int i;
    u32 mFlags = ALERT_IMMEDIATE_FAIL;
    const char *zSubject = find_option("subject", "S", 1);
    const char *zSource = find_option("body", 0, 1);
    AlertSender *pSender;
    if( find_option("smtp-trace",0,0)!=0 ) mFlags |= ALERT_TRACE;
    verify_all_options();
    blob_init(&prompt, 0, 0);
    blob_init(&body, 0, 0);
    blob_init(&hdr, 0, 0);
    blob_appendf(&hdr,"To: ");
    for(i=3; i<g.argc; i++){
      if( i>3 ) blob_append(&hdr, ", ", 2);
      blob_appendf(&hdr, "<%s>", g.argv[i]);
    }
    blob_append(&hdr,"\r\n",2);
    if( zSubject==0 ) zSubject = "fossil alerts test-message";
    blob_appendf(&hdr, "Subject: %s\r\n", zSubject);
    if( zSource ){
      blob_read_from_file(&body, zSource, ExtFILE);
    }else{
      prompt_for_user_comment(&body, &prompt);
    }
    blob_add_final_newline(&body);
    pSender = alert_sender_new(zDest, mFlags);
    alert_send(pSender, &hdr, &body, 0);
    alert_sender_free(pSender);
    blob_reset(&hdr);
    blob_reset(&body);
    blob_reset(&prompt);
  }else
  if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){
    verify_all_options();
    if( g.argc!=4 ) usage("unsubscribe EMAIL");
    db_multi_exec(
      "DELETE FROM subscriber WHERE semail=%Q", g.argv[3]);
  }else
  {
    usage("pending|reset|send|setting|status|"
          "subscribers|test-message|unsubscribe");
  }
}

/*
** Do error checking on a submitted subscription form.  Return TRUE
** if the submission is valid.  Return false if any problems are seen.
*/
1024
1025
1026
1027
1028
1029
1030
1031




1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048

  *peErr = 0;
  *pzErr = 0;

  /* Check the validity of the email address.
  **
  **  (1) Exactly one '@' character.
  **  (2) No other characters besides [a-zA-Z0-9._-]




  */
  zEAddr = P("e");
  if( zEAddr==0 ) return 0;
  for(i=j=n=0; (c = zEAddr[i])!=0; i++){
    if( c=='@' ){
      n = i;
      j++;
      continue;
    }
    if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' ){
      *peErr = 1;
      *pzErr = mprintf("illegal character in email address: 0x%x '%c'",
                   c, c);
      return 0;
    }
  }
  if( j!=1 ){







|
>
>
>
>









|







1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180

  *peErr = 0;
  *pzErr = 0;

  /* Check the validity of the email address.
  **
  **  (1) Exactly one '@' character.
  **  (2) No other characters besides [a-zA-Z0-9._+-]
  **
  **  The local part is currently more restrictive than RFC 5322 allows:
  **  https://stackoverflow.com/a/2049510/142454  We will expand this as
  **  necessary.
  */
  zEAddr = P("e");
  if( zEAddr==0 ) return 0;
  for(i=j=n=0; (c = zEAddr[i])!=0; i++){
    if( c=='@' ){
      n = i;
      j++;
      continue;
    }
    if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' && c!='+' ){
      *peErr = 1;
      *pzErr = mprintf("illegal character in email address: 0x%x '%c'",
                   c, c);
      return 0;
    }
  }
  if( j!=1 ){
1094
1095
1096
1097
1098
1099
1100









1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125

1126
1127
1128
1129
1130
1131
1132
1133
1134
@ Save the hyperlink above!  You can reuse this same hyperlink to
@ unsubscribe or to change the kinds of alerts you receive.
@
@ If you do not want to subscribe, you can simply ignore this message.
@ You will not be contacted again.
@
;










/*
** WEBPAGE: subscribe
**
** Allow users to subscribe to email notifications.
**
** This page is usually run by users who are not logged in.
** A logged-in user can add email notifications on the /alerts page.
** Access to this page by a logged in user (other than an
** administrator) results in a redirect to the /alerts page.
**
** Administrators can visit this page in order to sign up other
** users.
**
** The Email-Alerts permission ("7") is required to access this
** page.  To allow anonymous passers-by to sign up for email
** notification, set Email-Alerts on user "nobody" or "anonymous".
*/
void subscribe_page(void){
  int needCaptcha;
  unsigned int uSeed;
  const char *zDecoded;
  char *zCaptcha = 0;
  char *zErr = 0;
  int eErr = 0;


  if( email_webpages_disabled() ) return;
  login_check_credentials();
  if( !g.perm.EmailAlert ){
    login_needed(g.anon.EmailAlert);
    return;
  }
  if( login_is_individual()
   && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)







>
>
>
>
>
>
>
>
>














|










>

|







1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
@ Save the hyperlink above!  You can reuse this same hyperlink to
@ unsubscribe or to change the kinds of alerts you receive.
@
@ If you do not want to subscribe, you can simply ignore this message.
@ You will not be contacted again.
@
;

/*
** Append the text of an email confirmation message to the given
** Blob.  The security code is in zCode.
*/
void alert_append_confirmation_message(Blob *pMsg, const char *zCode){
  blob_appendf(pMsg, zConfirmMsg/*works-like:"%s%s%s"*/,
                   g.zBaseURL, g.zBaseURL, zCode);
}

/*
** WEBPAGE: subscribe
**
** Allow users to subscribe to email notifications.
**
** This page is usually run by users who are not logged in.
** A logged-in user can add email notifications on the /alerts page.
** Access to this page by a logged in user (other than an
** administrator) results in a redirect to the /alerts page.
**
** Administrators can visit this page in order to sign up other
** users.
**
** The Alerts permission ("7") is required to access this
** page.  To allow anonymous passers-by to sign up for email
** notification, set Email-Alerts on user "nobody" or "anonymous".
*/
void subscribe_page(void){
  int needCaptcha;
  unsigned int uSeed;
  const char *zDecoded;
  char *zCaptcha = 0;
  char *zErr = 0;
  int eErr = 0;
  int di;

  if( alert_webpages_disabled() ) return;
  login_check_credentials();
  if( !g.perm.EmailAlert ){
    login_needed(g.anon.EmailAlert);
    return;
  }
  if( login_is_individual()
   && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
    }else{
      /* Everybody else jumps to the page to administer their own
      ** account only. */
      cgi_redirectf("%R/alerts");
      return;
    }
  }
  email_submenu_common();
  needCaptcha = !login_is_individual();
  if( P("submit")
   && cgi_csrf_safe(1)
   && subscribe_error_check(&eErr,&zErr,needCaptcha)
  ){
    /* A validated request for a new subscription has been received. */
    char ssub[20];







|







1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
    }else{
      /* Everybody else jumps to the page to administer their own
      ** account only. */
      cgi_redirectf("%R/alerts");
      return;
    }
  }
  alert_submenu_common();
  needCaptcha = !login_is_individual();
  if( P("submit")
   && cgi_csrf_safe(1)
   && subscribe_error_check(&eErr,&zErr,needCaptcha)
  ){
    /* A validated request for a new subscription has been received. */
    char ssub[20];
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
      ** No verification is required.  Jump immediately to /alerts page.
      */
      cgi_redirectf("%R/alerts/%s", zCode);
      return;
    }else{
      /* We need to send a verification email */
      Blob hdr, body;
      EmailSender *pSender = email_sender_new(0,0);
      blob_init(&hdr,0,0);
      blob_init(&body,0,0);
      blob_appendf(&hdr, "To: <%s>\n", zEAddr);
      blob_appendf(&hdr, "Subject: Subscription verification\n");
      blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/,
                   g.zBaseURL, g.zBaseURL, zCode);
      email_send(pSender, &hdr, &body);
      style_header("Email Alert Verification");
      if( pSender->zErr ){
        @ <h1>Internal Error</h1>
        @ <p>The following internal error was encountered while trying
        @ to send the confirmation email:
        @ <blockquote><pre>
        @ %h(pSender->zErr)
        @ </pre></blockquote>
      }else{
        @ <p>An email has been sent to "%h(zEAddr)". That email contains a
        @ hyperlink that you must click on in order to activate your
        @ subscription.</p>
      }
      email_sender_free(pSender);
      style_footer();
    }
    return;
  }
  style_header("Signup For Email Alerts");
  if( P("submit")==0 ){
    /* If this is the first visit to this page (if this HTTP request did not







|




<
|
|













|







1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344

1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
      ** No verification is required.  Jump immediately to /alerts page.
      */
      cgi_redirectf("%R/alerts/%s", zCode);
      return;
    }else{
      /* We need to send a verification email */
      Blob hdr, body;
      AlertSender *pSender = alert_sender_new(0,0);
      blob_init(&hdr,0,0);
      blob_init(&body,0,0);
      blob_appendf(&hdr, "To: <%s>\n", zEAddr);
      blob_appendf(&hdr, "Subject: Subscription verification\n");

      alert_append_confirmation_message(&body, zCode);
      alert_send(pSender, &hdr, &body, 0);
      style_header("Email Alert Verification");
      if( pSender->zErr ){
        @ <h1>Internal Error</h1>
        @ <p>The following internal error was encountered while trying
        @ to send the confirmation email:
        @ <blockquote><pre>
        @ %h(pSender->zErr)
        @ </pre></blockquote>
      }else{
        @ <p>An email has been sent to "%h(zEAddr)". That email contains a
        @ hyperlink that you must click on in order to activate your
        @ subscription.</p>
      }
      alert_sender_free(pSender);
      style_footer();
    }
    return;
  }
  style_header("Signup For Email Alerts");
  if( P("submit")==0 ){
    /* If this is the first visit to this page (if this HTTP request did not
1235
1236
1237
1238
1239
1240
1241

1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253

1254
1255
1256
1257
1258
1259
1260
1261
1262
1263

1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288




1289

1290


1291


1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
  @ <p>To receive email notifications for changes to this
  @ repository, fill out the form below and press "Submit" button.</p>
  form_begin(0, "%R/subscribe");
  @ <table class="subscribe">
  @ <tr>
  @  <td class="form_label">Email&nbsp;Address:</td>
  @  <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>

  if( eErr==1 ){
    @  <td><span class="loginError">&larr; %h(zErr)</span></td>
  }
  @ </tr>
  if( needCaptcha ){
    uSeed = captcha_seed();
    zDecoded = captcha_decode(uSeed);
    zCaptcha = captcha_render(zDecoded);
    @ <tr>
    @  <td class="form_label">Security Code:</td>
    @  <td><input type="text" name="captcha" value="" size="30">
    @  <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>

    if( eErr==2 ){
      @  <td><span class="loginError">&larr; %h(zErr)</span></td>
    }
    @ </tr>
  }
  if( g.perm.Admin ){
    @ <tr>
    @  <td class="form_label">User:</td>
    @  <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
    @  size="30"></td>

    if( eErr==3 ){
      @  <td><span class="loginError">&larr; %h(zErr)</span></td>
    }
    @ </tr>
  }
  @ <tr>
  @  <td class="form_label">Options:</td>
  @  <td><label><input type="checkbox" name="sa" %s(PCK("sa"))> \
  @  Announcements</label><br>
  if( g.perm.Read ){
    @  <label><input type="checkbox" name="sc" %s(PCK("sc"))> \
    @  Check-ins</label><br>
  }
  if( g.perm.RdForum ){
    @  <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
    @  Forum Posts</label><br>
  }
  if( g.perm.RdTkt ){
    @  <label><input type="checkbox" name="st" %s(PCK("st"))> \
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
    @  Wiki</label><br>
  }




  @  <label><input type="checkbox" name="di" %s(PCK("di"))> \

  @  Daily digest only</label><br>


  if( g.perm.Admin ){


    @  <label><input type="checkbox" name="vi" %s(PCK("vi"))> \
    @  Verified</label><br>
    @  <label><input type="checkbox" name="dnc" %s(PCK("dnc"))> \
    @  Do not call</label><br>
  }
  @ </td>
  @ </tr>
  @ <tr>
  @  <td></td>
  if( needCaptcha && !email_enabled() ){
    @  <td><input type="submit" name="submit" value="Submit" disabled>
    @  (Email current disabled)</td>
  }else{
    @  <td><input type="submit" name="submit" value="Submit"></td>
  }
  @ </tr>
  @ </table>
  if( needCaptcha ){
    @ <div class="captcha"><table class="captcha"><tr><td><pre>
    @ %h(zCaptcha)
    @ </pre>
    @ Enter the 8 characters above in the "Security Code" box
    @ </td></tr></table></div>
  }
  @ </form>
  fossil_free(zErr);
  style_footer();
}

/*
** Either shutdown or completely delete a subscription entry given
** by the hex value zName.  Then paint a webpage that explains that
** the entry has been removed.
*/
static void email_unsubscribe(const char *zName){
  char *zEmail;
  zEmail = db_text(0, "SELECT semail FROM subscriber"
                      " WHERE subscriberCode=hextoblob(%Q)", zName);
  if( zEmail==0 ){
    style_header("Unsubscribe Fail");
    @ <p>Unable to locate a subscriber with the requested key</p>
  }else{







>

|










>

|








>

|




|


















>
>
>
>
|
>
|
>
>

>
>



|

<
<


|








|















|







1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449


1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
  @ <p>To receive email notifications for changes to this
  @ repository, fill out the form below and press "Submit" button.</p>
  form_begin(0, "%R/subscribe");
  @ <table class="subscribe">
  @ <tr>
  @  <td class="form_label">Email&nbsp;Address:</td>
  @  <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
  @ <tr>
  if( eErr==1 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ </tr>
  if( needCaptcha ){
    uSeed = captcha_seed();
    zDecoded = captcha_decode(uSeed);
    zCaptcha = captcha_render(zDecoded);
    @ <tr>
    @  <td class="form_label">Security Code:</td>
    @  <td><input type="text" name="captcha" value="" size="30">
    @  <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
    @ </tr>
    if( eErr==2 ){
      @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
    }
    @ </tr>
  }
  if( g.perm.Admin ){
    @ <tr>
    @  <td class="form_label">User:</td>
    @  <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
    @  size="30"></td>
    @ </tr>
    if( eErr==3 ){
      @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
    }
    @ </tr>
  }
  @ <tr>
  @  <td class="form_label">Topics:</td>
  @  <td><label><input type="checkbox" name="sa" %s(PCK("sa"))> \
  @  Announcements</label><br>
  if( g.perm.Read ){
    @  <label><input type="checkbox" name="sc" %s(PCK("sc"))> \
    @  Check-ins</label><br>
  }
  if( g.perm.RdForum ){
    @  <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
    @  Forum Posts</label><br>
  }
  if( g.perm.RdTkt ){
    @  <label><input type="checkbox" name="st" %s(PCK("st"))> \
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
    @  Wiki</label><br>
  }
  di = PB("di");
  @ </td></tr>
  @ <tr>
  @  <td class="form_label">Delivery:</td>
  @  <td><select size="1" name="di">
  @     <option value="0" %s(di?"":"selected")>Individual Emails</option>
  @     <option value="1" %s(di?"selected":"")>Daily Digest</option>
  @     </select></td>
  @ </tr>
  if( g.perm.Admin ){
    @ <tr>
    @  <td class="form_label">Admin Options:</td><td>
    @  <label><input type="checkbox" name="vi" %s(PCK("vi"))> \
    @  Verified</label><br>
    @  <label><input type="checkbox" name="dnc" %s(PCK("dnc"))> \
    @  Do not call</label></td></tr>
  }


  @ <tr>
  @  <td></td>
  if( needCaptcha && !alert_enabled() ){
    @  <td><input type="submit" name="submit" value="Submit" disabled>
    @  (Email current disabled)</td>
  }else{
    @  <td><input type="submit" name="submit" value="Submit"></td>
  }
  @ </tr>
  @ </table>
  if( needCaptcha ){
    @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
    @ %h(zCaptcha)
    @ </pre>
    @ Enter the 8 characters above in the "Security Code" box
    @ </td></tr></table></div>
  }
  @ </form>
  fossil_free(zErr);
  style_footer();
}

/*
** Either shutdown or completely delete a subscription entry given
** by the hex value zName.  Then paint a webpage that explains that
** the entry has been removed.
*/
static void alert_unsubscribe(const char *zName){
  char *zEmail;
  zEmail = db_text(0, "SELECT semail FROM subscriber"
                      " WHERE subscriberCode=hextoblob(%Q)", zName);
  if( zEmail==0 ){
    style_header("Unsubscribe Fail");
    @ <p>Unable to locate a subscriber with the requested key</p>
  }else{
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375




1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
**    (1)  The name= query parameter contains the subscriberCode.
**         
**    (2)  The user is logged into an account other than "nobody" or
**         "anonymous".  In that case the notification settings
**         associated with that account can be edited without needing
**         to know the subscriber code.
*/
void alerts_page(void){
  const char *zName = P("name");
  Stmt q;
  int sa, sc, sf, st, sw;
  int sdigest, sdonotcall, sverified;
  const char *ssub;
  const char *semail;
  const char *smip;
  const char *suname;
  const char *mtime;
  const char *sctime;
  int eErr = 0;
  char *zErr = 0;

  if( email_webpages_disabled() ) return;
  login_check_credentials();




  if( zName==0 && login_is_individual() ){
    zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
                       " WHERE suname=%Q", g.zLogin);
  }
  if( zName==0 || !validate16(zName, -1) ){
    cgi_redirect("subscribe");
    return;
  }
  email_submenu_common();
  if( P("submit")!=0 && cgi_csrf_safe(1) ){
    int sdonotcall = PB("sdonotcall");
    int sdigest = PB("sdigest");
    char ssub[10];
    int nsub = 0;
    if( PB("sa") )                   ssub[nsub++] = 'a';
    if( g.perm.Read && PB("sc") )    ssub[nsub++] = 'c';







|













|

>
>
>
>








|







1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
**    (1)  The name= query parameter contains the subscriberCode.
**         
**    (2)  The user is logged into an account other than "nobody" or
**         "anonymous".  In that case the notification settings
**         associated with that account can be edited without needing
**         to know the subscriber code.
*/
void alert_page(void){
  const char *zName = P("name");
  Stmt q;
  int sa, sc, sf, st, sw;
  int sdigest, sdonotcall, sverified;
  const char *ssub;
  const char *semail;
  const char *smip;
  const char *suname;
  const char *mtime;
  const char *sctime;
  int eErr = 0;
  char *zErr = 0;

  if( alert_webpages_disabled() ) return;
  login_check_credentials();
  if( !g.perm.EmailAlert ){
    login_needed(g.anon.EmailAlert);
    return;
  }
  if( zName==0 && login_is_individual() ){
    zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
                       " WHERE suname=%Q", g.zLogin);
  }
  if( zName==0 || !validate16(zName, -1) ){
    cgi_redirect("subscribe");
    return;
  }
  alert_submenu_common();
  if( P("submit")!=0 && cgi_csrf_safe(1) ){
    int sdonotcall = PB("sdonotcall");
    int sdigest = PB("sdigest");
    char ssub[10];
    int nsub = 0;
    if( PB("sa") )                   ssub[nsub++] = 'a';
    if( g.perm.Read && PB("sc") )    ssub[nsub++] = 'c';
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
        zName
      );
    }
  }
  if( P("delete")!=0 && cgi_csrf_safe(1) ){
    if( !PB("dodelete") ){
      eErr = 9;
      zErr = mprintf("Select this checkbox and press \"Unsubscribe\" to"
                     " unsubscribe");
    }else{
      email_unsubscribe(zName);
      return;
    }
  }
  db_prepare(&q,
    "SELECT"
    "  semail,"                       /* 0 */
    "  sverified,"                    /* 1 */







|


|







1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
        zName
      );
    }
  }
  if( P("delete")!=0 && cgi_csrf_safe(1) ){
    if( !PB("dodelete") ){
      eErr = 9;
      zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
                     " unsubscribe");
    }else{
      alert_unsubscribe(zName);
      return;
    }
  }
  db_prepare(&q,
    "SELECT"
    "  semail,"                       /* 0 */
    "  sverified,"                    /* 1 */
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534









1535
1536

1537


1538
1539
1540
1541
1542
1543



1544
1545
1546
1547
1548
1549
1550

1551
1552
1553
1554
1555
1556
1557
    @ <tr>
    @  <td class="form_label">User:</td>
    @  <td><input type="text" name="suname" value="%h(suname?suname:"")" \
    @  size="30"></td>
    @ </tr>
  }
  @ <tr>
  @  <td class="form_label">Options:</td>
  @  <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
  @  Announcements</label><br>
  if( g.perm.Read ){
    @  <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\
    @  Check-ins</label><br>
  }
  if( g.perm.RdForum ){
    @  <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
    @  Forum Posts</label><br>
  }
  if( g.perm.RdTkt ){
    @  <label><input type="checkbox" name="st" %s(st?"checked":"")>\
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
    @  Wiki</label><br>
  }









  @  <label><input type="checkbox" name="sdigest" %s(sdigest?"checked":"")>\
  @  Daily digest only</label><br>

  if( g.perm.Admin ){


    @  <label><input type="checkbox" name="sdonotcall" \
    @  %s(sdonotcall?"checked":"")> Do not call</label><br>
    @  <label><input type="checkbox" name="sverified" \
    @  %s(sverified?"checked":"")>\
    @  Verified</label><br>
  }



  @  <label><input type="checkbox" name="dodelete">
  @  Unsubscribe</label> \
  if( eErr==9 ){
    @ <span class="loginError">&larr; %h(zErr)</span>\
  }
  @ <br>
  @ </td></tr>

  @ <tr>
  @  <td></td>
  @  <td><input type="submit" name="submit" value="Submit">
  @  <input type="submit" name="delete" value="Unsubscribe">
  @ </tr>
  @ </table>
  @ </form>







|
















|

>
>
>
>
>
>
>
>
>


>

>
>




|

>
>
>
|
|
<
|
<
<
|
>







1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715

1716


1717
1718
1719
1720
1721
1722
1723
1724
1725
    @ <tr>
    @  <td class="form_label">User:</td>
    @  <td><input type="text" name="suname" value="%h(suname?suname:"")" \
    @  size="30"></td>
    @ </tr>
  }
  @ <tr>
  @  <td class="form_label">Topics:</td>
  @  <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
  @  Announcements</label><br>
  if( g.perm.Read ){
    @  <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\
    @  Check-ins</label><br>
  }
  if( g.perm.RdForum ){
    @  <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
    @  Forum Posts</label><br>
  }
  if( g.perm.RdTkt ){
    @  <label><input type="checkbox" name="st" %s(st?"checked":"")>\
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
    @  Wiki</label>
  }
  @ </td></tr>
  @ <tr>
  @  <td class="form_label">Delivery:</td>
  @  <td><select size="1" name="sdigest">
  @     <option value="0" %s(sdigest?"":"selected")>Individual Emails</option>
  @     <option value="1" %s(sdigest?"selected":"")>Daily Digest</option>
  @     </select></td>
  @ </tr>
#if 0
  @  <label><input type="checkbox" name="sdigest" %s(sdigest?"checked":"")>\
  @  Daily digest only</label><br>
#endif
  if( g.perm.Admin ){
    @ <tr>
    @  <td class="form_label">Admin Options:</td><td>
    @  <label><input type="checkbox" name="sdonotcall" \
    @  %s(sdonotcall?"checked":"")> Do not call</label><br>
    @  <label><input type="checkbox" name="sverified" \
    @  %s(sverified?"checked":"")>\
    @  Verified</label></td></tr>
  }
  if( eErr==9 ){
    @ <tr>
    @  <td class="form_label">Verify:</td><td>
    @  <label><input type="checkbox" name="dodelete">
    @  Unsubscribe</label>

    @ <span class="loginError">&larr; %h(zErr)</span>


    @ </td></tr>
  }
  @ <tr>
  @  <td></td>
  @  <td><input type="submit" name="submit" value="Submit">
  @  <input type="submit" name="delete" value="Unsubscribe">
  @ </tr>
  @ </table>
  @ </form>
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616

  /* If a valid subscriber code is supplied, then unsubscribe immediately.
  */
  if( zName 
   && db_exists("SELECT 1 FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
                zName)
  ){
    email_unsubscribe(zName);
    return;
  }

  /* Logged in users are redirected to the /alerts page */
  login_check_credentials();
  if( login_is_individual() ){
    cgi_redirectf("%R/alerts");







|







1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784

  /* If a valid subscriber code is supplied, then unsubscribe immediately.
  */
  if( zName 
   && db_exists("SELECT 1 FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
                zName)
  ){
    alert_unsubscribe(zName);
    return;
  }

  /* Logged in users are redirected to the /alerts page */
  login_check_credentials();
  if( login_is_individual() ){
    cgi_redirectf("%R/alerts");
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
      bSubmit = 0;
    }
  }
  if( bSubmit ){
    /* If we get this far, it means that a valid unsubscribe request has
    ** been submitted.  Send the appropriate email. */
    Blob hdr, body;
    EmailSender *pSender = email_sender_new(0,0);
    blob_init(&hdr,0,0);
    blob_init(&body,0,0);
    blob_appendf(&hdr, "To: <%s>\r\n", zEAddr);
    blob_appendf(&hdr, "Subject: Unsubscribe Instructions\r\n");
    blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/,
                  g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode);
    email_send(pSender, &hdr, &body);
    style_header("Unsubscribe Instructions Sent");
    if( pSender->zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was encountered while trying to send an
      @ email to %h(zEAddr):
      @ <blockquote><pre>
      @ %h(pSender->zErr)
      @ </pre></blockquote>
    }else{
      @ <p>An email has been sent to "%h(zEAddr)" that explains how to
      @ unsubscribe and/or modify your subscription settings</p>
    }
    email_sender_free(pSender);
    style_footer();
    return;
  }  

  /* Non-logged-in users have to enter an email address to which is
  ** sent a message containing the unsubscribe link.
  */







|






|












|







1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
      bSubmit = 0;
    }
  }
  if( bSubmit ){
    /* If we get this far, it means that a valid unsubscribe request has
    ** been submitted.  Send the appropriate email. */
    Blob hdr, body;
    AlertSender *pSender = alert_sender_new(0,0);
    blob_init(&hdr,0,0);
    blob_init(&body,0,0);
    blob_appendf(&hdr, "To: <%s>\r\n", zEAddr);
    blob_appendf(&hdr, "Subject: Unsubscribe Instructions\r\n");
    blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/,
                  g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode);
    alert_send(pSender, &hdr, &body, 0);
    style_header("Unsubscribe Instructions Sent");
    if( pSender->zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was encountered while trying to send an
      @ email to %h(zEAddr):
      @ <blockquote><pre>
      @ %h(pSender->zErr)
      @ </pre></blockquote>
    }else{
      @ <p>An email has been sent to "%h(zEAddr)" that explains how to
      @ unsubscribe and/or modify your subscription settings</p>
    }
    alert_sender_free(pSender);
    style_footer();
    return;
  }  

  /* Non-logged-in users have to enter an email address to which is
  ** sent a message containing the unsubscribe link.
  */
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
  @  <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\
  @  Completely unsubscribe</label><br>
  @ <tr>
  @  <td></td>
  @  <td><input type="submit" name="submit" value="Submit"></td>
  @ </tr>
  @ </table>
  @ <div class="captcha"><table class="captcha"><tr><td><pre>
  @ %h(zCaptcha)
  @ </pre>
  @ Enter the 8 characters above in the "Security Code" box
  @ </td></tr></table></div>
  @ </form>
  fossil_free(zErr);
  style_footer();







|







1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
  @  <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\
  @  Completely unsubscribe</label><br>
  @ <tr>
  @  <td></td>
  @  <td><input type="submit" name="submit" value="Submit"></td>
  @ </tr>
  @ </table>
  @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
  @ %h(zCaptcha)
  @ </pre>
  @ Enter the 8 characters above in the "Security Code" box
  @ </td></tr></table></div>
  @ </form>
  fossil_free(zErr);
  style_footer();
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744

1745
1746
1747
1748
1749
1750
1751
1752

1753
1754
1755


1756
1757
1758
1759
1760
1761
1762
1763
1764

1765

1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779

1780
1781
1782
1783
1784
1785
1786
1787
1788
1789


1790

1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801


1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819


1820


1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834

1835
1836
1837
1838
1839
1840
1841

1842

1843
1844
1845
1846
1847
1848
1849
1850
1851
1852


1853
1854
1855

1856
1857
1858

1859
1860
1861
1862
1863
1864
1865
1866






1867
1868
1869

















































































1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904





1905
1906
1907
1908


1909
1910


1911
1912
1913
1914
1915
1916
1917


1918
1919
1920
1921

1922
1923
1924
1925
1926
1927
1928




1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951


1952
1953
1954






1955
1956
1957
1958








1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984































1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011



2012
2013
2014
2015
2016

2017
2018


2019

2020
2021
2022


2023




2024
2025

2026
2027




2028
2029

























2030
2031
2032
2033
2034
2035
2036

2037
2038
2039
2040
2041
2042
2043
2044
2045

2046
2047
2048








































2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060

2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078



2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
** Clicking on an email takes one to the /alerts page
** for that email where the delivery settings can be
** modified.
*/
void subscriber_list_page(void){
  Blob sql;
  Stmt q;
  double rNow;
  if( email_webpages_disabled() ) return;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  email_submenu_common();
  style_header("Subscriber List");
  blob_init(&sql, 0, 0);
  blob_append_sql(&sql,
    "SELECT hex(subscriberCode),"          /* 0 */
    "       semail,"                       /* 1 */
    "       ssub,"                         /* 2 */
    "       suname,"                       /* 3 */
    "       sverified,"                    /* 4 */
    "       sdigest,"                      /* 5 */

    "       date(sctime,'unixepoch'),"     /* 6 */
    "       julianday(mtime,'unixepoch')"  /* 7 */
    " FROM subscriber"
  );
  if( P("only")!=0 ){
    blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
    style_submenu_element("Show All","%R/subscribers");
  }

  db_prepare_blob(&q, &sql);
  rNow = db_double(0.0,"SELECT julianday('now')");
  @ <table border="1">


  @ <tr>
  @ <th>Email
  @ <th>Events
  @ <th>Digest-Only?
  @ <th>User
  @ <th>Verified?
  @ <th>Last change
  @ <th>Created
  @ </tr>

  while( db_step(&q)==SQLITE_ROW ){

    double rAge = rNow - db_column_double(&q, 7);
    @ <tr>
    @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
    @ %h(db_column_text(&q,1))</a></td>
    @ <td>%h(db_column_text(&q,2))</td>
    @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
    @ <td>%h(db_column_text(&q,3))</td>
    @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
    @ <td>%z(human_readable_age(rAge))</td>
    @ <td>%h(db_column_text(&q,6))</td>
    @ </tr>
  }
  @ </table>
  db_finalize(&q);

  style_footer();
}

#if LOCAL_INTERFACE
/*
** A single event that might appear in an alert is recorded as an
** instance of the following object.
*/
struct EmailEvent {
  int type;          /* 'c', 't', 'w', etc. */


  Blob txt;          /* Text description to appear in an alert */

  EmailEvent *pNext; /* Next in chronological order */
};
#endif

/*
** Free a linked list of EmailEvent objects
*/
void email_free_eventlist(EmailEvent *p){
  while( p ){
    EmailEvent *pNext = p->pNext;
    blob_reset(&p->txt);


    fossil_free(p);
    p = pNext;
  }
}

/*
** Compute and return a linked list of EmailEvent objects
** corresponding to the current content of the temp.wantalert
** table which should be defined as follows:
**
**     CREATE TEMP TABLE wantalert(eventId TEXT);
*/
EmailEvent *email_compute_event_text(int *pnEvent){
  Stmt q;
  EmailEvent *p;
  EmailEvent anchor;
  EmailEvent *pLast;
  const char *zUrl = db_get("email-url","http://localhost:8080");





  db_prepare(&q,
    "SELECT"
    " blob.uuid,"  /* 0 */
    " datetime(event.mtime),"  /* 1 */
    " coalesce(ecomment,comment)"
    "  || ' (user: ' || coalesce(euser,user,'?')"
    "  || (SELECT case when length(x)>0 then ' tags: ' || x else '' end"
    "      FROM (SELECT group_concat(substr(tagname,5), ', ') AS x"
    "              FROM tag, tagxref"
    "             WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
    "               AND tagxref.rid=blob.rid AND tagxref.tagtype>0))"
    "  || ')' as comment,"  /* 2 */
    " tagxref.value AS branch,"  /* 3 */
    " wantalert.eventId"     /* 4 */

    " FROM temp.wantalert JOIN tag CROSS JOIN event CROSS JOIN blob"
    "  LEFT JOIN tagxref ON tagxref.tagid=tag.tagid"
    "                       AND tagxref.tagtype>0"
    "                       AND tagxref.rid=blob.rid"
    " WHERE blob.rid=event.objid"
    "   AND tag.tagname='branch'"
    "   AND event.objid=substr(wantalert.eventId,2)+0"

    " ORDER BY event.mtime"

  );
  memset(&anchor, 0, sizeof(anchor));
  pLast = &anchor;
  *pnEvent = 0;
  while( db_step(&q)==SQLITE_ROW ){
    const char *zType = "";
    p = fossil_malloc( sizeof(EmailEvent) );
    pLast->pNext = p;
    pLast = p;
    p->type = db_column_text(&q, 4)[0];


    p->pNext = 0;
    switch( p->type ){
      case 'c':  zType = "Check-In";        break;

      case 't':  zType = "Wiki Edit";       break;
      case 'w':  zType = "Ticket Change";   break;
    }

    blob_init(&p->txt, 0, 0);
    blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
      db_column_text(&q,1),
      zType,
      db_column_text(&q,2),
      zUrl,
      db_column_text(&q,0)
    );






    (*pnEvent)++;
  }
  db_finalize(&q);

















































































  return anchor.pNext;
}

/*
** Put a header on an alert email
*/
void email_header(Blob *pOut){
  blob_appendf(pOut,
    "This is an automated email reporting changes "
    "on Fossil repository %s (%s/timeline)\n",
    db_get("email-subname","(unknown)"),
    db_get("email-url","http://localhost:8080"));
}

/*
** Append the "unsubscribe" notification and other footer text to
** the end of an email alert being assemblied in pOut.
*/
void email_footer(Blob *pOut){
  blob_appendf(pOut, "\n-- \nTo unsubscribe: %s/unsubscribe\n",
     db_get("email-url","http://localhost:8080"));
}

/*
** COMMAND:  test-alert
**
** Usage: %fossil test-alert EVENTID ...
**
** Generate the text of an email alert for all of the EVENTIDs
** listed on the command-line.  Or if no events are listed on the
** command line, generate text for all events named in the
** pending_alert table.
**
** This command is intended for testing and debugging the logic
** that generates email alert text.





*/
void test_alert_cmd(void){
  Blob out;
  int nEvent;


  EmailEvent *pEvent, *p;



  db_find_and_open_repository(0, 0);
  verify_all_options();
  db_begin_transaction();
  email_schema(0);
  db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT)");
  if( g.argc==2 ){
    db_multi_exec("INSERT INTO wantalert SELECT eventid FROM pending_alert");


  }else{
    int i;
    for(i=2; i<g.argc; i++){
      db_multi_exec("INSERT INTO wantalert VALUES(%Q)", g.argv[i]);

    }
  }
  blob_init(&out, 0, 0);
  email_header(&out);
  pEvent = email_compute_event_text(&nEvent);
  for(p=pEvent; p; p=p->pNext){
    blob_append(&out, "\n", 1);




    blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt));
  }
  email_free_eventlist(pEvent);
  email_footer(&out);
  fossil_print("%s", blob_str(&out));
  blob_reset(&out);
  db_end_transaction(0);
}

/*
** COMMAND:  test-add-alerts
**
** Usage: %fossil test-add-alerts [--backoffice] EVENTID ...
**
** Add one or more events to the pending_alert queue.  Use this
** command during testing to force email notifications for specific
** events.
**
** EVENTIDs are text.  The first character is 'c', 'w', or 't'
** for check-in, wiki, or ticket.  The remaining text is a
** integer that references the EVENT.OBJID value for the event.
** Run /timeline?showid to see these OBJID values.
**


** If the --backoffice option is included, then email_backoffice() is run
** after all alerts have been added.  This will cause the alerts to
** be sent out with the SENDALERT_TRACE option.






*/
void test_add_alert_cmd(void){
  int i;
  int doAuto = find_option("backoffice",0,0)!=0;








  db_find_and_open_repository(0, 0);
  verify_all_options();
  db_begin_write();
  email_schema(0);
  for(i=2; i<g.argc; i++){
    db_multi_exec("REPLACE INTO pending_alert(eventId) VALUES(%Q)", g.argv[i]);
  }
  db_end_transaction(0);
  if( doAuto ){
    email_backoffice(SENDALERT_TRACE);
  }
}

#if INTERFACE
/*
** Flags for email_send_alerts()
*/
#define SENDALERT_DIGEST      0x0001    /* Send a digest */
#define SENDALERT_PRESERVE    0x0002    /* Do not mark the task as done */
#define SENDALERT_STDOUT      0x0004    /* Print emails instead of sending */
#define SENDALERT_TRACE       0x0008    /* Trace operation for debugging */

#endif /* INTERFACE */

/*
** Send alert emails to all subscribers.































*/
void email_send_alerts(u32 flags){
  EmailEvent *pEvents, *p;
  int nEvent = 0;
  Stmt q;
  const char *zDigest = "false";
  Blob hdr, body;
  const char *zUrl;
  const char *zRepoName;
  const char *zFrom;
  const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0;
  EmailSender *pSender = 0;
  u32 senderFlags = 0;

  if( g.fSqlTrace ) fossil_trace("-- BEGIN email_send_alerts(%u)\n", flags);
  db_begin_transaction();
  if( !email_enabled() ) goto send_alerts_done;
  zUrl = db_get("email-url",0);
  if( zUrl==0 ) goto send_alerts_done;
  zRepoName = db_get("email-subname",0);
  if( zRepoName==0 ) goto send_alerts_done;
  zFrom = db_get("email-self",0);
  if( zFrom==0 ) goto send_alerts_done;
  if( flags & SENDALERT_TRACE ){
    senderFlags |= EMAIL_TRACE;
  }
  pSender = email_sender_new(zDest, senderFlags);



  db_multi_exec(
    "DROP TABLE IF EXISTS temp.wantalert;"
    "CREATE TEMP TABLE wantalert(eventId TEXT);"
  );
  if( flags & SENDALERT_DIGEST ){

    db_multi_exec(
      "INSERT INTO wantalert SELECT eventid FROM pending_alert"


      "  WHERE sentDigest IS FALSE"

    );
    zDigest = "true";
  }else{


    db_multi_exec(




      "INSERT INTO wantalert SELECT eventid FROM pending_alert"
      "  WHERE sentSep IS FALSE"

    );
  }




  pEvents = email_compute_event_text(&nEvent);
  if( nEvent==0 ) goto send_alerts_done;

























  blob_init(&hdr, 0, 0);
  blob_init(&body, 0, 0);
  db_prepare(&q,
     "SELECT"
     " hex(subscriberCode),"  /* 0 */
     " semail,"               /* 1 */
     " ssub"                  /* 2 */

     " FROM subscriber"
     " WHERE sverified AND NOT sdonotcall"
     "  AND sdigest IS %s",
     zDigest/*safe-for-%s*/
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zCode = db_column_text(&q, 0);
    const char *zSub = db_column_text(&q, 2);
    const char *zEmail = db_column_text(&q, 1);

    int nHit = 0;
    for(p=pEvents; p; p=p->pNext){
      if( strchr(zSub,p->type)==0 ) continue;








































      if( nHit==0 ){
        blob_appendf(&hdr,"To: <%s>\r\n", zEmail);
        blob_appendf(&hdr,"Subject: %s activity alert\r\n", zRepoName);
        blob_appendf(&body,
          "This is an automated email sent by the Fossil repository "
          "at %s to report changes.\n",
          zUrl
        );
      }
      nHit++;
      blob_append(&body, "\n", 1);
      blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));

    }
    if( nHit==0 ) continue;
    blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
         zUrl, zCode);
    email_send(pSender,&hdr,&body);
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_reset(&hdr);
  blob_reset(&body);
  db_finalize(&q);
  email_free_eventlist(pEvents);
  if( (flags & SENDALERT_PRESERVE)==0 ){
    if( flags & SENDALERT_DIGEST ){
      db_multi_exec("UPDATE pending_alert SET sentDigest=true");
    }else{
      db_multi_exec("UPDATE pending_alert SET sentSep=true");
    }



    db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep");
  }
send_alerts_done:
  email_sender_free(pSender);
  if( g.fSqlTrace ) fossil_trace("-- END email_send_alerts(%u)\n", flags);
  db_end_transaction(0);
}

/*
** Do backoffice processing for email notifications.  In other words,
** check to see if any email notifications need to occur, and then
** do them.
**
** This routine is intended to run in the background, after webpages.
**
** The mFlags option is zero or more of the SENDALERT_* flags.  Normally
** this flag is zero, but the test-set-alert command sets it to
** SENDALERT_TRACE.
*/
void email_backoffice(u32 mFlags){
  int iJulianDay;
  if( !email_tables_exist() ) return;
  email_send_alerts(mFlags);
  iJulianDay = db_int(0, "SELECT julianday('now')");
  if( iJulianDay>db_get_int("email-last-digest",0) ){
    db_set_int("email-last-digest",iJulianDay,0);
    email_send_alerts(SENDALERT_DIGEST|mFlags);
  }
}

/*
** WEBPAGE: contact_admin
**
** A web-form to send an email message to the repository administrator,







|
|





|









>
|
<






>

|
|
>
>









>

>
|







|
|


|

>









|
>
>

>







|



>
>










|

|





>
>

>
>


|
|







|
<
|
>
|
<
<
<

<

>
|
>









|
>
>



>



>








>
>
>
>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


















|
















>
>
>
>
>




>
>


>
>



|
|

|
>
>



|
>




|


>
>
>
>


|
|








|





|
|



>
>
|
|
|
>
>
>
>
>
>




>
>
>
>
>
>
>
>



|





|





|









|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|









|


|
|
|

|

|

|

|

|
>
>
>


|


>

|
>
>

>



>
>

>
>
>
>
|
|
>


>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|
>
|








>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
>




|






|
<
<
<
<
<
|
>
>
>
|
|
|
|
|
<













|

|
|



|







1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914

1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015

2016
2017
2018



2019

2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493





2494
2495
2496
2497
2498
2499
2500
2501
2502

2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
** Clicking on an email takes one to the /alerts page
** for that email where the delivery settings can be
** modified.
*/
void subscriber_list_page(void){
  Blob sql;
  Stmt q;
  sqlite3_int64 iNow;
  if( alert_webpages_disabled() ) return;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  alert_submenu_common();
  style_header("Subscriber List");
  blob_init(&sql, 0, 0);
  blob_append_sql(&sql,
    "SELECT hex(subscriberCode),"          /* 0 */
    "       semail,"                       /* 1 */
    "       ssub,"                         /* 2 */
    "       suname,"                       /* 3 */
    "       sverified,"                    /* 4 */
    "       sdigest,"                      /* 5 */
    "       mtime,"                        /* 6 */
    "       date(sctime,'unixepoch')"      /* 7 */

    " FROM subscriber"
  );
  if( P("only")!=0 ){
    blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
    style_submenu_element("Show All","%R/subscribers");
  }
  blob_append_sql(&sql," ORDER BY mtime DESC");
  db_prepare_blob(&q, &sql);
  iNow = time(0);
  @ <table border='1' class='sortable' \
  @ data-init-sort='6' data-column-types='tttttKt'>
  @ <thead>
  @ <tr>
  @ <th>Email
  @ <th>Events
  @ <th>Digest-Only?
  @ <th>User
  @ <th>Verified?
  @ <th>Last change
  @ <th>Created
  @ </tr>
  @ </thead><tbody>
  while( db_step(&q)==SQLITE_ROW ){
    sqlite3_int64 iMtime = db_column_int64(&q, 6);
    double rAge = (iNow - iMtime)/86400.0;
    @ <tr>
    @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
    @ %h(db_column_text(&q,1))</a></td>
    @ <td>%h(db_column_text(&q,2))</td>
    @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
    @ <td>%h(db_column_text(&q,3))</td>
    @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
    @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
    @ <td>%h(db_column_text(&q,7))</td>
    @ </tr>
  }
  @ </tbody></table>
  db_finalize(&q);
  style_table_sorter();
  style_footer();
}

#if LOCAL_INTERFACE
/*
** A single event that might appear in an alert is recorded as an
** instance of the following object.
*/
struct EmailEvent {
  int type;          /* 'c', 'f', 'm', 't', 'w' */
  int needMod;       /* Pending moderator approval */
  Blob hdr;          /* Header content, for forum entries */
  Blob txt;          /* Text description to appear in an alert */
  char *zFromName;   /* Human name of the sender */
  EmailEvent *pNext; /* Next in chronological order */
};
#endif

/*
** Free a linked list of EmailEvent objects
*/
void alert_free_eventlist(EmailEvent *p){
  while( p ){
    EmailEvent *pNext = p->pNext;
    blob_reset(&p->txt);
    blob_reset(&p->hdr);
    fossil_free(p->zFromName);
    fossil_free(p);
    p = pNext;
  }
}

/*
** Compute and return a linked list of EmailEvent objects
** corresponding to the current content of the temp.wantalert
** table which should be defined as follows:
**
**     CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN);
*/
EmailEvent *alert_compute_event_text(int *pnEvent, int doDigest){
  Stmt q;
  EmailEvent *p;
  EmailEvent anchor;
  EmailEvent *pLast;
  const char *zUrl = db_get("email-url","http://localhost:8080");
  const char *zFrom;
  const char *zSub;


  /* First do non-forum post events */
  db_prepare(&q,
    "SELECT"
    " blob.uuid,"                /* 0 */
    " datetime(event.mtime),"    /* 1 */
    " coalesce(ecomment,comment)"
    "  || ' (user: ' || coalesce(euser,user,'?')"
    "  || (SELECT case when length(x)>0 then ' tags: ' || x else '' end"
    "      FROM (SELECT group_concat(substr(tagname,5), ', ') AS x"
    "              FROM tag, tagxref"
    "             WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
    "               AND tagxref.rid=blob.rid AND tagxref.tagtype>0))"
    "  || ')' as comment,"       /* 2 */

    " wantalert.eventId,"        /* 3 */
    " wantalert.needMod"         /* 4 */
    " FROM temp.wantalert, event, blob"



    " WHERE blob.rid=event.objid"

    "   AND event.objid=substr(wantalert.eventId,2)+0"
    "   AND (%d OR eventId NOT GLOB 'f*')"
    " ORDER BY event.mtime",
    doDigest
  );
  memset(&anchor, 0, sizeof(anchor));
  pLast = &anchor;
  *pnEvent = 0;
  while( db_step(&q)==SQLITE_ROW ){
    const char *zType = "";
    p = fossil_malloc( sizeof(EmailEvent) );
    pLast->pNext = p;
    pLast = p;
    p->type = db_column_text(&q, 3)[0];
    p->needMod = db_column_int(&q, 4);
    p->zFromName = 0;
    p->pNext = 0;
    switch( p->type ){
      case 'c':  zType = "Check-In";        break;
      case 'f':  zType = "Forum post";      break;
      case 't':  zType = "Wiki Edit";       break;
      case 'w':  zType = "Ticket Change";   break;
    }
    blob_init(&p->hdr, 0, 0);
    blob_init(&p->txt, 0, 0);
    blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
      db_column_text(&q,1),
      zType,
      db_column_text(&q,2),
      zUrl,
      db_column_text(&q,0)
    );
    if( p->needMod ){
      blob_appendf(&p->txt,
        "** Pending moderator approval (%s/modreq) **\n",
        zUrl
      );
    }
    (*pnEvent)++;
  }
  db_finalize(&q);

  /* Early-out if forumpost is not a table in this repository */
  if( !db_table_exists("repository","forumpost") ){
    return anchor.pNext;
  }

  /* For digests, the previous loop also handled forumposts already */
  if( doDigest ){
    return anchor.pNext;
  }

  /* If we reach this point, it means that forumposts exist and this
  ** is a normal email alert.  Construct full-text forum post alerts
  ** using a format that enables them to be sent as separate emails.
  */
  db_prepare(&q,
    "SELECT"
    " forumpost.fpid,"                                      /* 0 */
    " (SELECT uuid FROM blob WHERE rid=forumpost.fpid),"    /* 1 */
    " datetime(event.mtime),"                               /* 2 */
    " substr(comment,instr(comment,':')+2),"                /* 3 */
    " (SELECT uuid FROM blob WHERE rid=forumpost.firt),"    /* 4 */
    " wantalert.needMod,"                                   /* 5 */
    " coalesce(trim(substr(info,1,instr(info,'<')-1)),euser,user)"   /* 6 */
    " FROM temp.wantalert, event, forumpost"
    "      LEFT JOIN user ON (login=coalesce(euser,user))"
    " WHERE event.objid=substr(wantalert.eventId,2)+0"
    "   AND eventId GLOB 'f*'"
    "   AND forumpost.fpid=event.objid"
    " ORDER BY event.mtime"
  );
  zFrom = db_get("email-self",0);
  zSub = db_get("email-subname","");
  while( db_step(&q)==SQLITE_ROW ){
    Manifest *pPost = manifest_get(db_column_int(&q,0), CFTYPE_FORUM, 0);
    const char *zIrt;
    const char *zUuid;
    const char *zTitle;
    const char *z;
    if( pPost==0 ) continue;
    p = fossil_malloc( sizeof(EmailEvent) );
    pLast->pNext = p;
    pLast = p;
    p->type = 'f';
    p->needMod = db_column_int(&q, 5);
    z = db_column_text(&q,6);
    p->zFromName = z && z[0] ? fossil_strdup(z) : 0;
    p->pNext = 0;
    blob_init(&p->hdr, 0, 0);
    zUuid = db_column_text(&q, 1);
    zTitle = db_column_text(&q, 3);
    if( p->needMod ){
      blob_appendf(&p->hdr, "Subject: %s Pending Moderation: %s\r\n",
                   zSub, zTitle);
    }else{
      blob_appendf(&p->hdr, "Subject: %s %s\r\n", zSub, zTitle);
      blob_appendf(&p->hdr, "Message-Id: <%.32s@%s>\r\n", 
                   zUuid, alert_hostname(zFrom));
      zIrt = db_column_text(&q, 4);
      if( zIrt && zIrt[0] ){
        blob_appendf(&p->hdr, "In-Reply-To: <%.32s@%s>\r\n",
                     zIrt, alert_hostname(zFrom));
      }
    }
    blob_init(&p->txt, 0, 0);
    if( p->needMod ){
      blob_appendf(&p->txt,
        "** Pending moderator approval (%s/modreq) **\n",
        zUrl
      );
    }
    blob_appendf(&p->txt,
      "Forum post by %s on %s\n",
      pPost->zUser, db_column_text(&q, 2));
    blob_appendf(&p->txt, "%s/forumpost/%S\n\n", zUrl, zUuid);
    blob_append(&p->txt, pPost->zWiki, -1);
    manifest_destroy(pPost);
    (*pnEvent)++;
  }
  db_finalize(&q);

  return anchor.pNext;
}

/*
** Put a header on an alert email
*/
void email_header(Blob *pOut){
  blob_appendf(pOut,
    "This is an automated email reporting changes "
    "on Fossil repository %s (%s/timeline)\n",
    db_get("email-subname","(unknown)"),
    db_get("email-url","http://localhost:8080"));
}

/*
** Append the "unsubscribe" notification and other footer text to
** the end of an email alert being assemblied in pOut.
*/
void alert_footer(Blob *pOut){
  blob_appendf(pOut, "\n-- \nTo unsubscribe: %s/unsubscribe\n",
     db_get("email-url","http://localhost:8080"));
}

/*
** COMMAND:  test-alert
**
** Usage: %fossil test-alert EVENTID ...
**
** Generate the text of an email alert for all of the EVENTIDs
** listed on the command-line.  Or if no events are listed on the
** command line, generate text for all events named in the
** pending_alert table.
**
** This command is intended for testing and debugging the logic
** that generates email alert text.
**
** Options:
**
**      --digest           Generate digest alert text
**      --needmod          Assume all events are pending moderator approval
*/
void test_alert_cmd(void){
  Blob out;
  int nEvent;
  int needMod;
  int doDigest;
  EmailEvent *pEvent, *p;

  doDigest = find_option("digest",0,0)!=0;
  needMod = find_option("needmod",0,0)!=0;
  db_find_and_open_repository(0, 0);
  verify_all_options();
  db_begin_transaction();
  alert_schema(0);
  db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT, needMod BOOLEAN)");
  if( g.argc==2 ){
    db_multi_exec(
      "INSERT INTO wantalert(eventId,needMod)"
      " SELECT eventid, %d FROM pending_alert", needMod);
  }else{
    int i;
    for(i=2; i<g.argc; i++){
      db_multi_exec("INSERT INTO wantalert(eventId,needMod) VALUES(%Q,%d)",
           g.argv[i], needMod);
    }
  }
  blob_init(&out, 0, 0);
  email_header(&out);
  pEvent = alert_compute_event_text(&nEvent, doDigest);
  for(p=pEvent; p; p=p->pNext){
    blob_append(&out, "\n", 1);
    if( blob_size(&p->hdr) ){
      blob_append(&out, blob_buffer(&p->hdr), blob_size(&p->hdr));
      blob_append(&out, "\n", 1);
    }
    blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt));
  }
  alert_free_eventlist(pEvent);
  alert_footer(&out);
  fossil_print("%s", blob_str(&out));
  blob_reset(&out);
  db_end_transaction(0);
}

/*
** COMMAND:  test-add-alerts
**
** Usage: %fossil test-add-alerts [OPTIONS] EVENTID ...
**
** Add one or more events to the pending_alert queue.  Use this
** command during testing to force email notifications for specific
** events.
**
** EVENTIDs are text.  The first character is 'c', 'f', 't', or 'w'
** for check-in, forum, ticket, or wiki.  The remaining text is a
** integer that references the EVENT.OBJID value for the event.
** Run /timeline?showid to see these OBJID values.
**
** Options:
**
**    --backoffice        Run alert_backoffice() after all alerts have
**                        been added.  This will cause the alerts to be
**                        sent out with the SENDALERT_TRACE option.
**
**    --debug             Like --backoffice, but add the SENDALERT_STDOUT
**                        so that emails are printed to standard output
**                        rather than being sent.
**
**    --digest            Process emails using SENDALERT_DIGEST
*/
void test_add_alert_cmd(void){
  int i;
  int doAuto = find_option("backoffice",0,0)!=0;
  unsigned mFlags = 0;
  if( find_option("debug",0,0)!=0 ){
    doAuto = 1;
    mFlags = SENDALERT_STDOUT;
  }
  if( find_option("digest",0,0)!=0 ){
    mFlags |= SENDALERT_DIGEST;
  }
  db_find_and_open_repository(0, 0);
  verify_all_options();
  db_begin_write();
  alert_schema(0);
  for(i=2; i<g.argc; i++){
    db_multi_exec("REPLACE INTO pending_alert(eventId) VALUES(%Q)", g.argv[i]);
  }
  db_end_transaction(0);
  if( doAuto ){
    alert_backoffice(SENDALERT_TRACE|mFlags);
  }
}

#if INTERFACE
/*
** Flags for alert_send_alerts()
*/
#define SENDALERT_DIGEST      0x0001    /* Send a digest */
#define SENDALERT_PRESERVE    0x0002    /* Do not mark the task as done */
#define SENDALERT_STDOUT      0x0004    /* Print emails instead of sending */
#define SENDALERT_TRACE       0x0008    /* Trace operation for debugging */

#endif /* INTERFACE */

/*
** Send alert emails to subscribers.
**
** This procedure is run by either the backoffice, or in response to the
** "fossil alerts send" command.  Details of operation are controlled by
** the flags parameter.
**
** Here is a summary of what happens:
**
**   (1) Create a TEMP table wantalert(eventId,needMod) and fill it with
**       all the events that we want to send alerts about.  The needMod
**       flags is set if and only if the event is still awaiting
**       moderator approval.  Events with the needMod flag are only
**       shown to users that have moderator privileges.
**
**   (2) Call alert_compute_event_text() to compute a list of EmailEvent
**       objects that describe all events about which we want to send
**       alerts.
**
**   (3) Loop over all subscribers.  Compose and send one or more email
**       messages to each subscriber that describe the events for
**       which the subscriber has expressed interest and has
**       appropriate privileges.
**
**   (4) Update the pending_alerts table to indicate that alerts have been
**       sent.
**
** Update 2018-08-09:  Do step (3) before step (4).  Update the
** pending_alerts table *before* the emails are sent.  That way, if
** the process malfunctions or crashes, some notifications may never
** be sent.  But that is better than some recurring bug causing
** subscribers to be flooded with repeated notifications every 60
** seconds!
*/
void alert_send_alerts(u32 flags){
  EmailEvent *pEvents, *p;
  int nEvent = 0;
  Stmt q;
  const char *zDigest = "false";
  Blob hdr, body;
  const char *zUrl;
  const char *zRepoName;
  const char *zFrom;
  const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0;
  AlertSender *pSender = 0;
  u32 senderFlags = 0;

  if( g.fSqlTrace ) fossil_trace("-- BEGIN alert_send_alerts(%u)\n", flags);
  alert_schema(0);
  if( !alert_enabled() ) goto send_alert_done;
  zUrl = db_get("email-url",0);
  if( zUrl==0 ) goto send_alert_done;
  zRepoName = db_get("email-subname",0);
  if( zRepoName==0 ) goto send_alert_done;
  zFrom = db_get("email-self",0);
  if( zFrom==0 ) goto send_alert_done;
  if( flags & SENDALERT_TRACE ){
    senderFlags |= ALERT_TRACE;
  }
  pSender = alert_sender_new(zDest, senderFlags);

  /* Step (1):  Compute the alerts that need sending
  */
  db_multi_exec(
    "DROP TABLE IF EXISTS temp.wantalert;"
    "CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN, sentMod);"
  );
  if( flags & SENDALERT_DIGEST ){
    /* Unmoderated changes are never sent as part of a digest */
    db_multi_exec(
      "INSERT INTO wantalert(eventId,needMod)"
      " SELECT eventid, 0"
      "   FROM pending_alert"
      "  WHERE sentDigest IS FALSE"
      "    AND NOT EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2));"
    );
    zDigest = "true";
  }else{
    /* Immediate alerts might include events that are subject to
    ** moderator approval */
    db_multi_exec(
      "INSERT INTO wantalert(eventId,needMod,sentMod)"
      " SELECT eventid,"
      "        EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2)),"
      "        sentMod"
      "   FROM pending_alert"
      "  WHERE sentSep IS FALSE;"
      "DELETE FROM wantalert WHERE needMod AND sentMod;"
    );
  }

  /* Step 2: compute EmailEvent objects for every notification that
  ** needs sending.
  */
  pEvents = alert_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0);
  if( nEvent==0 ) goto send_alert_done;

  /* Step 4a: Update the pending_alerts table to designate the
  ** alerts as having all been sent.  This is done *before* step (3)
  ** so that a crash will not cause alerts to be sent multiple times.
  ** Better a missed alert than being spammed with hundreds of alerts
  ** due to a bug.
  */
  if( (flags & SENDALERT_PRESERVE)==0 ){
    if( flags & SENDALERT_DIGEST ){
      db_multi_exec(
        "UPDATE pending_alert SET sentDigest=true"
        " WHERE eventid IN (SELECT eventid FROM wantalert);"
      );
    }else{
      db_multi_exec(
        "UPDATE pending_alert SET sentSep=true"
        " WHERE eventid IN (SELECT eventid FROM wantalert WHERE NOT needMod);"
        "UPDATE pending_alert SET sentMod=true"
        " WHERE eventid IN (SELECT eventid FROM wantalert WHERE needMod);"
      );
    }
  }

  /* Step 3: Loop over subscribers.  Send alerts
  */
  blob_init(&hdr, 0, 0);
  blob_init(&body, 0, 0);
  db_prepare(&q,
     "SELECT"
     " hex(subscriberCode),"  /* 0 */
     " semail,"               /* 1 */
     " ssub,"                 /* 2 */
     " fullcap(user.cap)"     /* 3 */
     " FROM subscriber LEFT JOIN user ON (login=suname)"
     " WHERE sverified AND NOT sdonotcall"
     "  AND sdigest IS %s",
     zDigest/*safe-for-%s*/
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zCode = db_column_text(&q, 0);
    const char *zSub = db_column_text(&q, 2);
    const char *zEmail = db_column_text(&q, 1);
    const char *zCap = db_column_text(&q, 3);
    int nHit = 0;
    for(p=pEvents; p; p=p->pNext){
      if( strchr(zSub,p->type)==0 ) continue;
      if( p->needMod ){
        /* For events that require moderator approval, only send an alert
        ** if the recipient is a moderator for that type of event */
        char xType = '*';
        switch( p->type ){
          case 'f':  xType = '5';  break;
          case 't':  xType = 'q';  break;
          case 'w':  xType = 'l';  break;
        }
        if( strchr(zCap,xType)==0 ) continue;
      }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
        /* Setup and admin users can get any notification that does not
        ** require moderation */
      }else{
        /* Other users only see the alert if they have sufficient
        ** privilege to view the event itself */
        char xType = '*';
        switch( p->type ){
          case 'c':  xType = 'o';  break;
          case 'f':  xType = '2';  break;
          case 't':  xType = 'r';  break;
          case 'w':  xType = 'j';  break;
        }
        if( strchr(zCap,xType)==0 ) continue;
      }
      if( blob_size(&p->hdr)>0 ){
        /* This alert should be sent as a separate email */
        Blob fhdr, fbody;
        blob_init(&fhdr, 0, 0);
        blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
        blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
        blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
        blob_appendf(&fbody, "\n-- \nSubscription info: %s/alerts/%s\n",
           zUrl, zCode);
        alert_send(pSender,&fhdr,&fbody,p->zFromName);
        blob_reset(&fhdr);
        blob_reset(&fbody);
      }else{
        /* Events other than forum posts are gathered together into
        ** a single email message */
        if( nHit==0 ){
          blob_appendf(&hdr,"To: <%s>\r\n", zEmail);
          blob_appendf(&hdr,"Subject: %s activity alert\r\n", zRepoName);
          blob_appendf(&body,
            "This is an automated email sent by the Fossil repository "
            "at %s to report changes.\n",
            zUrl
          );
        }
        nHit++;
        blob_append(&body, "\n", 1);
        blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
      }
    }
    if( nHit==0 ) continue;
    blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
         zUrl, zCode);
    alert_send(pSender,&hdr,&body,0);
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_reset(&hdr);
  blob_reset(&body);
  db_finalize(&q);
  alert_free_eventlist(pEvents);






  /* Step 4b: Update the pending_alerts table to remove all of the
  ** alerts that have been completely sent.
  */
  db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep;");

send_alert_done:
  alert_sender_free(pSender);
  if( g.fSqlTrace ) fossil_trace("-- END alert_send_alerts(%u)\n", flags);

}

/*
** Do backoffice processing for email notifications.  In other words,
** check to see if any email notifications need to occur, and then
** do them.
**
** This routine is intended to run in the background, after webpages.
**
** The mFlags option is zero or more of the SENDALERT_* flags.  Normally
** this flag is zero, but the test-set-alert command sets it to
** SENDALERT_TRACE.
*/
void alert_backoffice(u32 mFlags){
  int iJulianDay;
  if( !alert_tables_exist() ) return;
  alert_send_alerts(mFlags);
  iJulianDay = db_int(0, "SELECT julianday('now')");
  if( iJulianDay>db_get_int("email-last-digest",0) ){
    db_set_int("email-last-digest",iJulianDay,0);
    alert_send_alerts(SENDALERT_DIGEST|mFlags);
  }
}

/*
** WEBPAGE: contact_admin
**
** A web-form to send an email message to the repository administrator,
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
   && P("subject")!=0
   && P("msg")!=0
   && P("from")!=0
   && cgi_csrf_safe(1)
   && captcha_is_correct(0)
  ){
    Blob hdr, body;
    EmailSender *pSender = email_sender_new(0,0);
    blob_init(&hdr, 0, 0);
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s administrator message\r\n",
                 zAdminEmail, db_get("email-subname","Fossil Repo"));
    blob_init(&body, 0, 0);
    blob_appendf(&body, "Message from [%s]\n", PT("from")/*safe-for-%s*/);
    blob_appendf(&body, "Subject: [%s]\n\n", PT("subject")/*safe-for-%s*/);
    blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
    email_send(pSender, &hdr, &body);
    style_header("Message Sent");
    if( pSender->zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was reported by the system:
      @ <blockquote><pre>
      @ %h(pSender->zErr)
      @ </pre></blockquote>
    }else{
      @ <p>Your message has been sent to the repository administrator.
      @ Thank you for your input.</p>
    }
    email_sender_free(pSender);
    style_footer();
    return;
  }
  if( captcha_needed() ){
    uSeed = captcha_seed();
    zDecoded = captcha_decode(uSeed);
    zCaptcha = captcha_render(zDecoded);







|







|











|







2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
   && P("subject")!=0
   && P("msg")!=0
   && P("from")!=0
   && cgi_csrf_safe(1)
   && captcha_is_correct(0)
  ){
    Blob hdr, body;
    AlertSender *pSender = alert_sender_new(0,0);
    blob_init(&hdr, 0, 0);
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s administrator message\r\n",
                 zAdminEmail, db_get("email-subname","Fossil Repo"));
    blob_init(&body, 0, 0);
    blob_appendf(&body, "Message from [%s]\n", PT("from")/*safe-for-%s*/);
    blob_appendf(&body, "Subject: [%s]\n\n", PT("subject")/*safe-for-%s*/);
    blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
    alert_send(pSender, &hdr, &body, 0);
    style_header("Message Sent");
    if( pSender->zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was reported by the system:
      @ <blockquote><pre>
      @ %h(pSender->zErr)
      @ </pre></blockquote>
    }else{
      @ <p>Your message has been sent to the repository administrator.
      @ Thank you for your input.</p>
    }
    alert_sender_free(pSender);
    style_footer();
    return;
  }
  if( captcha_needed() ){
    uSeed = captcha_seed();
    zDecoded = captcha_decode(uSeed);
    zCaptcha = captcha_render(zDecoded);
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
  @ </tr>
  @ <tr>
  @   <td></td>
  @   <td><input type="submit" name="submit" value="Send Message">
  @ </tr>
  @ </table>
  if( zCaptcha ){
    @ <div class="captcha"><table class="captcha"><tr><td><pre>
    @ %h(zCaptcha)
    @ </pre>
    @ Enter the 8 characters above in the "Security Code" box
    @ </td></tr></table></div>
  }
  @ </form>
  style_footer();
}

/*
** Send an annoucement message described by query parameter.
** Permission to do this has already been verified.
*/
static char *email_send_announcement(void){
  EmailSender *pSender;
  char *zErr;
  const char *zTo = PT("to");
  char *zSubject = PT("subject");
  int bAll = PB("all");
  int bAA = PB("aa");
  const char *zSub = db_get("email-subname", "[Fossil Repo]");
  int bTest2 = fossil_strcmp(P("name"),"test2")==0;
  Blob hdr, body;
  blob_init(&body, 0, 0);
  blob_init(&hdr, 0, 0);
  blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
  pSender = email_sender_new(bTest2 ? "blob" : 0, 0);
  if( zTo[0] ){
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
    email_send(pSender, &hdr, &body);
  }
  if( bAll || bAA ){
    Stmt q;
    int nUsed = blob_size(&body);
    const char *zURL =  db_get("email-url",0);
    db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
                   " WHERE sverified AND NOT sdonotcall %s",
                   bAll ? "" : " AND ssub LIKE '%a%'");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zCode = db_column_text(&q, 1);
      zTo = db_column_text(&q, 0);
      blob_truncate(&hdr, 0);
      blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
      if( zURL ){
        blob_truncate(&body, nUsed);
        blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zURL, zCode);
      }
      email_send(pSender, &hdr, &body);
    }
    db_finalize(&q);
  }
  if( bTest2 ){
    /* If the URL is /announce/test2 instead of just /announce, then no
    ** email is actually sent.  Instead, the text of the email that would
    ** have been sent is displayed in the result window. */
    @ <pre style='border: 2px solid blue; padding: 1ex'>
    @ %h(blob_str(&pSender->out))
    @ </pre>
  }
  zErr = pSender->zErr;
  pSender->zErr = 0;
  email_sender_free(pSender);
  return zErr;
}


/*
** WEBPAGE: announce
**







|













|
|











|


|


















|













|







2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
  @ </tr>
  @ <tr>
  @   <td></td>
  @   <td><input type="submit" name="submit" value="Send Message">
  @ </tr>
  @ </table>
  if( zCaptcha ){
    @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
    @ %h(zCaptcha)
    @ </pre>
    @ Enter the 8 characters above in the "Security Code" box
    @ </td></tr></table></div>
  }
  @ </form>
  style_footer();
}

/*
** Send an annoucement message described by query parameter.
** Permission to do this has already been verified.
*/
static char *alert_send_announcement(void){
  AlertSender *pSender;
  char *zErr;
  const char *zTo = PT("to");
  char *zSubject = PT("subject");
  int bAll = PB("all");
  int bAA = PB("aa");
  const char *zSub = db_get("email-subname", "[Fossil Repo]");
  int bTest2 = fossil_strcmp(P("name"),"test2")==0;
  Blob hdr, body;
  blob_init(&body, 0, 0);
  blob_init(&hdr, 0, 0);
  blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
  pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
  if( zTo[0] ){
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
    alert_send(pSender, &hdr, &body, 0);
  }
  if( bAll || bAA ){
    Stmt q;
    int nUsed = blob_size(&body);
    const char *zURL =  db_get("email-url",0);
    db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
                   " WHERE sverified AND NOT sdonotcall %s",
                   bAll ? "" : " AND ssub LIKE '%a%'");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zCode = db_column_text(&q, 1);
      zTo = db_column_text(&q, 0);
      blob_truncate(&hdr, 0);
      blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
      if( zURL ){
        blob_truncate(&body, nUsed);
        blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zURL, zCode);
      }
      alert_send(pSender, &hdr, &body, 0);
    }
    db_finalize(&q);
  }
  if( bTest2 ){
    /* If the URL is /announce/test2 instead of just /announce, then no
    ** email is actually sent.  Instead, the text of the email that would
    ** have been sent is displayed in the result window. */
    @ <pre style='border: 2px solid blue; padding: 1ex'>
    @ %h(blob_str(&pSender->out))
    @ </pre>
  }
  zErr = pSender->zErr;
  pSender->zErr = 0;
  alert_sender_free(pSender);
  return zErr;
}


/*
** WEBPAGE: announce
**
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
  if( fossil_strcmp(P("name"),"test1")==0 ){
    /* Visit the /announce/test1 page to see the CGI variables */
    @ <p style='border: 1px solid black; padding: 1ex;'>
    cgi_print_all(0, 0);
    @ </p>
  }else
  if( P("submit")!=0 && cgi_csrf_safe(1) ){
    char *zErr = email_send_announcement();
    style_header("Announcement Sent");
    if( zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was reported by the system:
      @ <blockquote><pre>
      @ %h(zErr)
      @ </pre></blockquote>







|







2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
  if( fossil_strcmp(P("name"),"test1")==0 ){
    /* Visit the /announce/test1 page to see the CGI variables */
    @ <p style='border: 1px solid black; padding: 1ex;'>
    cgi_print_all(0, 0);
    @ </p>
  }else
  if( P("submit")!=0 && cgi_csrf_safe(1) ){
    char *zErr = alert_send_announcement();
    style_header("Announcement Sent");
    if( zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was reported by the system:
      @ <blockquote><pre>
      @ %h(zErr)
      @ </pre></blockquote>

Changes to src/attach.c.

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    }else if( type==2 ){
      zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
    }else{
      zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    }
    @ <li><p>
    @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
    if( moderation_pending(attachid) ){
      @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
    }
    @ <br /><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
    @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    if( zComment && zComment[0] ){
      @ %!W(zComment)<br />
    }
    if( zPage==0 && zTkt==0 && zTechNote==0 ){
      if( zSrc==0 || zSrc[0]==0 ){
        zSrc = "Deleted from";







|
<
<

|







104
105
106
107
108
109
110
111


112
113
114
115
116
117
118
119
120
    }else if( type==2 ){
      zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
    }else{
      zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    }
    @ <li><p>
    @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
    moderation_pending_www(attachid);


    @ <br /><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
    @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
    if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    if( zComment && zComment[0] ){
      @ %!W(zComment)<br />
    }
    if( zPage==0 && zTkt==0 && zTechNote==0 ){
      if( zSrc==0 || zSrc[0]==0 ){
        zSrc = "Deleted from";
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){
    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
  }
  if( zTktUuid ){
    @ <tr><th>Ticket:</th>
    @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
  }
  if( zTNUuid ){
    @ <tr><th>Tech Note:</th>
    @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>







|
<
<
<







560
561
562
563
564
565
566
567



568
569
570
571
572
573
574
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending_www(rid);



  if( zTktUuid ){
    @ <tr><th>Ticket:</th>
    @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
  }
  if( zTNUuid ){
    @ <tr><th>Tech Note:</th>
    @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>

Changes to src/backoffice.c.

33
34
35
36
37
38
39



















40






41
42
43
44
45






46
47
48
49



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75


76
77
78
79
80
81
82








83



84























85








86
87
88
89
90
















91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108











109
110
111
112
113
114
115
116
117
118
119
120
121
122
123




















124
125
126
127
128
129
130
131
132
133
134
135
136


















137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
**
** At the same time, we do not want a backoffice process to run forever.
** Backoffice processes should die off after doing whatever work they need
** to do.  In this way, we avoid having lots of idle processes in the
** process table, doing nothing on rarely accessed repositories, and
** if the Fossil binary is updated on a system, the backoffice processes
** will restart using the new binary automatically.



















*/






#include "config.h"
#include "backoffice.h"
#include <time.h>
#if defined(_WIN32)
# include <windows.h>






#else
# include <unistd.h>
# include <sys/types.h>
# include <signal.h>



#endif

/*
** The BKOFCE_LEASE_TIME is the amount of time for which a single backoffice
** processing run is valid.  Each backoffice run monopolizes the lease for
** at least this amount of time.  Hopefully all backoffice processing is
** finished much faster than this - usually in less than a second.  But
** regardless of how fast each invocations run, successive backoffice runs
** must be spaced out by at least this much time.
*/
#define BKOFCE_LEASE_TIME   60    /* Length of lease validity */

#if LOCAL_INTERFACE
/*
** An instance of the following object describes a lease on the backoffice
** processing timeslot.  This lease is used to help ensure that no more than
** one processing is running backoffice at a time.
*/
struct Lease {
  sqlite3_uint64 idCurrent;   /* ID for the current lease holder */
  sqlite3_uint64 tmCurrent;   /* Expiration of the current lease */
  sqlite3_uint64 idNext;      /* ID for the next lease holder on queue */
  sqlite3_uint64 tmNext;      /* Expiration of the next lease */
};
#endif



/*
** Set to prevent backoffice processing from every entering sleep or
** otherwise taking a long time to complete.  Set this when a user-visible
** process might need to wait for backoffice to complete.
*/
static int backofficeNoDelay = 0;













/*























** Disable the backoffice








*/
void backoffice_no_delay(void){
  backofficeNoDelay = 1;
}


















/*
** Parse a unsigned 64-bit integer from a string.  Return a pointer
** to the character of z[] that occurs after the integer.
*/
static const char *backofficeParseInt(const char *z, sqlite3_uint64 *pVal){
  *pVal = 0;
  if( z==0 ) return 0;
  while( fossil_isspace(z[0]) ){ z++; }
  while( fossil_isdigit(z[0]) ){
    *pVal = (*pVal)*10 + z[0] - '0';
    z++;
  }
  return z;
}

/*
** Read the "backoffice" property and parse it into a Lease object.











*/
static void backofficeReadLease(Lease *pLease){
  Stmt q;
  memset(pLease, 0, sizeof(*pLease));
  db_prepare(&q, "SELECT value FROM repository.config"
                 " WHERE name='backoffice'");
  if( db_step(&q)==SQLITE_ROW ){
    const char *z = db_column_text(&q,0);
    z = backofficeParseInt(z, &pLease->idCurrent);
    z = backofficeParseInt(z, &pLease->tmCurrent);
    z = backofficeParseInt(z, &pLease->idNext);
    backofficeParseInt(z, &pLease->tmNext);
  }
  db_finalize(&q);
}





















/*
** Write a lease to the backoffice property
*/
static void backofficeWriteLease(Lease *pLease){
  db_multi_exec(
    "REPLACE INTO repository.config(name,value,mtime)"
    " VALUES('backoffice','%lld %lld %lld %lld',now())",
    pLease->idCurrent, pLease->tmCurrent,
    pLease->idNext, pLease->tmNext);
}

/*


















** Check to see if the process identified by selfId is alive.  If
** we cannot prove the the process is dead, return true.
*/
static int backofficeProcessExists(sqlite3_uint64 pid){
#if defined(_WIN32)
  return 1;
#else
  return pid>0 && kill((pid_t)pid, 0)==0;
#endif 
}

/*
** Check to see if the process identified by selfId has finished.  If
** we cannot prove the the process is still running, return true.
*/
static int backofficeProcessDone(sqlite3_uint64 pid){
#if defined(_WIN32)
  return 1;
#else
  return pid<=0 || kill((pid_t)pid, 0)!=0;
#endif 
}

/*
** Return a process id number for the current process
*/
static sqlite3_uint64 backofficeProcessId(void){
#if defined(_WIN32)
  return (sqlite3_uint64)GetCurrentProcessId();
#else
  return (sqlite3_uint64)getpid();
#endif
}

/*
** Set an alarm to cause the process to exit after "x" seconds.  This
** prevents any kind of bug from keeping a backoffice process running
** indefinitely.
*/
#if !defined(_WIN32)
static void backofficeSigalrmHandler(int x){
  fossil_panic("backoffice timeout");
}
#endif
static void backofficeTimeout(int x){
#if !defined(_WIN32)
  signal(SIGALRM, backofficeSigalrmHandler);
  alarm(x);
#endif
}


/*
** COMMAND: test-process-id
**
** Usage: %fossil [--sleep N] PROCESS-ID ...







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>





>
>
>
>
>
>




>
>
>







|


|





|


|
|
|
|



>
>
|
|





>
>
>
>
>
>
>
>

>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


















>
>
>
>
>
>
>
>
>
>
>















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|




|






|




|









<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306

307




















308
309
310
311
312
313
314
**
** At the same time, we do not want a backoffice process to run forever.
** Backoffice processes should die off after doing whatever work they need
** to do.  In this way, we avoid having lots of idle processes in the
** process table, doing nothing on rarely accessed repositories, and
** if the Fossil binary is updated on a system, the backoffice processes
** will restart using the new binary automatically.
**
** At any point in time there should be at most two backoffice processes.
** There is a main process that is doing the actually work, and there is
** a second stand-by process that is waiting for the main process to finish
** and that will become the main process after a delay.
**
** After any successful web page reply, the backoffice_check_if_needed()
** routine is called.  That routine checks to see if both one or both of
** the backoffice processes are already running.  That routine remembers the
** status in a global variable.
**
** Later, after the repository database is closed, the
** backoffice_run_if_needed() routine is called.  If the prior call
** to backoffice_check_if_needed() indicated that backoffice processing
** might be required, the run_if_needed() attempts to kick off a backoffice
** process.
**
** All work performance by the backoffice is in the backoffice_work()
** routine.
*/
#if defined(_WIN32)
# if defined(_WIN32_WINNT)
#  undef _WIN32_WINNT
# endif
# define _WIN32_WINNT 0x501
#endif
#include "config.h"
#include "backoffice.h"
#include <time.h>
#if defined(_WIN32)
# include <windows.h>
# include <stdio.h>
# include <process.h>
# if defined(__MINGW32__)
#  include <wchar.h>
# endif
# define GETPID (int)GetCurrentProcessId
#else
# include <unistd.h>
# include <sys/types.h>
# include <signal.h>
# include <errno.h>
# include <fcntl.h>
# define GETPID getpid
#endif

/*
** The BKOFCE_LEASE_TIME is the amount of time for which a single backoffice
** processing run is valid.  Each backoffice run monopolizes the lease for
** at least this amount of time.  Hopefully all backoffice processing is
** finished much faster than this - usually in less than a second.  But
** regardless of how long each invocation lasts, successive backoffice runs
** must be spaced out by at least this much time.
*/
#define BKOFCE_LEASE_TIME   60    /* Length of lease validity in seconds */

#if LOCAL_INTERFACE
/*
** An instance of the following object describes a lease on the backoffice
** processing timeslot.  This lease is used to help ensure that no more than
** one process is running backoffice at a time.
*/
struct Lease {
  sqlite3_uint64 idCurrent; /* process ID for the current lease holder */
  sqlite3_uint64 tmCurrent; /* Expiration of the current lease */
  sqlite3_uint64 idNext;    /* process ID for the next lease holder on queue */
  sqlite3_uint64 tmNext;    /* Expiration of the next lease */
};
#endif

/***************************************************************************
** Local state variables
**
** Set to prevent backoffice processing from ever entering sleep or
** otherwise taking a long time to complete.  Set this when a user-visible
** process might need to wait for backoffice to complete.
*/
static int backofficeNoDelay = 0;

/* This variable is set to the name of a database on which backoffice
** should run if backoffice process is needed.  It is set by the
** backoffice_check_if_needed() routine which must be run while the database
** file is open.  Later, after the database is closed, the
** backoffice_run_if_needed() will consult this variable to see if it
** should be a no-op.
*/
static char *backofficeDb = 0;

/* End of state variables
****************************************************************************/

/*
** This function emits a diagnostic message related to the processing in
** this module.
*/
#if defined(_WIN32)
# define BKOFCE_ALWAYS_TRACE   (1)
extern void sqlite3_win32_write_debug(const char *, int);
#else
# define BKOFCE_ALWAYS_TRACE   (0)
#endif
static void backofficeTrace(const char *zFormat, ...){
  char *zMsg = 0;
  if( BKOFCE_ALWAYS_TRACE || g.fAnyTrace ){
    va_list ap;
    va_start(ap, zFormat);
    zMsg = sqlite3_vmprintf(zFormat, ap);
    va_end(ap);
#if defined(_WIN32)
    sqlite3_win32_write_debug(zMsg, -1);
#endif
  }
  if( g.fAnyTrace ) fprintf(stderr, "%s", zMsg);
  if( zMsg ) sqlite3_free(zMsg);
}

/*
** Do not allow backoffice processes to sleep waiting on a timeslot.
** They must either do their work immediately or exit.
**
** In a perfect world, this interface would not exist, as there would
** never be a problem with waiting backoffice threads.  But in some cases
** a backoffice will delay a UI thread, so we don't want them to run for
** longer than needed.
*/
void backoffice_no_delay(void){
  backofficeNoDelay = 1;
}

/*
** Sleeps for the specified number of milliseconds -OR- until interrupted
** by another thread (if supported by the underlying platform).  Non-zero
** will be returned if the sleep was interrupted.
*/
static int backofficeSleep(int milliseconds){
#if defined(_WIN32)
  assert( milliseconds>=0 );
  if( SleepEx((DWORD)milliseconds, TRUE)==WAIT_IO_COMPLETION ){
    return 1;
  }
#else
  sqlite3_sleep(milliseconds);
#endif
  return 0;
}

/*
** Parse a unsigned 64-bit integer from a string.  Return a pointer
** to the character of z[] that occurs after the integer.
*/
static const char *backofficeParseInt(const char *z, sqlite3_uint64 *pVal){
  *pVal = 0;
  if( z==0 ) return 0;
  while( fossil_isspace(z[0]) ){ z++; }
  while( fossil_isdigit(z[0]) ){
    *pVal = (*pVal)*10 + z[0] - '0';
    z++;
  }
  return z;
}

/*
** Read the "backoffice" property and parse it into a Lease object.
**
** The backoffice property should consist of four integers:
**
**    (1)  Process ID for the active backoffice process.
**    (2)  Time (seconds since 1970) for when the active backoffice
**         lease expires.
**    (3)  Process ID for the on-deck backoffice process.
**    (4)  Time when the on-deck process should expire.
**
** No other process should start active backoffice processing until
** process (1) no longer exists and the current time exceeds (2).
*/
static void backofficeReadLease(Lease *pLease){
  Stmt q;
  memset(pLease, 0, sizeof(*pLease));
  db_prepare(&q, "SELECT value FROM repository.config"
                 " WHERE name='backoffice'");
  if( db_step(&q)==SQLITE_ROW ){
    const char *z = db_column_text(&q,0);
    z = backofficeParseInt(z, &pLease->idCurrent);
    z = backofficeParseInt(z, &pLease->tmCurrent);
    z = backofficeParseInt(z, &pLease->idNext);
    backofficeParseInt(z, &pLease->tmNext);
  }
  db_finalize(&q);
}

/*
** Return a string that describes how long it has been since the
** last backoffice run.  The string is obtained from fossil_malloc().
*/
char *backoffice_last_run(void){
  Lease x;
  sqlite3_uint64 tmNow;
  double rAge;
  backofficeReadLease(&x);
  tmNow = time(0);
  if( x.tmCurrent==0 ){
    return fossil_strdup("never");
  }
  if( tmNow<=(x.tmCurrent-BKOFCE_LEASE_TIME) ){
    return fossil_strdup("moments ago");
  }
  rAge = (tmNow - (x.tmCurrent-BKOFCE_LEASE_TIME))/86400.0;
  return mprintf("%z ago", human_readable_age(rAge));
}

/*
** Write a lease to the backoffice property
*/
static void backofficeWriteLease(Lease *pLease){
  db_multi_exec(
    "REPLACE INTO repository.config(name,value,mtime)"
    " VALUES('backoffice','%lld %lld %lld %lld',now())",
    pLease->idCurrent, pLease->tmCurrent,
    pLease->idNext, pLease->tmNext);
}

/*
** Check to see if the specified Win32 process is still alive.  It
** should be noted that even if this function returns non-zero, the
** process may die before another operation on it can be completed.
*/
#if defined(_WIN32)
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
#  define PROCESS_QUERY_LIMITED_INFORMATION  (0x1000)
#endif
static int backofficeWin32ProcessExists(DWORD dwProcessId){
  HANDLE hProcess;
  hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,FALSE,dwProcessId);
  if( hProcess==NULL ) return 0;
  CloseHandle(hProcess);
  return 1;
}
#endif

/*
** Check to see if the process identified by pid is alive.  If
** we cannot prove the the process is dead, return true.
*/
static int backofficeProcessExists(sqlite3_uint64 pid){
#if defined(_WIN32)
  return pid>0 && backofficeWin32ProcessExists((DWORD)pid)!=0;
#else
  return pid>0 && kill((pid_t)pid, 0)==0;
#endif 
}

/*
** Check to see if the process identified by pid has finished.  If
** we cannot prove the the process is still running, return true.
*/
static int backofficeProcessDone(sqlite3_uint64 pid){
#if defined(_WIN32)
  return pid<=0 || backofficeWin32ProcessExists((DWORD)pid)==0;
#else
  return pid<=0 || kill((pid_t)pid, 0)!=0;
#endif 
}

/*
** Return a process id number for the current process
*/
static sqlite3_uint64 backofficeProcessId(void){

  return (sqlite3_uint64)GETPID();




















}


/*
** COMMAND: test-process-id
**
** Usage: %fossil [--sleep N] PROCESS-ID ...
205
206
207
208
209
210
211





212


213








214





















215

216

217
218
219
220
221
222
223

224

225








226






227
228
229
230
231
232
233
234
235
236
237

238





















239
240
241
242
243
244
245
    sqlite3_uint64 x = (sqlite3_uint64)atoi(g.argv[i]);
    fossil_print("ProcessId %lld: exists %d done %d\n",
                 x, backofficeProcessExists(x),
                    backofficeProcessDone(x));
  }
}






/* This is the main public interface to the backoffice.  A process invokes this


** routine in an attempt to become the backoffice.  If another process is








** already working as the backoffice, this routine returns very quickly





















** without doing any work - allowing the other process to continue.  But

** if no other processes are currently operating as the backoffice, this

** routine enters a loop to do background work periodically.
*/
void backoffice_run(void){
  Lease x;
  sqlite3_uint64 tmNow;
  sqlite3_uint64 idSelf;
  int lastWarning = 0;

  int warningDelay = 30;

  static int once = 0;















  if( once ){
    fossil_panic("multiple calls to backoffice_run()");
  }
  once = 1;
  if( g.db==0 ){
    fossil_panic("database not open for backoffice processing");
  }
  if( db_transaction_nesting_depth()!=0 ){
    fossil_panic("transaction %s not closed prior to backoffice processing",
                 db_transaction_start_point());
  }

  backofficeTimeout(BKOFCE_LEASE_TIME*2);





















  idSelf = backofficeProcessId();
  while(1){
    tmNow = time(0);
    db_begin_write();
    backofficeReadLease(&x);
    if( x.tmNext>=tmNow
     && x.idNext!=idSelf







>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
|

|


|
|
>
|
>
|
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
|

|







>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    sqlite3_uint64 x = (sqlite3_uint64)atoi(g.argv[i]);
    fossil_print("ProcessId %lld: exists %d done %d\n",
                 x, backofficeProcessExists(x),
                    backofficeProcessDone(x));
  }
}

/*
** COMMAND: test-backoffice-lease
**
** Usage: %fossil test-backoffice-lease
**
** Print out information about the backoffice "lease" entry in the
** config table that controls whether or not backoffice should run.
*/
void test_backoffice_lease(void){
  sqlite3_int64 tmNow = time(0);
  Lease x;
  const char *zLease;
  db_find_and_open_repository(0,0);
  verify_all_options();
  zLease = db_get("backoffice","");
  fossil_print("now:        %lld\n", tmNow);
  fossil_print("lease:      \"%s\"\n", zLease);
  backofficeReadLease(&x);
  fossil_print("idCurrent:  %-20lld", x.idCurrent);
  if( backofficeProcessExists(x.idCurrent) ) fossil_print(" (exists)");
  if( backofficeProcessDone(x.idCurrent) ) fossil_print(" (done)");
  fossil_print("\n");
  fossil_print("tmCurrent:  %-20lld", x.tmCurrent);
  if( x.tmCurrent>0 ){
    fossil_print(" (now%+d)\n",x.tmCurrent-tmNow);
  }else{
    fossil_print("\n");
  }
  fossil_print("idNext:     %-20lld", x.idNext);
  if( backofficeProcessExists(x.idNext) ) fossil_print(" (exists)");
  if( backofficeProcessDone(x.idNext) ) fossil_print(" (done)");
  fossil_print("\n");
  fossil_print("tmNext:     %-20lld", x.tmNext);
  if( x.tmNext>0 ){
    fossil_print(" (now%+d)\n",x.tmNext-tmNow);
  }else{
    fossil_print("\n");
  }
}

/*
** If backoffice processing is needed set the backofficeDb variable to the
** name of the database file.  If no backoffice processing is needed,
** this routine makes no changes to state.
*/
void backoffice_check_if_needed(void){
  Lease x;
  sqlite3_uint64 tmNow;

  if( backofficeDb ) return;
  if( g.zRepositoryName==0 ) return;
  if( g.db==0 ) return;
  if( !db_table_exists("repository","config") ) return;
  tmNow = time(0);
  backofficeReadLease(&x);
  if( x.tmNext>=tmNow && backofficeProcessExists(x.idNext) ){
    /* Another backoffice process is already queued up to run.  This
    ** process does not need to do any backoffice work. */
    return;
  }else{
    /* We need to run backup to be (at a minimum) on-deck */
    backofficeDb = fossil_strdup(g.zRepositoryName);
  }
}

/*
** Check for errors prior to running backoffice_thread() or backoffice_run().
*/
static void backoffice_error_check_one(int *pOnce){
  if( *pOnce ){
    fossil_panic("multiple calls to backoffice()");
  }
  *pOnce = 1;
  if( g.db==0 ){
    fossil_panic("database not open for backoffice processing");
  }
  if( db_transaction_nesting_depth()!=0 ){
    fossil_panic("transaction %s not closed prior to backoffice processing",
                 db_transaction_start_point());
  }
}

/* This is the main loop for backoffice processing.
**
** If another process is already working as the current backoffice and
** the on-deck backoffice, then this routine returns very quickly
** without doing any work.
**
** If no backoffice processes are running at all, this routine becomes
** the main backoffice.
**
** If a primary backoffice is running, but a on-deck backoffice is
** needed, this routine becomes that on-desk backoffice.
*/
static void backoffice_thread(void){
  Lease x;
  sqlite3_uint64 tmNow;
  sqlite3_uint64 idSelf;
  int lastWarning = 0;
  int warningDelay = 30;
  static int once = 0;

  backoffice_error_check_one(&once);
  idSelf = backofficeProcessId();
  while(1){
    tmNow = time(0);
    db_begin_write();
    backofficeReadLease(&x);
    if( x.tmNext>=tmNow
     && x.idNext!=idSelf
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286





287
288
289
290
291
292
293
294
295





296
297
298
299
300
301
302
303
304
305














306

307
308
309
310
311
312
313
314













315













































316












317

318

319
      /* This process can start doing backoffice work immediately */
      x.idCurrent = idSelf;
      x.tmCurrent = tmNow + BKOFCE_LEASE_TIME;
      x.idNext = 0;
      x.tmNext = 0;
      backofficeWriteLease(&x);
      db_end_transaction(0);
      if( g.fAnyTrace ){
        fprintf(stderr, "/***** Begin Backoffice Processing %d *****/\n",
                        getpid());
      }
      backoffice_work();
      break;
    }
    if( backofficeNoDelay ){
      /* If the no-delay flag is set, exit immediately rather than queuing
      ** up.  Assume that some future request will come along and handle any
      ** necessary backoffice work. */
      db_end_transaction(0);
      break;
    }
    /* This process needs to queue up and wait for the current lease
    ** to expire before continuing. */
    x.idNext = idSelf;
    x.tmNext = (tmNow>x.tmCurrent ? tmNow : x.tmCurrent) + BKOFCE_LEASE_TIME;
    backofficeWriteLease(&x);
    db_end_transaction(0);
    if( g.fAnyTrace ){
      fprintf(stderr, "/***** Backoffice On-deck %d *****/\n",  getpid());
    }
    if( x.tmCurrent >= tmNow ){
      sqlite3_sleep(1000*(x.tmCurrent - tmNow + 1));





    }else{
      if( lastWarning+warningDelay < tmNow ){
        fossil_warning(
           "backoffice process %lld still running after %d seconds",
           x.idCurrent, (int)(BKOFCE_LEASE_TIME + tmNow - x.tmCurrent));
        lastWarning = tmNow;
        warningDelay *= 2;
      }
      sqlite3_sleep(1000);





    }
  }
  return;
}

/*
** This routine runs to do the backoffice processing.  When adding new
** backoffice processing tasks, add them here.
*/
void backoffice_work(void){














  email_backoffice(0);

}

/*
** COMMAND: test-backoffice
**
** Usage: test-backoffice
**
** Run backoffice processing













*/













































void test_backoffice_command(void){












  db_find_and_open_repository(0,0);

  backoffice_run();

}







<
|
|
<



|












<
|
<

|
>
>
>
>
>








|
>
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>



|

|

|
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>

453
454
455
456
457
458
459

460
461

462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477

478

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
      /* This process can start doing backoffice work immediately */
      x.idCurrent = idSelf;
      x.tmCurrent = tmNow + BKOFCE_LEASE_TIME;
      x.idNext = 0;
      x.tmNext = 0;
      backofficeWriteLease(&x);
      db_end_transaction(0);

      backofficeTrace("/***** Begin Backoffice Processing %d *****/\n",
                      GETPID());

      backoffice_work();
      break;
    }
    if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",0) ){
      /* If the no-delay flag is set, exit immediately rather than queuing
      ** up.  Assume that some future request will come along and handle any
      ** necessary backoffice work. */
      db_end_transaction(0);
      break;
    }
    /* This process needs to queue up and wait for the current lease
    ** to expire before continuing. */
    x.idNext = idSelf;
    x.tmNext = (tmNow>x.tmCurrent ? tmNow : x.tmCurrent) + BKOFCE_LEASE_TIME;
    backofficeWriteLease(&x);
    db_end_transaction(0);

    backofficeTrace("/***** Backoffice On-deck %d *****/\n",  GETPID());

    if( x.tmCurrent >= tmNow ){
      if( backofficeSleep(1000*(x.tmCurrent - tmNow + 1)) ){
        /* The sleep was interrupted by a signal from another thread. */
        backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
        db_end_transaction(0);
        break;
      }
    }else{
      if( lastWarning+warningDelay < tmNow ){
        fossil_warning(
           "backoffice process %lld still running after %d seconds",
           x.idCurrent, (int)(BKOFCE_LEASE_TIME + tmNow - x.tmCurrent));
        lastWarning = tmNow;
        warningDelay *= 2;
      }
      if( backofficeSleep(1000) ){
        /* The sleep was interrupted by a signal from another thread. */
        backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
        db_end_transaction(0);
        break;
      }
    }
  }
  return;
}

/*
** This routine runs to do the backoffice processing.  When adding new
** backoffice processing tasks, add them here.
*/
void backoffice_work(void){
  /* Log the backoffice run for testing purposes.  For production deployments
  ** the "backoffice-logfile" property should be unset and the following code
  ** should be a no-op. */
  char *zLog = db_get("backoffice-logfile",0);
  if( zLog && zLog[0] ){
    FILE *pLog = fossil_fopen(zLog, "a");
    if( pLog ){
      char *zDate = db_text(0, "SELECT datetime('now');");
      fprintf(pLog, "%s (%d) backoffice running\n", zDate, GETPID());
      fclose(pLog);
    }
  }

  /* Here is where the actual work of the backoffice happens */
  alert_backoffice(0);
  smtp_cleanup();
}

/*
** COMMAND: backoffice
**
** Usage: backoffice [-R repository]
**
** Run backoffice processing.  This might be done by a cron job or
** similar to make sure backoffice processing happens periodically.
*/
void backoffice_command(void){
  if( find_option("trace",0,0)!=0 ) g.fAnyTrace = 1;
  db_find_and_open_repository(0,0);
  verify_all_options();
  backoffice_thread();
}

/*
** This is the main interface to backoffice from the rest of the system.
** This routine launches either backoffice_thread() directly or as a
** subprocess.
*/
void backoffice_run_if_needed(void){
  if( backofficeDb==0 ) return;
  if( strcmp(backofficeDb,"x")==0 ) return;
  if( g.db ) return;
  if( g.repositoryOpen ) return;
#if defined(_WIN32)
  {
    int i;
    intptr_t x;
    char *argv[4];
    wchar_t *ax[5];
    argv[0] = g.nameOfExe;
    argv[1] = "backoffice";
    argv[2] = "-R";
    argv[3] = backofficeDb;
    ax[4] = 0;
    for(i=0; i<=3; i++) ax[i] = fossil_utf8_to_unicode(argv[i]);
    x = _wspawnv(_P_NOWAIT, ax[0], (const wchar_t * const *)ax);
    for(i=0; i<=3; i++) fossil_unicode_free(ax[i]);
    backofficeTrace(
      "/***** Subprocess %d creates backoffice child %lu *****/\n",
      GETPID(), GetProcessId((HANDLE)x));
    if( x>=0 ) return;
  }
#else /* unix */
  {
    pid_t pid = fork();
    if( pid>0 ){
      /* This is the parent in a successful fork().  Return immediately. */
      backofficeTrace(
        "/***** Subprocess %d creates backoffice child %d *****/\n",
        GETPID(), (int)pid);
      return;
    }
    if( pid==0 ){
      /* This is the child of a successful fork().  Run backoffice. */
      int i;
      setsid();
      for(i=0; i<=2; i++){
        close(i);
        open("/dev/null", O_RDWR);
      }
      for(i=3; i<100; i++){ close(i); }
      db_open_repository(backofficeDb);
      backofficeDb = "x";
      backoffice_thread();
      db_close(1);
      backofficeTrace("/***** Backoffice Child %d exits *****/\n", GETPID());
      exit(0);
    }
    fossil_warning("backoffice process %d fork failed, errno %d", GETPID(),
                   errno);
  }
#endif
  /* Fork() failed or is unavailable.  Run backoffice in this process, but
  ** do so with the no-delay setting.
  */
  backofficeNoDelay = 1;
  db_open_repository(backofficeDb);
  backofficeDb = "x";
  backoffice_thread();
  db_close(1);
}

Changes to src/browse.c.

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
**    type=TYPE        TYPE=flat: use this display
**                     TYPE=tree: use the /tree display instead
*/
void page_dir(void){
  char *zD = fossil_strdup(P("name"));
  int nD = zD ? strlen(zD)+1 : 0;
  int mxLen;
  int nCol, nRow;
  int cnt, i;
  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;







|
<







119
120
121
122
123
124
125
126

127
128
129
130
131
132
133
**    type=TYPE        TYPE=flat: use this display
**                     TYPE=tree: use the /tree display instead
*/
void page_dir(void){
  char *zD = fossil_strdup(P("name"));
  int nD = zD ? strlen(zD)+1 : 0;
  int mxLen;
  int n;

  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
267
268
269
270
271
272
273
274
275
276
277
278
279
280

281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    );
  }

  /* Generate a multi-column table listing the contents of zD[]
  ** directory.
  */
  mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
  cnt = db_int(0, "SELECT count(*) FROM localfiles /*scan*/");
  if( mxLen<12 ) mxLen = 12;
  nCol = 100/mxLen;
  if( nCol<1 ) nCol = 1;
  if( nCol>5 ) nCol = 5;
  nRow = (cnt+nCol-1)/nCol;
  db_prepare(&q, "SELECT x, u FROM localfiles ORDER BY x /*scan*/");

  @ <table class="browser"><tr><td class="browser"><ul class="browser">
  i = 0;
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFN;
    if( i==nRow ){
      @ </ul></td><td class="browser"><ul class="browser">
      i = 0;
    }
    i++;
    zFN = db_column_text(&q, 0);
    if( zFN[0]=='/' ){
      zFN++;
      @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
    }else{
      const char *zLink;
      if( zCI ){
        const char *zUuid = db_column_text(&q, 1);
        zLink = href("%R/artifact/%!S",zUuid);
      }else{
        zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
      }
      @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
    }
  }
  db_finalize(&q);
  manifest_destroy(pM);
  @ </ul></td></tr></table>

  /* If the directory contains a readme file, then display its content below
  ** the list of files
  */
  db_prepare(&q,
    "SELECT x, u FROM localfiles"
    " WHERE x COLLATE nocase IN"







|

|
<
<
<

>
|
<


<
<
<
<
<

















|







266
267
268
269
270
271
272
273
274
275



276
277
278

279
280





281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    );
  }

  /* Generate a multi-column table listing the contents of zD[]
  ** directory.
  */
  mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
  n = db_int(1,"SELECT count(*) FROM localfiles; /*scan*/");
  if( mxLen<12 ) mxLen = 12;
  mxLen += (mxLen+9)/10;



  db_prepare(&q, "SELECT x, u FROM localfiles ORDER BY x /*scan*/");
  @ <div class="columns" style="columns: %d(mxLen)ex %d(n);">
  @ <ul class="browser">

  while( db_step(&q)==SQLITE_ROW ){
    const char *zFN;





    zFN = db_column_text(&q, 0);
    if( zFN[0]=='/' ){
      zFN++;
      @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
    }else{
      const char *zLink;
      if( zCI ){
        const char *zUuid = db_column_text(&q, 1);
        zLink = href("%R/artifact/%!S",zUuid);
      }else{
        zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
      }
      @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
    }
  }
  db_finalize(&q);
  manifest_destroy(pM);
  @ </ul></div>

  /* If the directory contains a readme file, then display its content below
  ** the list of files
  */
  db_prepare(&q,
    "SELECT x, u FROM localfiles"
    " WHERE x COLLATE nocase IN"

Added src/capabilities.c.



































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
/*
** Copyright (c) 2018 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used managing user capability strings.
*/
#include "config.h"
#include "capabilities.h"
#include <assert.h>

#if INTERFACE
/*
** A capability string object holds all defined capabilities in a
** vector format that is subject to boolean operations.
*/
struct CapabilityString {
  unsigned char x[128];
};
#endif

/*
** Add capabilities to a CapabilityString.  If pIn is NULL, then create
** a new capability string.
**
** Call capability_free() on the allocated CapabilityString object to
** deallocate.
*/
CapabilityString *capability_add(CapabilityString *pIn, const char *zCap){
  int c;
  int i;
  if( pIn==0 ){
    pIn = fossil_malloc( sizeof(*pIn) );
    memset(pIn, 0, sizeof(*pIn));
  }
  if( zCap ){
    for(i=0; (c = zCap[i])!=0; i++){
      if( c>='0' && c<='z' ) pIn->x[c] = 1;
    }
  }
  return pIn;
}

/*
** Remove capabilities from a CapabilityString.
*/
CapabilityString *capability_remove(CapabilityString *pIn, const char *zCap){
  int c;
  int i;
  if( pIn==0 ){
    pIn = fossil_malloc( sizeof(*pIn) );
    memset(pIn, 0, sizeof(*pIn));
  }
  if( zCap ){
    for(i=0; (c = zCap[i])!=0; i++){
      if( c>='0' && c<='z' ) pIn->x[c] = 0;
    }
  }
  return pIn;
}

/*
** Return true if any of the capabilities in zNeeded are found in pCap
*/
int capability_has_any(CapabilityString *p, const char *zNeeded){
  if( p==0 ) return 0;
  if( zNeeded==0 ) return 0;
  while( zNeeded[0] ){
    int c = zNeeded[0];
    if( fossil_isalnum(c) && p->x[c] ) return 1;
    zNeeded++;
  }
  return 0;
}

/*
** Delete a CapabilityString object.
*/
void capability_free(CapabilityString *p){
  fossil_free(p);
}

/*
** Expand the capability string by including all capabilities for 
** special users "nobody" and "anonymous".  Also include "reader"
** if "u" is present and "developer" if "v" is present.
*/
void capability_expand(CapabilityString *pIn){
  static char *zNobody = 0;
  static char *zAnon = 0;
  static char *zReader = 0;
  static char *zDev = 0;
  int doneV = 0;

  if( pIn==0 ){
    fossil_free(zNobody); zNobody = 0;
    fossil_free(zAnon);   zAnon = 0;
    fossil_free(zReader); zReader = 0;
    fossil_free(zDev);    zDev = 0;
    return;
  }
  if( zNobody==0 ){
    zNobody = db_text(0, "SELECT cap FROM user WHERE login='nobody'");
    zAnon = db_text(0, "SELECT cap FROM user WHERE login='anonymous'");
    zReader = db_text(0, "SELECT cap FROM user WHERE login='reader'");
    zDev = db_text(0, "SELECT cap FROM user WHERE login='developer'");
  }
  pIn = capability_add(pIn, zAnon);
  pIn = capability_add(pIn, zNobody);
  if( pIn->x['v'] ){
    pIn = capability_add(pIn, zDev);
    doneV = 1;
  }
  if( pIn->x['u'] ){
    pIn = capability_add(pIn, zReader);
    if( pIn->x['v'] && !doneV ){
      pIn = capability_add(pIn, zDev);
    }
  }
}

/*
** Render a capability string in canonical string format.  Space to hold
** the returned string is obtained from fossil_malloc() can should be freed
** by the caller.
*/
char *capability_string(CapabilityString *p){
  Blob out;
  int i;
  int j = 0;
  char buf[100];
  blob_init(&out, 0, 0);
  for(i='a'; i<='z'; i++){
    if( p->x[i] ) buf[j++] = i;
  }
  for(i='0'; i<='9'; i++){
    if( p->x[i] ) buf[j++] = i;
  }
  for(i='A'; i<='Z'; i++){
    if( p->x[i] ) buf[j++] = i;
  }
  buf[j] = 0;
  return fossil_strdup(buf);
}

/*
** The next two routines implement an aggregate SQL function that
** takes multiple capability strings and in the end returns their
** union.  Example usage:
**
**    SELECT capunion(cap) FROM user WHERE login IN ('nobody','anonymous');
*/
void capability_union_step(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  CapabilityString *p;
  const char *zIn;

  zIn = (const char*)sqlite3_value_text(argv[0]);
  if( zIn==0 ) return;
  p = (CapabilityString*)sqlite3_aggregate_context(context, sizeof(*p));
  p = capability_add(p, zIn);
}
void capability_union_finalize(sqlite3_context *context){
  CapabilityString *p;
  p = sqlite3_aggregate_context(context, 0);
  if( p ){
    char *zOut = capability_string(p);
    sqlite3_result_text(context, zOut, -1, fossil_free);
  }
}

/*
** The next routines takes the raw USER.CAP field and expands it with
** capabilities from special users.  Example:
**
**   SELECT fullcap(cap) FROM user WHERE login=?1
*/
void capability_fullcap(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  CapabilityString *p;
  const char *zIn;
  char *zOut;

  zIn = (const char*)sqlite3_value_text(argv[0]);
  if( zIn==0 ) zIn = "";
  p = capability_add(0, zIn);
  capability_expand(p);
  zOut = capability_string(p);
  sqlite3_result_text(context, zOut, -1, fossil_free);
  capability_free(p);
}

#if INTERFACE
/*
** Capabilities are grouped into "classes" as follows:
*/
#define CAPCLASS_CODE  0x0001
#define CAPCLASS_WIKI  0x0002
#define CAPCLASS_TKT   0x0004
#define CAPCLASS_FORUM 0x0008
#define CAPCLASS_DATA  0x0010
#define CAPCLASS_ALERT 0x0020
#define CAPCLASS_OTHER 0x0040
#define CAPCLASS_SUPER 0x0080
#define CAPCLASS_ALL   0xffff
#endif /* INTERFACE */


/*
** The following structure holds descriptions of the various capabilities.
*/
static struct Caps {
  char cCap;              /* The capability letter */
  unsigned short eClass;  /* The "class" for this capability */
  char *zAbbrev;          /* Abbreviated mnemonic name */
  char *zOneLiner;        /* One-line summary */
} aCap[] = {
  { 'a', CAPCLASS_SUPER,
    "Admin", "Create and delete users" },
  { 'b', CAPCLASS_WIKI|CAPCLASS_TKT,
    "Attach", "Add attchments to wiki or tickets" },
  { 'c', CAPCLASS_TKT,
    "Append-Tkt", "Append to existing tickets" },
  { 'd', CAPCLASS_WIKI|CAPCLASS_TKT,
    "Delete", "Delete wiki or tickets" },
  { 'e', CAPCLASS_DATA,
    "View-PII", "View sensitive info such as email addresses" },
  { 'f', CAPCLASS_WIKI,
    "New-Wiki", "Create new wiki pages" },
  { 'g', CAPCLASS_DATA,
    "Clone", "Clone the repository" },
  { 'h', CAPCLASS_OTHER,
    "Hyperlinks", "Show hyperlinks to detailed repository history" },
  { 'i', CAPCLASS_CODE,
    "Check-In", "Check-in code changes" },
  { 'j', CAPCLASS_WIKI,
    "Read-Wiki", "View wiki pages" },
  { 'k', CAPCLASS_WIKI,
    "Write-Wiki", "Edit wiki pages" },
  { 'l', CAPCLASS_WIKI|CAPCLASS_SUPER,
    "Mod-Wiki", "Moderator for wiki pages" },
  { 'm', CAPCLASS_WIKI,
    "Append-Wiki", "Append to wiki pages" },
  { 'n', CAPCLASS_TKT,
    "New-Tkt", "Create new tickets" },
  { 'o', CAPCLASS_CODE,
    "Check-Out", "Check out code" },
  { 'p', CAPCLASS_OTHER,
    "Password", "Change your own password" },
  { 'q', CAPCLASS_TKT|CAPCLASS_SUPER,
    "Mod-Tkt", "Moderate tickets" },
  { 'r', CAPCLASS_TKT,
    "Read-Tkt", "View tickets" },
  { 's', CAPCLASS_SUPER,
    "Superuser", "Setup and configure the respository" },
  { 't', CAPCLASS_TKT,
    "Reports", "Create new ticket report formats" },
  { 'u', CAPCLASS_OTHER,
    "Reader", "Inherit all the capabilities of the \"reader\" user" },
  { 'v', CAPCLASS_OTHER,
    "Developer", "Inherit all capabilities of the \"developer\" user" },
  { 'w', CAPCLASS_TKT,
    "Write-Tkt", "Edit tickets" },
  { 'x', CAPCLASS_DATA,
    "Private", "Push and/or pull private branches" },
  { 'y', CAPCLASS_SUPER,
    "Write-UV", "Push unversioned content" },
  { 'z', CAPCLASS_CODE,
    "Zip-Download", "Download a ZIP archive, tarball, or SQL archive" },
  { '2', CAPCLASS_FORUM,
    "Forum-Read", "Read forum posts by others" },
  { '3', CAPCLASS_FORUM,
    "Forum-Write", "Create new forum messages" },
  { '4', CAPCLASS_FORUM,
    "Forum-Trusted", "Create forum messages that bypass moderation" },
  { '5', CAPCLASS_FORUM|CAPCLASS_SUPER,
    "Forum-Mod", "Moderator for forum messages" },
  { '6', CAPCLASS_FORUM|CAPCLASS_SUPER,
    "Forum-Admin", "Set or remove capability '4' from other users" },
  { '7', CAPCLASS_ALERT,
    "Alerts", "Sign up for email alerts" },
  { 'A', CAPCLASS_ALERT|CAPCLASS_SUPER,
    "Announce", "Send announcements to all subscribers" },
  { 'D', CAPCLASS_OTHER,
    "Debug", "Enable debugging features" },
};


/*
** Generate HTML that lists all of the capability letters together with
** a brief summary of what each letter means.
*/
void capabilities_table(unsigned mClass){
  int i;
  @ <table>
  for(i=0; i<sizeof(aCap)/sizeof(aCap[0]); i++){
    if( (aCap[i].eClass & mClass)==0 ) continue;
    @ <tr><th valign="top">%c(aCap[i].cCap)</th>
    @  <td><i>%h(aCap[i].zAbbrev):</i> %h(aCap[i].zOneLiner)</td></tr>
  }
  @ </table>
}

/*
** Generate a "capability summary table" that shows the major capabilities
** against the various user categories.
*/
void capability_summary(void){
  Stmt q;
  db_prepare(&q,
    "WITH t(id,seq) AS (VALUES('nobody',1),('anonymous',2),('reader',3),"
                       "('developer',4))"
    " SELECT id, fullcap(user.cap),seq,1"
    "   FROM t LEFT JOIN user ON t.id=user.login"
    " UNION ALL"
    " SELECT 'New User Default', fullcap(%Q), 10, 1"
    " UNION ALL"
    " SELECT 'Regular User', fullcap(capunion(cap)), 20, count(*) FROM user"
    " WHERE cap NOT GLOB '*[as]*'"
    " UNION ALL"
    " SELECT 'Adminstator', fullcap(capunion(cap)), 30, count(*) FROM user"
    " WHERE cap GLOB '*[as]*'"
    " ORDER BY 3 ASC",
    db_get("default-perms","")
  );
  @ <table id='capabilitySummary' cellpadding="0" cellspacing="0" border="1">
  @ <tr><th>&nbsp;<th>Code<th>Forum<th>Tickets<th>Wiki\
  @ <th>Unversioned Content</th></tr>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zId = db_column_text(&q, 0);
    const char *zCap = db_column_text(&q, 1);
    int n = db_column_int(&q, 3);
    int eType;
    static const char *azType[] = { "off", "read", "write" };
    static const char *azClass[] = { "capsumOff", "capsumRead", "capsumWrite" };

    if( n==0 ) continue;

    /* Code */
    if( db_column_int(&q,2)<10 ){
      @ <tr><th align="right"><tt>"%h(zId)"</tt></th>
    }else if( n>1 ){
      @ <tr><th align="right">%d(n) %h(zId)s</th>
    }else{
      @ <tr><th align="right">%h(zId)</th>
    }
    if( sqlite3_strglob("*[asi]*",zCap)==0 ){
      eType = 2;
    }else if( sqlite3_strglob("*[oz]*",zCap)==0 ){
      eType = 1;
    }else{
      eType = 0;
    }
    @ <td class="%s(azClass[eType])">%s(azType[eType])</td>

    /* Forum */
    if( sqlite3_strglob("*[as3456]*",zCap)==0 ){
      eType = 2;
    }else if( sqlite3_strglob("*2*",zCap)==0 ){
      eType = 1;
    }else{
      eType = 0;
    }
    @ <td class="%s(azClass[eType])">%s(azType[eType])</td>

    /* Ticket */
    if( sqlite3_strglob("*[ascdnqtw]*",zCap)==0 ){
      eType = 2;
    }else if( sqlite3_strglob("*r*",zCap)==0 ){
      eType = 1;
    }else{
      eType = 0;
    }
    @ <td class="%s(azClass[eType])">%s(azType[eType])</td>

    /* Wiki */
    if( sqlite3_strglob("*[asdfklm]*",zCap)==0 ){
      eType = 2;
    }else if( sqlite3_strglob("*j*",zCap)==0 ){
      eType = 1;
    }else{
      eType = 0;
    }
    @ <td class="%s(azClass[eType])">%s(azType[eType])</td>

    /* Unversioned */
    if( sqlite3_strglob("*y*",zCap)==0 ){
      eType = 2;
    }else if( sqlite3_strglob("*[ioas]*",zCap)==0 ){
      eType = 1;
    }else{
      eType = 0;
    }
    @ <td class="%s(azClass[eType])">%s(azType[eType])</td>

  }
  db_finalize(&q);
  @ </table>
}

Changes to src/captcha.c.

537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  const char *zDecoded;
  char *zCaptcha;

  if( !captcha_needed() ) return;
  uSeed = captcha_seed();
  zDecoded = captcha_decode(uSeed);
  zCaptcha = captcha_render(zDecoded);
  @ <div class="captcha"><table class="captcha"><tr><td><pre>
  @ %h(zCaptcha)
  @ </pre>
  @ Enter security code shown above:
  @ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
  @ <input type="text" name="captcha" size=8 />
  if( showButton ){
    @ <input type="submit" value="Submit">







|







537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  const char *zDecoded;
  char *zCaptcha;

  if( !captcha_needed() ) return;
  uSeed = captcha_seed();
  zDecoded = captcha_decode(uSeed);
  zCaptcha = captcha_render(zDecoded);
  @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
  @ %h(zCaptcha)
  @ </pre>
  @ Enter security code shown above:
  @ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
  @ <input type="text" name="captcha" size=8 />
  if( showButton ){
    @ <input type="submit" value="Submit">

Changes to src/cgi.c.

343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
  CGIDEBUG(("DONE\n"));

  /* After the webpage has been sent, do any useful background
  ** processing.
  */
  g.cgiOutput = 2;
  if( g.db!=0 && iReplyStatus==200 ){
    fclose(g.httpOut);
#ifdef _WIN32
    g.httpOut = fossil_fopen("NUL", "wb");
#else
    g.httpOut = fossil_fopen("/dev/null", "wb");
#endif
    if( g.httpOut==0 ){
      fossil_warning("failed ot open /dev/null");
    }else{
      backoffice_run();
    }
  }
}

/*
** Do a redirect request to the URL given in the argument.
**
** The URL must be relative to the base of the fossil server.







<
<
<
<
<
<
<
<
<
|
<







343
344
345
346
347
348
349









350

351
352
353
354
355
356
357
  CGIDEBUG(("DONE\n"));

  /* After the webpage has been sent, do any useful background
  ** processing.
  */
  g.cgiOutput = 2;
  if( g.db!=0 && iReplyStatus==200 ){









    backoffice_check_if_needed();

  }
}

/*
** Do a redirect request to the URL given in the argument.
**
** The URL must be relative to the base of the fossil server.
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135




1136
1137
1138
1139
1140
1141
1142
1143
1144
  }
  CGIDEBUG(("no-match [%s]\n", zName));
  return zDefault;
}

/*
** Return the value of a CGI parameter with leading and trailing
** spaces removed.
*/
char *cgi_parameter_trimmed(const char *zName, const char *zDefault){
  const char *zIn;
  char *zOut;
  int i;
  zIn = cgi_parameter(zName, 0);
  if( zIn==0 ) zIn = zDefault;
  if( zIn==0 ) return 0;
  while( fossil_isspace(zIn[0]) ) zIn++;
  zOut = fossil_strdup(zIn);




  for(i=0; zOut[i]; i++){}
  while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
  return zOut;
}

/*
** Return true if the CGI parameter zName exists and is not equal to 0,
** or "no" or "off".
*/







|



|
|





>
>
>
>
|
|







1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
  }
  CGIDEBUG(("no-match [%s]\n", zName));
  return zDefault;
}

/*
** Return the value of a CGI parameter with leading and trailing
** spaces removed and with internal \r\n changed to just \n
*/
char *cgi_parameter_trimmed(const char *zName, const char *zDefault){
  const char *zIn;
  char *zOut, c;
  int i, j;
  zIn = cgi_parameter(zName, 0);
  if( zIn==0 ) zIn = zDefault;
  if( zIn==0 ) return 0;
  while( fossil_isspace(zIn[0]) ) zIn++;
  zOut = fossil_strdup(zIn);
  for(i=j=0; (c = zOut[i])!=0; i++){
    if( c=='\r' && zOut[i+1]=='\n' ) continue;
    zOut[j++] = c;
  }
  zOut[j] = 0;
  while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0;
  return zOut;
}

/*
** Return true if the CGI parameter zName exists and is not equal to 0,
** or "no" or "off".
*/

Changes to src/checkin.c.

2123
2124
2125
2126
2127
2128
2129
2130




2131
2132
2133
2134
2135
2136
2137
  if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
  useCksum = db_get_boolean("repo-cksum", 1);
  outputManifest = db_get_manifest_setting();
  verify_all_options();

  /* Do not allow the creation of a new branch using an existing open
  ** branch name unless the --force flag is used */
  if( sCiInfo.zBranch!=0 && !forceFlag && branch_is_open(sCiInfo.zBranch) ){




    fossil_fatal("an open branch named \"%s\" already exists - use --force"
                 " to override", sCiInfo.zBranch);
  }

  /* Escape special characters in tags and put all tags in sorted order */
  if( nTag ){
    int i;







|
>
>
>
>







2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
  if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
  useCksum = db_get_boolean("repo-cksum", 1);
  outputManifest = db_get_manifest_setting();
  verify_all_options();

  /* Do not allow the creation of a new branch using an existing open
  ** branch name unless the --force flag is used */
  if( sCiInfo.zBranch!=0
   && !forceFlag
   && fossil_strcmp(sCiInfo.zBranch,"private")!=0
   && branch_is_open(sCiInfo.zBranch)
  ){
    fossil_fatal("an open branch named \"%s\" already exists - use --force"
                 " to override", sCiInfo.zBranch);
  }

  /* Escape special characters in tags and put all tags in sorted order */
  if( nTag ){
    int i;

Changes to src/codecheck1.c.

396
397
398
399
400
401
402

403
404
405
406
407
408
409
  { "json_new_string_f",       1, 0 },
  { "json_set_err",            2, 0 },
  { "json_warn",               2, 0 },
  { "mprintf",                 1, 0 },
  { "socket_set_errmsg",       1, 0 },
  { "ssl_set_errmsg",          1, 0 },
  { "style_header",            1, FMT_HTML },

  { "style_set_current_page",  1, FMT_URL },
  { "style_submenu_element",   2, FMT_URL },
  { "style_submenu_sql",       3, FMT_SQL },
  { "webpage_error",           1, FMT_SAFE },
  { "xhref",                   2, FMT_URL },
};








>







396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
  { "json_new_string_f",       1, 0 },
  { "json_set_err",            2, 0 },
  { "json_warn",               2, 0 },
  { "mprintf",                 1, 0 },
  { "socket_set_errmsg",       1, 0 },
  { "ssl_set_errmsg",          1, 0 },
  { "style_header",            1, FMT_HTML },
  { "style_js_onload",         1, FMT_HTML },
  { "style_set_current_page",  1, FMT_URL },
  { "style_submenu_element",   2, FMT_URL },
  { "style_submenu_sql",       3, FMT_SQL },
  { "webpage_error",           1, FMT_SAFE },
  { "xhref",                   2, FMT_URL },
};

Changes to src/configure.c.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#define CONFIGSET_PROJ      0x000008     /* Project name */
#define CONFIGSET_SHUN      0x000010     /* Shun settings */
#define CONFIGSET_USER      0x000020     /* The USER table */
#define CONFIGSET_ADDR      0x000040     /* The CONCEALED table */
#define CONFIGSET_XFER      0x000080     /* Transfer configuration */
#define CONFIGSET_ALIAS     0x000100     /* URL Aliases */
#define CONFIGSET_SCRIBER   0x000200     /* Email subscribers */
#define CONFIGSET_FORUM     0x000400     /* Forum posts */

#define CONFIGSET_ALL       0x0007ff     /* Everything */

#define CONFIGSET_OVERWRITE 0x100000     /* Causes overwrite instead of merge */

/*
** This mask is used for the common TH1 configuration settings (i.e. those
** that are not specific to one particular subsystem, such as the transfer
** subsystem).







<
<
|







35
36
37
38
39
40
41


42
43
44
45
46
47
48
49
#define CONFIGSET_PROJ      0x000008     /* Project name */
#define CONFIGSET_SHUN      0x000010     /* Shun settings */
#define CONFIGSET_USER      0x000020     /* The USER table */
#define CONFIGSET_ADDR      0x000040     /* The CONCEALED table */
#define CONFIGSET_XFER      0x000080     /* Transfer configuration */
#define CONFIGSET_ALIAS     0x000100     /* URL Aliases */
#define CONFIGSET_SCRIBER   0x000200     /* Email subscribers */


#define CONFIGSET_ALL       0x0003ff     /* Everything */

#define CONFIGSET_OVERWRITE 0x100000     /* Causes overwrite instead of merge */

/*
** This mask is used for the common TH1 configuration settings (i.e. those
** that are not specific to one particular subsystem, such as the transfer
** subsystem).
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
                                     "Web interface appearance settings"    },
  { "/css",         CONFIGSET_CSS,   "Style sheet"                          },
  { "/shun",        CONFIGSET_SHUN,  "List of shunned artifacts"            },
  { "/ticket",      CONFIGSET_TKT,   "Ticket setup",                        },
  { "/user",        CONFIGSET_USER,  "Users and privilege settings"         },
  { "/xfer",        CONFIGSET_XFER,  "Transfer setup",                      },
  { "/alias",       CONFIGSET_ALIAS, "URL Aliases",                         },
  { "/subscriber",  CONFIGSET_SCRIBER,"Email notification subscriber list" },
/*{ "/forum",       CONFIGSET_FORUM, "Forum posts",                         },*/
  { "/all",         CONFIGSET_ALL,   "All of the above"                     },
};


/*
** The following is a list of settings that we are willing to
** transfer.







|
<







66
67
68
69
70
71
72
73

74
75
76
77
78
79
80
                                     "Web interface appearance settings"    },
  { "/css",         CONFIGSET_CSS,   "Style sheet"                          },
  { "/shun",        CONFIGSET_SHUN,  "List of shunned artifacts"            },
  { "/ticket",      CONFIGSET_TKT,   "Ticket setup",                        },
  { "/user",        CONFIGSET_USER,  "Users and privilege settings"         },
  { "/xfer",        CONFIGSET_XFER,  "Transfer setup",                      },
  { "/alias",       CONFIGSET_ALIAS, "URL Aliases",                         },
  { "/subscriber",  CONFIGSET_SCRIBER,"Email notification subscriber list"  },

  { "/all",         CONFIGSET_ALL,   "All of the above"                     },
};


/*
** The following is a list of settings that we are willing to
** transfer.
100
101
102
103
104
105
106




107
108
109
110
111
112
113
  { "background-image",       CONFIGSET_SKIN },
  { "timeline-block-markup",  CONFIGSET_SKIN },
  { "timeline-max-comment",   CONFIGSET_SKIN },
  { "timeline-plaintext",     CONFIGSET_SKIN },
  { "adunit",                 CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   CONFIGSET_SKIN },
  { "adunit-omit-if-user",    CONFIGSET_SKIN },





#ifdef FOSSIL_ENABLE_TH1_DOCS
  { "th1-docs",               CONFIGSET_TH1 },
#endif
#ifdef FOSSIL_ENABLE_TH1_HOOKS
  { "th1-hooks",              CONFIGSET_TH1 },
#endif







>
>
>
>







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  { "background-image",       CONFIGSET_SKIN },
  { "timeline-block-markup",  CONFIGSET_SKIN },
  { "timeline-max-comment",   CONFIGSET_SKIN },
  { "timeline-plaintext",     CONFIGSET_SKIN },
  { "adunit",                 CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   CONFIGSET_SKIN },
  { "adunit-omit-if-user",    CONFIGSET_SKIN },
  { "sitemap-docidx",         CONFIGSET_SKIN },
  { "sitemap-download",       CONFIGSET_SKIN },
  { "sitemap-license",        CONFIGSET_SKIN },
  { "sitemap-contact",        CONFIGSET_SKIN },

#ifdef FOSSIL_ENABLE_TH1_DOCS
  { "th1-docs",               CONFIGSET_TH1 },
#endif
#ifdef FOSSIL_ENABLE_TH1_HOOKS
  { "th1-hooks",              CONFIGSET_TH1 },
#endif
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
  }
  for(i=0; i<count(aConfig); i++){
    if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){
      int m = aConfig[i].groupMask;
      if( !g.perm.Admin ){
        m &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
      }
      if( !g.perm.RdForum ){
        m &= ~(CONFIGSET_FORUM);
      }
      if( !g.perm.RdAddr ){
        m &= ~CONFIGSET_ADDR;
      }
      return m;
    }
  }
  if( strncmp(zName, "walias:/", 8)==0 ){







<
<
<







236
237
238
239
240
241
242



243
244
245
246
247
248
249
  }
  for(i=0; i<count(aConfig); i++){
    if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){
      int m = aConfig[i].groupMask;
      if( !g.perm.Admin ){
        m &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
      }



      if( !g.perm.RdAddr ){
        m &= ~CONFIGSET_ADDR;
      }
      return m;
    }
  }
  if( strncmp(zName, "walias:/", 8)==0 ){
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
      thisMask = configure_is_exportable(azToken[1]);
    }else{
      thisMask = configure_is_exportable(aType[ii].zName);
    }
    if( (thisMask & groupMask)==0 ) return;
    if( (thisMask & checkMask)!=0 ){
      if( (thisMask & CONFIGSET_SCRIBER)!=0 ){
        email_schema(1);
      }
      checkMask &= ~thisMask;
    }

    blob_zero(&sql);
    if( groupMask & CONFIGSET_OVERWRITE ){
      if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){
        db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]);
        configHasBeenReset |= thisMask;
      }
      blob_append_sql(&sql, "REPLACE INTO ");
    }else{
      blob_append_sql(&sql, "INSERT OR IGNORE INTO ");
    }
    blob_append_sql(&sql, "\"%w\"(\"%w\",mtime",
         &zName[1], aType[ii].zPrimKey);
    if( fossil_stricmp(zName,"/subscriber") ) email_schema(0);
    for(jj=2; jj<nToken; jj+=2){
       blob_append_sql(&sql, ",\"%w\"", azToken[jj]);
    }
    blob_append_sql(&sql,") VALUES(%s,%s",
       azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
    for(jj=2; jj<nToken; jj+=2){
       blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);







|
















|







400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
      thisMask = configure_is_exportable(azToken[1]);
    }else{
      thisMask = configure_is_exportable(aType[ii].zName);
    }
    if( (thisMask & groupMask)==0 ) return;
    if( (thisMask & checkMask)!=0 ){
      if( (thisMask & CONFIGSET_SCRIBER)!=0 ){
        alert_schema(1);
      }
      checkMask &= ~thisMask;
    }

    blob_zero(&sql);
    if( groupMask & CONFIGSET_OVERWRITE ){
      if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){
        db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]);
        configHasBeenReset |= thisMask;
      }
      blob_append_sql(&sql, "REPLACE INTO ");
    }else{
      blob_append_sql(&sql, "INSERT OR IGNORE INTO ");
    }
    blob_append_sql(&sql, "\"%w\"(\"%w\",mtime",
         &zName[1], aType[ii].zPrimKey);
    if( fossil_stricmp(zName,"/subscriber") ) alert_schema(0);
    for(jj=2; jj<nToken; jj+=2){
       blob_append_sql(&sql, ",\"%w\"", azToken[jj]);
    }
    blob_append_sql(&sql,") VALUES(%s,%s",
       azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
    for(jj=2; jj<nToken; jj+=2){
       blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
695
696
697
698
699
700
701
702


703
704
705
706
707
708
709
**
** Where METHOD is one of: export import merge pull push reset.  All methods
** accept the -R or --repository option to specify a repository.
**
**    %fossil configuration export AREA FILENAME
**
**         Write to FILENAME exported configuration information for AREA.
**         AREA can be one of:  all email project shun skin ticket user alias


**
**    %fossil configuration import FILENAME
**
**         Read a configuration from FILENAME, overwriting the current
**         configuration.
**
**    %fossil configuration merge FILENAME







|
>
>







693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
**
** Where METHOD is one of: export import merge pull push reset.  All methods
** accept the -R or --repository option to specify a repository.
**
**    %fossil configuration export AREA FILENAME
**
**         Write to FILENAME exported configuration information for AREA.
**         AREA can be one of:
**
**             all email project shun skin ticket user alias subscriber
**
**    %fossil configuration import FILENAME
**
**         Read a configuration from FILENAME, overwriting the current
**         configuration.
**
**    %fossil configuration merge FILENAME

Changes to src/db.c.

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#endif /* INTERFACE */
const struct Stmt empty_Stmt = empty_Stmt_m;

/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){
  static int rcLooping = 0;
  va_list ap;
  char *z;
  int rc = 1;
  if( rcLooping ) exit(rcLooping);
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){
    json_err( 0, z, 1 );
    if( g.isHTTP ){
      rc = 0 /* avoid HTTP 500 */;
    }
  }
  else
#endif /* FOSSIL_ENABLE_JSON */
  if( g.xferPanic ){
    cgi_reset_content();
    @ error Database\serror:\s%F(z)
      cgi_reply();
  }
  else if( g.cgiOutput ){
    g.cgiOutput = 0;
    cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z);
    cgi_reply();
  }else{
    fprintf(stderr, "%s: %s\n", g.argv[0], z);
  }
  free(z);
  rcLooping = rc;
  db_force_rollback();
  fossil_exit(rc);
}

/*
** All static variable that a used by only this file are gathered into
** the following structure.
*/
static struct DbLocalData {







<


<
<






<
<
<



|


|

<
<
<
<
<
<
<
<
<
<
|







68
69
70
71
72
73
74

75
76


77
78
79
80
81
82



83
84
85
86
87
88
89
90










91
92
93
94
95
96
97
98
#endif /* INTERFACE */
const struct Stmt empty_Stmt = empty_Stmt_m;

/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){

  va_list ap;
  char *z;


  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
  if( g.json.isJsonMode ){
    json_err( 0, z, 1 );



  }
  else
#endif /* FOSSIL_ENABLE_JSON */
  if( g.xferPanic && g.cgiOutput==1 ){
    cgi_reset_content();
    @ error Database\serror:\s%F(z)
    cgi_reply();
  }










  fossil_panic("Database error: %s", z);
}

/*
** All static variable that a used by only this file are gathered into
** the following structure.
*/
static struct DbLocalData {
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511





512
513
514
515
516
517
518
  db_stats(pStmt);
  rc = sqlite3_reset(pStmt->pStmt);
  db_check_result(rc);
  return rc;
}
int db_finalize(Stmt *pStmt){
  int rc;
  db_stats(pStmt);
  blob_reset(&pStmt->sql);
  rc = sqlite3_finalize(pStmt->pStmt);
  db_check_result(rc);
  pStmt->pStmt = 0;
  if( pStmt->pNext ){
    pStmt->pNext->pPrev = pStmt->pPrev;
  }
  if( pStmt->pPrev ){
    pStmt->pPrev->pNext = pStmt->pNext;
  }else if( db.pAllStmt==pStmt ){
    db.pAllStmt = pStmt->pNext;
  }
  pStmt->pNext = 0;
  pStmt->pPrev = 0;





  return rc;
}

/*
** Return the rowid of the most recent insert
*/
int db_last_insert_rowid(void){







<
<
<
<
<










>
>
>
>
>







474
475
476
477
478
479
480





481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
  db_stats(pStmt);
  rc = sqlite3_reset(pStmt->pStmt);
  db_check_result(rc);
  return rc;
}
int db_finalize(Stmt *pStmt){
  int rc;





  if( pStmt->pNext ){
    pStmt->pNext->pPrev = pStmt->pPrev;
  }
  if( pStmt->pPrev ){
    pStmt->pPrev->pNext = pStmt->pNext;
  }else if( db.pAllStmt==pStmt ){
    db.pAllStmt = pStmt->pNext;
  }
  pStmt->pNext = 0;
  pStmt->pPrev = 0;
  db_stats(pStmt);
  blob_reset(&pStmt->sql);
  rc = sqlite3_finalize(pStmt->pStmt);
  db_check_result(rc);
  pStmt->pStmt = 0;
  return rc;
}

/*
** Return the rowid of the most recent insert
*/
int db_last_insert_rowid(void){
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
){
  const unsigned char *zIn = sqlite3_value_text(argv[0]);
  int nIn = sqlite3_value_bytes(argv[0]);
  unsigned char *zOut;
  if( zIn==0 ) return;
  if( nIn&1 ) return;
  if( !validate16((const char*)zIn, nIn) ) return;
  zOut = sqlite3_malloc64( nIn/2 );
  if( zOut==0 ){
    sqlite3_result_error_nomem(context);
    return;
  }
  decode16(zIn, zOut, nIn);
  sqlite3_result_blob(context, zOut, nIn/2, sqlite3_free);
}







|







966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
){
  const unsigned char *zIn = sqlite3_value_text(argv[0]);
  int nIn = sqlite3_value_bytes(argv[0]);
  unsigned char *zOut;
  if( zIn==0 ) return;
  if( nIn&1 ) return;
  if( !validate16((const char*)zIn, nIn) ) return;
  zOut = sqlite3_malloc64( nIn/2 + 1 );
  if( zOut==0 ){
    sqlite3_result_error_nomem(context);
    return;
  }
  decode16(zIn, zOut, nIn);
  sqlite3_result_blob(context, zOut, nIn/2, sqlite3_free);
}
1010
1011
1012
1013
1014
1015
1016






1017
1018
1019
1020
1021
1022
1023
                          db_now_function, 0, 0);
  sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
                          db_tolocal_function, 0, 0);
  sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
                          db_fromlocal_function, 0, 0);
  sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
                          db_hextoblob, 0, 0);






}

#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
static char *zSavedKey = 0;







>
>
>
>
>
>







994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
                          db_now_function, 0, 0);
  sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
                          db_tolocal_function, 0, 0);
  sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
                          db_fromlocal_function, 0, 0);
  sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
                          db_hextoblob, 0, 0);
  sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
                          0, capability_union_step, capability_union_finalize);
  sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
                          capability_fullcap, 0, 0);
  sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
                          alert_find_emailaddr_func, 0, 0);
}

#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
static char *zSavedKey = 0;
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
    }
  }
  if( file_access(zDbName, R_OK) || file_size(zDbName, ExtFILE)<1024 ){
    if( file_access(zDbName, F_OK) ){
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
      fossil_panic("repository does not exist or"
                   " is in an unreadable directory: %s", zDbName);
    }else if( file_access(zDbName, R_OK) ){
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DENIED;
#endif
      fossil_panic("read permission denied for repository %s", zDbName);
    }else{
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
#endif
      fossil_panic("not a valid repository: %s", zDbName);
    }
  }
  g.zRepositoryName = mprintf("%s", zDbName);
  db_open_or_attach(g.zRepositoryName, "repository");
  g.repositoryOpen = 1;
  sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
                       &g.iRepoDataVers);







|





|




|







1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
    }
  }
  if( file_access(zDbName, R_OK) || file_size(zDbName, ExtFILE)<1024 ){
    if( file_access(zDbName, F_OK) ){
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
      fossil_fatal("repository does not exist or"
                   " is in an unreadable directory: %s", zDbName);
    }else if( file_access(zDbName, R_OK) ){
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DENIED;
#endif
      fossil_fatal("read permission denied for repository %s", zDbName);
    }else{
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
#endif
      fossil_fatal("not a valid repository: %s", zDbName);
    }
  }
  g.zRepositoryName = mprintf("%s", zDbName);
  db_open_or_attach(g.zRepositoryName, "repository");
  g.repositoryOpen = 1;
  sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
                       &g.iRepoDataVers);
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
  }
rep_not_found:
  if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
#ifdef FOSSIL_ENABLE_JSON
    g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
    if( nArgUsed==0 ){
      fossil_panic("use --repository or -R to specify the repository database");
    }else{
      fossil_panic("specify the repository name as a command-line argument");
    }
  }
}

/*
** Return TRUE if the schema is out-of-date
*/







|

|







1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
  }
rep_not_found:
  if( (bFlags & OPEN_OK_NOT_FOUND)==0 ){
#ifdef FOSSIL_ENABLE_JSON
    g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
    if( nArgUsed==0 ){
      fossil_fatal("use --repository or -R to specify the repository database");
    }else{
      fossil_fatal("specify the repository name as a command-line argument");
    }
  }
}

/*
** Return TRUE if the schema is out-of-date
*/
1885
1886
1887
1888
1889
1890
1891

1892
1893
1894
1895
1896
1897
1898
    }
    g.db = 0;
  }
  g.repositoryOpen = 0;
  g.localOpen = 0;
  assert( g.dbConfig==0 );
  assert( g.zConfigDbName==0 );

}

/*
** Close the database as quickly as possible without unnecessary processing.
*/
void db_panic_close(void){
  if( g.db ){







>







1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
    }
    g.db = 0;
  }
  g.repositoryOpen = 0;
  g.localOpen = 0;
  assert( g.dbConfig==0 );
  assert( g.zConfigDbName==0 );
  backoffice_run_if_needed();
}

/*
** Close the database as quickly as possible without unnecessary processing.
*/
void db_panic_close(void){
  if( g.db ){
3031
3032
3033
3034
3035
3036
3037













3038
3039
3040
3041
3042
3043
3044
*/
/*
** SETTING: autosync-tries  width=16 default=1
** If autosync is enabled setting this to a value greater
** than zero will cause autosync to try no more than this
** number of attempts if there is a sync failure.
*/













/*
** SETTING: binary-glob     width=40 versionable block-text
** The VALUE of this setting is a comma or newline-separated list of
** GLOB patterns that should be treated as binary files
** for committing and merging purposes.  Example: *.jpg
*/
#if defined(_WIN32)||defined(__CYGWIN__)||defined(__DARWIN__)







>
>
>
>
>
>
>
>
>
>
>
>
>







3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
*/
/*
** SETTING: autosync-tries  width=16 default=1
** If autosync is enabled setting this to a value greater
** than zero will cause autosync to try no more than this
** number of attempts if there is a sync failure.
*/
/*
** SETTING: backoffice-nodelay boolean default=off
** If backoffice-nodelay is true, then the backoffice processing
** will never invoke sleep().  If it has nothing useful to do,
** it simply exits.
*/
/*
** SETTING: backoffice-logfile width=40
** If backoffice-logfile is not an empty string and is a valid
** filename, then a one-line message is appended to that file
** every time the backoffice runs.  This can be used for debugging,
** to ensure that backoffice is running appropriately.
*/
/*
** SETTING: binary-glob     width=40 versionable block-text
** The VALUE of this setting is a comma or newline-separated list of
** GLOB patterns that should be treated as binary files
** for committing and merging purposes.  Example: *.jpg
*/
#if defined(_WIN32)||defined(__CYGWIN__)||defined(__DARWIN__)

Changes to src/default_css.txt.

182
183
184
185
186
187
188
189

190


191

192
193
194

195



196
197
198
199
200
201
202
}
span.infoTag {
  font-weight: bold;
}
span.wikiTagCancelled {
  text-decoration: line-through;
}
table.browser {

  width: 100%;


  border: 0;

}
td.browser {
  width: 24%;

  vertical-align: top;



}
.filetree {
  margin: 1em 0;
  line-height: 1.5;
}
.filetree > ul {
  display: inline-block;







|
>
|
>
>
|
>

<
<
>
|
>
>
>







182
183
184
185
186
187
188
189
190
191
192
193
194
195
196


197
198
199
200
201
202
203
204
205
206
207
208
}
span.infoTag {
  font-weight: bold;
}
span.wikiTagCancelled {
  text-decoration: line-through;
}
div.columns {
  padding: 0 2em 0 2em;
  max-width: 1000px;
}
div.columns > ul {
  margin: 0;
  padding: 0 0 0 1em;
}


div.columns > ul li:first-child {
  margin-top:0px;
}
div.columns li {
  break-inside: avoid;
}
.filetree {
  margin: 1em 0;
  line-height: 1.5;
}
.filetree > ul {
  display: inline-block;
298
299
300
301
302
303
304



305
306
307
308
309
310
311
table.captcha {
  margin: auto;
  padding: 10px;
  border-width: 4px;
  border-style: double;
  border-color: black;
}



td.login_out_label {
  text-align: center;
}
span.loginError {
  color: red;
}
span.note {







>
>
>







304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
table.captcha {
  margin: auto;
  padding: 10px;
  border-width: 4px;
  border-style: double;
  border-color: black;
}
pre.captcha {
  font-size: 50%;
}
td.login_out_label {
  text-align: center;
}
span.loginError {
  color: red;
}
span.note {
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
  text-align: center;
  padding-right: 15px;
}
td.usetupListCon {
  text-align: left
}
div.ueditCapBox {
  float: left;
  margin-right: 20px;
  margin-bottom: 20px;
}
td.usetupEditLabel {
  text-align: right;
  vertical-align: top;
  white-space: nowrap;







<







356
357
358
359
360
361
362

363
364
365
366
367
368
369
  text-align: center;
  padding-right: 15px;
}
td.usetupListCon {
  text-align: left
}
div.ueditCapBox {

  margin-right: 20px;
  margin-bottom: 20px;
}
td.usetupEditLabel {
  text-align: right;
  vertical-align: top;
  white-space: nowrap;
532
533
534
535
536
537
538




539
540
541
542
543
544
545
  word-wrap: break-word;
}
pre.th1error {
  white-space: pre-wrap;
  word-wrap: break-word;
  color: red;
}




.statistics-report-graph-line {
  background-color: #446979;
}
.statistics-report-table-events th {
  padding: 0 1em 0 1em;
}
.statistics-report-table-events td {







>
>
>
>







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
  word-wrap: break-word;
}
pre.th1error {
  white-space: pre-wrap;
  word-wrap: break-word;
  color: red;
}
pre.textPlain {
  white-space: pre-wrap;
  word-wrap: break-word;
}
.statistics-report-graph-line {
  background-color: #446979;
}
.statistics-report-table-events th {
  padding: 0 1em 0 1em;
}
.statistics-report-table-events td {
670
671
672
673
674
675
676












































div.forum_body p {
  margin-top: 0;
}
td.form_label {
  vertical-align: top;
  text-align: right;
}



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
div.forum_body p {
  margin-top: 0;
}
td.form_label {
  vertical-align: top;
  text-align: right;
}
.debug {
  background-color: #ffc;
  border: 2px solid #ff0;
}
div.forumEdit {
  border: 1px solid black;
  padding-left: 1ex;
  padding-right: 1ex;
}
div.forumHier, div.forumTime {
  border: 1px solid black;
  padding-left: 1ex;
  padding-right: 1ex;
  margin-top: 1ex;
}
div.forumSel {
  background-color: #cef;
}
div.forumObs {
  color: #bbb;
}
#capabilitySummary {
  text-align: center;
}
#capabilitySummary td {
  padding-left: 3ex;
  padding-right: 3ex;
}
#capabilitySummary th {
  padding-left: 1ex;
  padding-right: 1ex;
}
.capsumOff {
  background-color: #bbb;
}
.capsumRead {
  background-color: #bfb;
}
.capsumWrite {
  background-color: #ffb;
}
label {
  white-space: nowrap;
}

Changes to src/dispatch.c.

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
  if( z[i]=='?' ){
    z[i] = 0;
    zQ = &z[i+1];
  }else{
    zQ = &z[i];
  }
  if( dispatch_name_search(z, CMDFLAG_WEBPAGE, ppCmd) ){
    fossil_panic("\"%s\" aliased to \"%s\" but \"%s\" does not exist",
                 zName, z, z);
  }
  z = zQ;
  while( *z ){
    char *zName = z;
    char *zValue = 0;
    while( *z && *z!='=' && *z!='&' && *z!='!' ){ z++; }







|







165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
  if( z[i]=='?' ){
    z[i] = 0;
    zQ = &z[i+1];
  }else{
    zQ = &z[i];
  }
  if( dispatch_name_search(z, CMDFLAG_WEBPAGE, ppCmd) ){
    fossil_fatal("\"%s\" aliased to \"%s\" but \"%s\" does not exist",
                 zName, z, z);
  }
  z = zQ;
  while( *z ){
    char *zName = z;
    char *zValue = 0;
    while( *z && *z!='=' && *z!='&' && *z!='!' ){ z++; }
347
348
349
350
351
352
353
354
355
356
357
358
359

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421

422
423
424
425
426




427


428




429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
      }else{
        @ <blockquote>
        help_to_html(pCmd->zHelp, cgi_output_blob());
        @ </blockquote>
      }
    }
  }else{
    int i, j, n;

    style_header("Help");

    @ <a name='commands'></a>
    @ <h1>Available commands:</h1>

    @ <table border="0"><tr>
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue;
      j++;
    }
    n = (j+5)/6;
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      const char *zBoldOn  = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :"";
      const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":"";
      if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue;
      if( j==0 ){
        @ <td valign="top"><ul>
      }
      @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a></li>
      j++;
      if( j>=n ){
        @ </ul></td>
        j = 0;
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

    @ <a name='webpages'></a>
    @ <h1>Available web UI pages:</h1>

    @ <table border="0"><tr>
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;
      j++;
    }
    n = (j+4)/5;
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;
      if( j==0 ){
        @ <td valign="top"><ul>
      }
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
      }else{
        @ <li>%s(z+1)</li>
      }
      j++;
      if( j>=n ){
        @ </ul></td>
        j = 0;
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

    @ <a name='unsupported'></a>
    @ <h1>Unsupported commands:</h1>

    @ <table border="0"><tr>
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( strncmp(z,"test",4)!=0 ) continue;
      j++;




    }


    n = (j+3)/4;




    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( strncmp(z,"test",4)!=0 ) continue;
      if( j==0 ){
        @ <td valign="top"><ul>
      }
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
      j++;
      if( j>=n ){
        @ </ul></td>
        j = 0;
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

    @ <a name='settings'></a>
    @ <h1>Settings:</h1>
    @ <table border="0"><tr>
    for(i=j=0; i<MX_COMMAND; i++){
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
      j++;
    }
    n = (j+4)/5;
    for(i=j=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
      if( j==0 ){
        @ <td valign="top"><ul>
      }
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
      j++;
      if( j>=n ){
        @ </ul></td>
        j = 0;
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

  }
  style_footer();
}

/*
** WEBPAGE: test-all-help







|





>
|
|
<
<
<
<
<
<
<





<
<
<

<
<
<
<
|
<
<
|
<
<



>
|
|


<
<
<
<
<
<
<
<
<





<
<
<
<
|
<
<
|
|
<
<


>
|
|


<
>
>
>
>
|
>
>
|
>
>
>
>
|

|
<
<
<





<
<
<
<
|
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362







363
364
365
366
367



368




369


370


371
372
373
374
375
376
377
378









379
380
381
382
383




384


385
386


387
388
389
390
391
392
393

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408



409
410
411
412
413




414


415
































416
417
418
419
420
421
422
      }else{
        @ <blockquote>
        help_to_html(pCmd->zHelp, cgi_output_blob());
        @ </blockquote>
      }
    }
  }else{
    int i;

    style_header("Help");

    @ <a name='commands'></a>
    @ <h1>Available commands:</h1>
    @ <div class="columns" style="column-width: 12ex;">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){







      const char *z = aCommand[i].zName;
      const char *zBoldOn  = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :"";
      const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":"";
      if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue;



      @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a></li>




    }


    @ </ui></div>



    @ <a name='webpages'></a>
    @ <h1>Available web UI pages:</h1>
    @ <div class="columns" style="column-width: 18ex;">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;









      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
      }else{
        @ <li>%s(z+1)</li>
      }




    }


    @ </ul></div>



    @ <a name='unsupported'></a>
    @ <h1>Unsupported commands:</h1>
    @ <div class="columns" style="column-width: 20ex;">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( strncmp(z,"test",4)!=0 ) continue;

      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
    }
    @ </ul></div>

    @ <a name='settings'></a>
    @ <h1>Settings:</h1>
    @ <div class="columns" style="column-width: 20ex;">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;



      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }




    }


    @ </ul></div>

































  }
  style_footer();
}

/*
** WEBPAGE: test-all-help

Changes to src/doc.c.

465
466
467
468
469
470
471




472
473
474
475
476
477
478
** Look for a file named zName in the check-in with RID=vid.  Load the content
** of that file into pContent and return the RID for the file.  Or return 0
** if the file is not found or could not be loaded.
*/
int doc_load_content(int vid, const char *zName, Blob *pContent){
  int writable = db_is_writeable("repository");
  int rid;   /* The RID of the file being loaded */




  if( !db_table_exists("repository", "vcache") || !writable ){
    db_multi_exec(
      "CREATE %s TABLE IF NOT EXISTS vcache(\n"
      "  vid INTEGER,         -- check-in ID\n"
      "  fname TEXT,          -- filename\n"
      "  rid INTEGER,         -- artifact ID\n"
      "  PRIMARY KEY(vid,fname)\n"







>
>
>
>







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
** Look for a file named zName in the check-in with RID=vid.  Load the content
** of that file into pContent and return the RID for the file.  Or return 0
** if the file is not found or could not be loaded.
*/
int doc_load_content(int vid, const char *zName, Blob *pContent){
  int writable = db_is_writeable("repository");
  int rid;   /* The RID of the file being loaded */
  if( writable ){
    db_end_transaction(0);
    db_begin_write();
  }
  if( !db_table_exists("repository", "vcache") || !writable ){
    db_multi_exec(
      "CREATE %s TABLE IF NOT EXISTS vcache(\n"
      "  vid INTEGER,         -- check-in ID\n"
      "  fname TEXT,          -- filename\n"
      "  rid INTEGER,         -- artifact ID\n"
      "  PRIMARY KEY(vid,fname)\n"

Changes to src/forum.c.

18
19
20
21
22
23
24



25




26
27
28












29









30
31



32








33
34
35
36
37









38
39





40

41



42
43
44



45

46
47






48



49









50





























51




































52
53
54



55
56




57
58







59



60

































61












































































































62
63
64
65
66
67
68
69


70

71




72



















73


























74










75

76


77

78
79


80







81

82
83
84


85




86
87
88
89




90

91
92

93











94


95
96
97





























98
99
100



101




102






103

104









105









106
107





108


109












110
111
112





113



114
115
116
117

118




119
120



121













122







123


124




125






126























































127


128
129
130
131
132









133


134







135



136



137
138

139





140

































141

142







143




144

145
146
147















148

149

150
151





152









153

154









































155
156

157




158













159


160

161
162
163
164



165












166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256

257
258






















259

260









261












262
263

264


265


266
267

268
269


270

271
272
273
274


275

276


277
278
279

280
281

282
283
284






285


286
287
288





289


290
291
292
293
294
295
296
297

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
** This file contains code used to generate the user forum.
*/
#include "config.h"
#include <assert.h>
#include "forum.h"

/*



** The schema for the tables that manage the forum, if forum is




** enabled.
*/
static const char zForumInit[] = 












@ CREATE TABLE repository.forumpost(









@   mpostid INTEGER PRIMARY KEY,  -- unique id for each post (local)
@   mposthash TEXT,               -- uuid for this post



@   mthreadid INTEGER,            -- thread to which this post belongs








@   uname TEXT,                   -- name of user
@   mtime REAL,                   -- julian day number
@   mstatus TEXT,                 -- status.  NULL=ok. 'mod'=pending moderation
@   mimetype TEXT,                -- Mimetype for mbody
@   ipaddr TEXT,                  -- IP address of post origin









@   inreplyto INT,                -- Parent posting
@   mbody TEXT                    -- Content of the post





@ );

@ CREATE INDEX repository.forumpost_x1 ON



@   forumpost(inreplyto,mtime);
@ CREATE TABLE repository.forumthread(
@   mthreadid INTEGER PRIMARY KEY,



@   mthreadhash TEXT,             -- uuid for this thread

@   mtitle TEXT,                  -- Title or subject line
@   mtime REAL,                   -- Most recent update






@   npost INT                     -- Number of posts on this thread



@ );









;


































































/*
** Create the forum tables in the schema if they do not already
** exist.



*/
static void forum_verify_schema(void){




  if( !db_table_exists("repository","forumpost") ){
    db_multi_exec(zForumInit /*works-like:""*/);







  }



}














































































































































/*
** WEBPAGE: forum
** URL: /forum
** Query parameters:
**
**    item=N             Show post N and its replies
**    
*/


void forum_page(void){

  int itemId;




  Stmt q;



















  int i;





































  login_check_credentials();

  if( !g.perm.RdForum ){ login_needed(g.anon.RdForum); return; }


  forum_verify_schema();

  style_header("Forum");
  itemId = atoi(PD("item","0"));


  if( itemId>0 ){







    int iUp;

    double rNow;
    style_submenu_element("Topics", "%R/forum");
    iUp = db_int(0, "SELECT inreplyto FROM forumpost WHERE mpostid=%d", itemId);


    if( iUp ){




      style_submenu_element("Parent", "%R/forum?item=%d", iUp);
    }
    rNow = db_double(0.0, "SELECT julianday('now')");
    /* Show the post given by itemId and all its descendents */




    db_prepare(&q,

      "WITH RECURSIVE"
      " post(id,uname,mstat,mime,ipaddr,parent,mbody,depth,mtime) AS ("

      "    SELECT mpostid, uname, mstatus, mimetype, ipaddr, inreplyto, mbody,"











      "           0, mtime FROM forumpost WHERE mpostid=%d"


      "  UNION"
      "  SELECT f.mpostid, f.uname, f.mstatus, f.mimetype, f.ipaddr,"
      "         f.inreplyto, f.mbody, p.depth+1 AS xdepth, f.mtime AS xtime"





























      "    FROM forumpost AS f, post AS p"
      "   WHERE f.inreplyto=p.id"
      "   ORDER BY xdepth DESC, xtime ASC"



      ") SELECT * FROM post;",




      itemId






    );

    while( db_step(&q)==SQLITE_ROW ){









      int id = db_column_int(&q, 0);









      const char *zUser = db_column_text(&q, 1);
      const char *zMime = db_column_text(&q, 3);





      int iDepth = db_column_int(&q, 7);


      double rMTime = db_column_double(&q, 8);












      char *zAge = db_timespan_name(rNow - rMTime);
      Blob body;
      @ <!-- Forum post %d(id) -->





      @ <table class="forum_post">



      @ <tr>
      @ <td class="forum_margin" width="%d(iDepth*40)" rowspan="2">
      @ <td><span class="forum_author">%h(zUser)</span>
      @ <span class="forum_age">%s(zAge) ago</span>

      sqlite3_free(zAge);




      if( g.perm.WrForum ){
        @ <span class="forum_buttons">



        if( g.perm.AdminForum || fossil_strcmp(g.zLogin, zUser)==0 ){













          @ <a href='%R/forumedit?item=%d(id)'>Edit</a>







        }


        @ <a href='%R/forumedit?replyto=%d(id)'>Reply</a>




        @ </span>






      }























































      @ </tr>


      @ <tr><td><div class="forum_body">
      blob_init(&body, db_column_text(&q,6), db_column_bytes(&q,6));
      wiki_render_by_mimetype(&body, zMime);
      blob_reset(&body);
      @ </div></td></tr>









      @ </table>


    }







  }else{



    /* If we reach this point, that means the users wants a list of



    ** recent threads.
    */

    i = 0;





    db_prepare(&q,

































      "SELECT a.mtitle, a.npost, b.mpostid"

      "  FROM forumthread AS a, forumpost AS b "







      " WHERE a.mthreadid=b.mthreadid"




      "   AND b.inreplyto IS NULL"

      " ORDER BY a.mtime DESC LIMIT 40"
    );
    if( g.perm.WrForum ){















      style_submenu_element("New", "%R/forumedit");

    }

    @ <h1>Recent Forum Threads</h1>
    while( db_step(&q)==SQLITE_ROW ){





      int n = db_column_int(&q,1);









      int itemid = db_column_int(&q,2);

      const char *zTitle = db_column_text(&q,0);









































      if( (i++)==0 ){
        @ <ol>

      }




      @ <li><span class="forum_title">













      @ %z(href("%R/forum?item=%d",itemid))%h(zTitle)</a></span>


      @ <span class="forum_npost">%d(n) post%s(n==1?"":"s")</span></li>

    }
    if( i ){
      @ </ol>
    }



  }












  style_footer();
}

/*
** Use content in CGI parameters "s" (subject), "b" (body), and
** "mimetype" (mimetype) to create a new forum entry.
** Return the id of the new forum entry.
**
** If any problems occur, return 0 and set *pzErr to a description of
** the problem.
**
** Cases:
**
**    itemId==0 && parentId==0        Starting a new thread.
**    itemId==0 && parentId>0         New reply to parentId
**    itemId>0 && parentId==0         Edit existing post itemId
*/
static int forum_post(int itemId, int parentId, char **pzErr){
  const char *zSubject = 0;
  int threadId;
  double rNow = db_double(0.0, "SELECT julianday('now')");
  const char *zMime = wiki_filter_mimetypes(P("mimetype"));
  if( itemId==0 && parentId==0 ){
    /* Start a new thread.  Subject required. */
    sqlite3_uint64 r1, r2;
    zSubject = PT("s");
    if( zSubject==0 || zSubject[0]==0 ){
      *pzErr = "\"Subject\" required to start a new thread";
      return 0;
    }
    sqlite3_randomness(sizeof(r1), &r1);
    sqlite3_randomness(sizeof(r2), &r2);
    db_multi_exec(
      "INSERT INTO forumthread(mthreadhash, mtitle, mtime, npost)"
      "VALUES(lower(hex(randomblob(28))),%Q,%!.17g,1)",
      zSubject, rNow
    );
    threadId = db_last_insert_rowid();
  }else{
    threadId = db_int(0, "SELECT mthreadid FROM forumpost"
                         " WHERE mpostid=%d", itemId ? itemId : parentId);
  }
  if( itemId ){
    if( db_int(0, "SELECT inreplyto IS NULL FROM forumpost"
                  " WHERE mpostid=%d", itemId) ){
      db_multi_exec(
        "UPDATE forumthread SET mtitle=%Q WHERE mthreadid=%d",
        PT("s"), threadId
      );
    }
    db_multi_exec(
       "UPDATE forumpost SET"
       " mtime=%!.17g,"
       " mimetype=%Q,"
       " ipaddr=%Q,"
       " mbody=%Q"
       " WHERE mpostid=%d",
       rNow, PT("mimetype"), P("REMOTE_ADDR"), PT("b"), itemId
    );
  }else{
    db_multi_exec(
       "INSERT INTO forumpost(mposthash,mthreadid,uname,mtime,"
       "  mstatus,mimetype,ipaddr,inreplyto,mbody) VALUES"
       "  (lower(hex(randomblob(28))),%d,%Q,%!.17g,%Q,%Q,%Q,nullif(%d,0),%Q)",
       threadId,g.zLogin,rNow,NULL,zMime,P("REMOTE_ADDR"),parentId,P("b"));
    itemId = db_last_insert_rowid();
  }
  if( zSubject==0 ){
    db_multi_exec(
      "UPDATE forumthread SET mtime=%!.17g, npost=npost+1"
      " WHERE mthreadid=(SELECT mthreadid FROM forumpost WHERE mpostid=%d)",
      rNow, itemId
    );
  }
  return itemId;
}

/*
** WEBPAGE: forumedit
**
** Query parameters:
**
**    replyto=N      Enter a reply to forum item N
**    item=N         Edit item N
**    s=SUBJECT      Subject. New thread only. Omitted for replies
**    b=BODY         Body of the post
**    m=MIMETYPE     Mimetype for the body of the post
**    x              Submit changes
**    p              Preview changes
*/
void forum_edit_page(void){

  int itemId;
  int parentId;






















  char *zErr = 0;

  const char *zMime;









  const char *zSub;













  login_check_credentials();

  if( !g.perm.WrForum ){ login_needed(g.anon.WrForum); return; }


  forum_verify_schema();


  itemId = atoi(PD("item","0"));
  parentId = atoi(PD("replyto","0"));

  if( P("cancel")!=0 ){
    cgi_redirectf("%R/forum?item=%d", itemId ? itemId : parentId);


    return;

  }
  if( P("x")!=0 && cgi_csrf_safe(1) ){
    itemId = forum_post(itemId,parentId,&zErr);
    if( itemId ){


      cgi_redirectf("%R/forum?item=%d",itemId);

      return;


    }
  }
  if( itemId && (P("mimetype")==0 || P("b")==0) ){

    Stmt q;
    db_prepare(&q, "SELECT mimetype, mbody FROM forumpost"

                   " WHERE mpostid=%d", itemId);
    if( db_step(&q)==SQLITE_ROW ){
      if( P("mimetype")==0 ){






        cgi_set_query_parameter("mimetype", db_column_text(&q, 0));


      }
      if( P("b")==0 ){
        cgi_set_query_parameter("b", db_column_text(&q, 1));





      }


    }
    db_finalize(&q);
  }
  zMime = wiki_filter_mimetypes(P("mimetype"));
  if( itemId>0 ){
    style_header("Edit Forum Post");
  }else if( parentId>0 ){
    style_header("Comment On Forum Post");

  }else{
    style_header("New Forum Thread");
  }
  @ <form action="%R/forumedit" method="POST">
  if( itemId ){
    @ <input type="hidden" name="item" value="%d(itemId)">
  }
  if( parentId ){
    @ <input type="hidden" name="replyto" value="%d(parentId)">
  }
  if( P("p") ){
    Blob x;
    @ <div class="forumpreview">
    if( P("s") ){
      @ <h1>%h(PT("s"))</h1>
    }
    @ <div class="forumpreviewbody">
    blob_init(&x, PT("b"), -1);
    wiki_render_by_mimetype(&x, PT("mimetype"));
    blob_reset(&x);
    @ </div>
    @ </div>
    @ <hr>
  }
  @ <table border="0" class="forumeditform"> 
  if( zErr ){
    @ <tr><td colspan="2">
    @ <span class='forumFormErr'>%h(zErr)</span>
  }
  if( (itemId==0 && parentId==0)
   || (itemId && db_int(0, "SELECT inreplyto IS NULL FROM forumpost"
                           " WHERE mpostid=%d", itemId))
  ){
    zSub = PT("s");
    if( zSub==0 && itemId ){
      zSub = db_text("",
         "SELECT mtitle FROM forumthread"
         " WHERE mthreadid=(SELECT mthreadid FROM forumpost"
                          "  WHERE mpostid=%d)", itemId);
    }
    @ <tr><td>Subject:</td>
    @ <td><input type='text' class='forumFormSubject' name='s' value='%h(zSub)'>
  }
  @ <tr><td>Markup:</td><td>
  mimetype_option_menu(zMime);
  @ <tr><td>Comment:</td><td>
  @ <textarea name="b" class="wikiedit" cols="80"\
  @  rows="20" wrap="virtual">%h(PD("b",""))</textarea></td>
  @ <tr><td></td><td>
  @ <input type="submit" name="p" value="Preview">
  if( P("p")!=0 ){
    @ <input type="submit" name="x" value="Submit">
  }
  @ <input type="submit" name="cancel" value="Cancel">
  @ </table>
  @ </form>
  style_footer();
}







>
>
>
|
>
>
>
>
|

|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
|
>
>
>
|
>
>
>
>
>
>
>
>
|
<
<
<
<
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>
>
|
>
|
>
>
>
|
|
|
>
>
>
|
>
|
<
>
>
>
>
>
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
|
>
>
>

|
>
>
>
>
|
|
>
>
>
>
>
>
>

>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
|
<
<
<
<

>
>
|
>
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
|
>
|
<
>
>
|
>
>
>
>
>
>
>
|
>
|
<
<
>
>
|
>
>
>
>
|

<
<
>
>
>
>
|
>
|
<
>
|
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
<
>
>
>
|
>
>
>
>
|
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
>
>
>
>
|
>
>
>
|
<
<
<
>
|
>
>
>
>
|
<
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
|
|
|
|
>
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>

>
>
>
|
>
>
>
|
|
>
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
|
>
>
>
>
|
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>

>
|
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
>
|
|
|
|
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>




<
<
|

<
|
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<



<
|
<
<
<
|
<

|
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
|
>
>
|
>
>
|
|
>
|
<
>
>
|
>
|
<
|
|
>
>
|
>
|
>
>
|
|
<
>
|
|
>
|
<
|
>
>
>
>
>
>
|
>
>

|
<
>
>
>
>
>

>
>



<
|
<
<
<
>

<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72




73
74
75
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

359




360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
443
444
445
446
447
448


449
450
451
452
453
454
455
456
457


458
459
460
461
462
463
464

465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481


482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512

513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585



586
587
588
589
590
591
592

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918


919
920

921









922
























































923


924
925
926

927



928

929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993

994
995
996
997
998

999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009

1010
1011
1012
1013
1014

1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026

1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037

1038



1039
1040













1041
1042








































1043
1044
** This file contains code used to generate the user forum.
*/
#include "config.h"
#include <assert.h>
#include "forum.h"

/*
** Default to using Markdown markup
*/
#define DEFAULT_FORUM_MIMETYPE  "text/x-markdown"

#if INTERFACE
/*
** Each instance of the following object represents a single message - 
** either the initial post, an edit to a post, a reply, or an edit to
** a reply.
*/
struct ForumEntry {
  int fpid;              /* rid for this entry */
  int fprev;             /* zero if initial entry.  non-zero if an edit */
  int firt;              /* This entry replies to firt */
  int mfirt;             /* Root in-reply-to */
  char *zUuid;           /* Artifact hash */
  ForumEntry *pLeaf;     /* Most recent edit for this entry */
  ForumEntry *pEdit;     /* This entry is an edit of pEditee */
  ForumEntry *pNext;     /* Next in chronological order */
  ForumEntry *pPrev;     /* Previous in chronological order */
  ForumEntry *pDisplay;  /* Next in display order */
  int nIndent;           /* Number of levels of indentation for this entry */
};

/*
** A single instance of the following tracks all entries for a thread.
*/
struct ForumThread {
  ForumEntry *pFirst;    /* First entry in chronological order */
  ForumEntry *pLast;     /* Last entry in chronological order */
  ForumEntry *pDisplay;  /* Entries in display order */
  ForumEntry *pTail;     /* Last on the display list */
};
#endif /* INTERFACE */

/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
  ForumEntry *pEntry, *pNext;
  for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
    pNext = pEntry->pNext;
    fossil_free(pEntry->zUuid);
    fossil_free(pEntry);
  }
  fossil_free(pThread);
}





#if 0 /* not used */
/*
** Search a ForumEntry list forwards looking for the entry with fpid
*/
static ForumEntry *forumentry_forward(ForumEntry *p, int fpid){
  while( p && p->fpid!=fpid ) p = p->pNext;
  return p;
}
#endif


/*
** Search backwards for a ForumEntry
*/
static ForumEntry *forumentry_backward(ForumEntry *p, int fpid){
  while( p && p->fpid!=fpid ) p = p->pPrev;
  return p;
}

/*
** Add an entry to the display list
*/
static void forumentry_add_to_display(ForumThread *pThread, ForumEntry *p){
  if( pThread->pDisplay==0 ){
    pThread->pDisplay = p;
  }else{
    pThread->pTail->pDisplay = p;
  }
  pThread->pTail = p;
}


/*
** Extend the display list for pThread by adding all entries that
** reference fpid.  The first such entry will be no earlier then
** entry "p".
*/
static void forumthread_display_order(
  ForumThread *pThread,
  ForumEntry *p,
  int fpid,
  int nIndent
){
  while( p ){
    if( p->fprev==0 && p->mfirt==fpid ){
      p->nIndent = nIndent;
      forumentry_add_to_display(pThread, p);
      forumthread_display_order(pThread, p->pNext, p->fpid, nIndent+1);
    }
    p = p->pNext;
  }
}

/*
** Construct a ForumThread object given the root record id.
*/
static ForumThread *forumthread_create(int froot, int computeHierarchy){
  ForumThread *pThread;
  ForumEntry *pEntry;
  Stmt q;
  pThread = fossil_malloc( sizeof(*pThread) );
  memset(pThread, 0, sizeof(*pThread));
  db_prepare(&q,
     "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
     "  FROM forumpost"
     " WHERE froot=%d ORDER BY fmtime",
     froot
  );
  while( db_step(&q)==SQLITE_ROW ){
    pEntry = fossil_malloc( sizeof(*pEntry) );
    memset(pEntry, 0, sizeof(*pEntry));
    pEntry->fpid = db_column_int(&q, 0);
    pEntry->firt = db_column_int(&q, 1);
    pEntry->fprev = db_column_int(&q, 2);
    pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
    pEntry->mfirt = pEntry->firt;
    pEntry->pPrev = pThread->pLast;
    pEntry->pNext = 0;
    if( pThread->pLast==0 ){
      pThread->pFirst = pEntry;
    }else{
      pThread->pLast->pNext = pEntry;
    }
    pThread->pLast = pEntry;
  }
  db_finalize(&q);

  /* Establish which entries are the latest edit.  After this loop
  ** completes, entries that have non-NULL pLeaf should not be
  ** displayed.
  */
  for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
    if( pEntry->fprev ){
      ForumEntry *pBase = 0, *p;
      p = forumentry_backward(pEntry->pPrev, pEntry->fprev);
      pEntry->pEdit = p;
      while( p ){
        pBase = p;
        p->pLeaf = pEntry;
        p = pBase->pEdit;
      }
      for(p=pEntry->pNext; p; p=p->pNext){
        if( p->mfirt==pEntry->fpid ) p->mfirt = pBase->fpid;
      }
    }
  }

  if( computeHierarchy ){
    /* Compute the hierarchical display order */
    pEntry = pThread->pFirst;
    pEntry->nIndent = 1;
    forumentry_add_to_display(pThread, pEntry);
    forumthread_display_order(pThread, pEntry, pEntry->fpid, 2);
  }

  /* Return the result */
  return pThread;
}

/*
** COMMAND: test-forumthread
**
** Usage: %fossil test-forumthread THREADID
**
** Display a summary of all messages on a thread.
*/
void forumthread_cmd(void){
  int fpid;
  int froot;
  const char *zName;
  ForumThread *pThread;
  ForumEntry *p;

  db_find_and_open_repository(0,0);
  verify_all_options();
  if( g.argc!=3 ) usage("THREADID");
  zName = g.argv[2];
  fpid = symbolic_name_to_rid(zName, "f");
  if( fpid<=0 ){
    fossil_fatal("Unknown or ambiguous forum id: \"%s\"", zName);
  }
  froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
  if( froot==0 ){
    fossil_fatal("Not a forum post: \"%s\"", zName);
  }
  fossil_print("fpid  = %d\n", fpid);
  fossil_print("froot = %d\n", froot);
  pThread = forumthread_create(froot, 1);
  fossil_print("Chronological:\n");
           /*   123456789 123456789 123456789 123456789 123456789  */
  fossil_print("     fpid      firt     fprev     mfirt     pLeaf\n");
  for(p=pThread->pFirst; p; p=p->pNext){
    fossil_print("%9d %9d %9d %9d %9d\n",
       p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0);
  }
  fossil_print("\nDisplay\n");
  for(p=pThread->pDisplay; p; p=p->pDisplay){
    fossil_print("%*s", (p->nIndent-1)*3, "");
    if( p->pLeaf ){
      fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid);
    }else{
      fossil_print("%d\n", p->fpid);
    }
  }
  forumthread_delete(pThread);
}

/*
** Render a forum post for display
*/
void forum_render(
  const char *zTitle,         /* The title.  Might be NULL for no title */
  const char *zMimetype,      /* Mimetype of the message */
  const char *zContent,       /* Content of the message */
  const char *zClass          /* Put in a <div> if not NULL */
){
  if( zClass ){
    @ <div class='%s(zClass)'>
  }
  if( zTitle ){
    if( zTitle[0] ){
      @ <h1>%h(zTitle)</h1>
    }else{
      @ <h1><i>Deleted</i></h1>
    }
  }
  if( zContent && zContent[0] ){
    Blob x;
    blob_init(&x, 0, 0);
    blob_append(&x, zContent, -1);
    wiki_render_by_mimetype(&x, zMimetype);
    blob_reset(&x);
  }else{
    @ <i>Deleted</i>
  }
  if( zClass ){
    @ </div>
  }
}

/*
** Display all posts in a forum thread in chronological order
*/
static void forum_display_chronological(int froot, int target){
  ForumThread *pThread = forumthread_create(froot, 0);
  ForumEntry *p;
  int notAnon = login_is_individual();
  for(p=pThread->pFirst; p; p=p->pNext){
    char *zDate;
    Manifest *pPost;
    int isPrivate;        /* True for posts awaiting moderation */
    int sameUser;         /* True if author is also the reader */

    pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
    if( pPost==0 ) continue;
    if( p->fpid==target ){
      @ <div id="forum%d(p->fpid)" class="forumTime forumSel">
    }else if( p->pLeaf!=0 ){
      @ <div id="forum%d(p->fpid)" class="forumTime forumObs">
    }else{
      @ <div id="forum%d(p->fpid)" class="forumTime">
    }
    if( pPost->zThreadTitle ){
      @ <h1>%h(pPost->zThreadTitle)</h1>
    }
    zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
    @ <p>By %h(pPost->zUser) on %h(zDate) (%d(p->fpid))
    fossil_free(zDate);
    if( p->pEdit ){
      @ edit of %z(href("%R/forumpost/%S?t",p->pEdit->zUuid))%d(p->fprev)</a>
    }
    if( p->firt ){
      ForumEntry *pIrt = p->pPrev;
      while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
      if( pIrt ){
        @ reply to %z(href("%R/forumpost/%S?t",pIrt->zUuid))%d(p->firt)</a>
      }
    }
    if( p->pLeaf ){
      @ updated by %z(href("%R/forumpost/%S?t",p->pLeaf->zUuid))\
      @ %d(p->pLeaf->fpid)</a>
    }
    if( g.perm.Debug ){
      @ <span class="debug">\
      @ <a href="%R/artifact/%h(p->zUuid)">artifact</a></span>
    }
    if( p->fpid!=target ){
      @ %z(href("%R/forumpost/%S?t",p->zUuid))[link]</a>
    }
    isPrivate = content_is_private(p->fpid);
    sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
    if( isPrivate && !g.perm.ModForum && !sameUser ){
      @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
    }else{
      forum_render(0, pPost->zMimetype, pPost->zWiki, 0);
    }
    if( g.perm.WrForum && p->pLeaf==0 ){
      int sameUser = login_is_individual()
                     && fossil_strcmp(pPost->zUser, g.zLogin)==0;
      @ <p><form action="%R/forumedit" method="POST">
      @ <input type="hidden" name="fpid" value="%s(p->zUuid)">
      if( !isPrivate ){
        /* Reply and Edit are only available if the post has already
        ** been approved */
        @ <input type="submit" name="reply" value="Reply">
        if( g.perm.Admin || sameUser ){
          @ <input type="submit" name="edit" value="Edit">
          @ <input type="submit" name="nullout" value="Delete">
        }
      }else if( g.perm.ModForum ){
        /* Provide moderators with moderation buttons for posts that
        ** are pending moderation */
        @ <input type="submit" name="approve" value="Approve">
        @ <input type="submit" name="reject" value="Reject">
      }else if( sameUser ){
        /* A post that is pending moderation can be deleted by the
        ** person who originally submitted the post */
        @ <input type="submit" name="reject" value="Delete">
      }
      @ </form></p>
    }
    manifest_destroy(pPost);
    @ </div>
  }
  forumthread_delete(pThread);
}

/*

** Display all messages in a forumthread with indentation.




*/
static int forum_display_hierarchical(int froot, int target){
  ForumThread *pThread;
  ForumEntry *p;
  Manifest *pPost, *pOPost;
  int fpid;
  const char *zUuid;
  char *zDate;
  const char *zSel;
  int notAnon = login_is_individual();

  pThread = forumthread_create(froot, 1);
  for(p=pThread->pFirst; p; p=p->pNext){
    if( p->fpid==target ){
      while( p->pEdit ) p = p->pEdit;
      target = p->fpid;
      break;
    }
  }
  for(p=pThread->pDisplay; p; p=p->pDisplay){
    int isPrivate;         /* True for posts awaiting moderation */
    int sameUser;          /* True if reader is also the poster */
    pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
    if( p->pLeaf ){
      fpid = p->pLeaf->fpid;
      zUuid = p->pLeaf->zUuid;
      pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
    }else{
      fpid = p->fpid;
      zUuid = p->zUuid;
      pPost = pOPost;
    }
    zSel = p->fpid==target ? " forumSel" : "";
    if( p->nIndent==1 ){
      @ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
    }else{
      @ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
      @ style='margin-left: %d((p->nIndent-1)*3)ex;'>
    }
    pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
    if( pPost==0 ) continue;
    if( pPost->zThreadTitle ){
      @ <h1>%h(pPost->zThreadTitle)</h1>
    }
    zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
    @ <p>By %h(pOPost->zUser) on %h(zDate)
    fossil_free(zDate);
    if( g.perm.Debug ){
      @ <span class="debug">\
      @ <a href="%R/artifact/%h(p->zUuid)">(%d(p->fpid))</a></span>
    }
    if( p->pLeaf ){
      zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
      if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
        @ and edited on %h(zDate)
      }else{
        @ as edited by %h(pPost->zUser) on %h(zDate)
      }
      fossil_free(zDate);
      if( g.perm.Debug ){
        @ <span class="debug">\
        @ <a href="%R/artifact/%h(p->pLeaf->zUuid)">(%d(fpid))</a></span>
      }
      manifest_destroy(pOPost);
    }
    if( fpid!=target ){
      @ %z(href("%R/forumpost/%S",zUuid))[link]</a>
    }
    isPrivate = content_is_private(fpid);
    sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
    if( isPrivate && !g.perm.ModForum && !sameUser ){
      @ <p><span class="modpending">Awaiting Moderator Approval</span></p>
    }else{
      forum_render(0, pPost->zMimetype, pPost->zWiki, 0);
    }
    if( g.perm.WrForum ){

      @ <p><form action="%R/forumedit" method="POST">
      @ <input type="hidden" name="fpid" value="%s(zUuid)">
      if( !isPrivate ){
        /* Reply and Edit are only available if the post has already
        ** been approved */
        @ <input type="submit" name="reply" value="Reply">
        if( g.perm.Admin || sameUser ){
          @ <input type="submit" name="edit" value="Edit">
          @ <input type="submit" name="nullout" value="Delete">
        }
      }else if( g.perm.ModForum ){
        /* Provide moderators with moderation buttons for posts that
        ** are pending moderation */


        @ <input type="submit" name="approve" value="Approve">
        @ <input type="submit" name="reject" value="Reject">
      }else if( sameUser ){
        /* A post that is pending moderation can be deleted by the
        ** person who originally submitted the post */
        @ <input type="submit" name="reject" value="Delete">
      }
      @ </form></p>
    }


    manifest_destroy(pPost);
    @ </div>
  }
  forumthread_delete(pThread);
  return target;
}


/*
** WEBPAGE: forumpost
**
** Show a single forum posting. The posting is shown in context with
** it's entire thread.  The selected posting is enclosed within
** <div class='forumSel'>...</div>.  Javascript is used to move the
** selected posting into view after the page loads.
**
** Query parameters:
**
**   name=X        REQUIRED.  The hash of the post to display
**   t             Show a chronologic listing instead of hierarchical
*/
void forumpost_page(void){
  forumthread_page();
}



/*
** WEBPAGE: forumthread
**
** Show all forum messages associated with a particular message thread.
** The result is basically the same as /forumpost except that none of
** the postings in the thread are selected.
**
** Query parameters:
**
**   name=X        REQUIRED.  The hash of any post of the thread.
**   t             Show a chronologic listing instead of hierarchical
*/
void forumthread_page(void){
  int fpid;
  int froot;
  const char *zName = P("name");
  login_check_credentials();
  if( !g.perm.RdForum ){
    login_needed(g.anon.RdForum);
    return;
  }
  if( zName==0 ){
    webpage_error("Missing \"name=\" query parameter");
  }
  fpid = symbolic_name_to_rid(zName, "f");
  if( fpid<=0 ){
    webpage_error("Unknown or ambiguous forum id: \"%s\"", zName);
  }
  style_header("Forum");
  froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
  if( froot==0 ){

    webpage_error("Not a forum post: \"%s\"", zName);
  }
  if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
  if( P("t") ){
    if( g.perm.Debug ){
      style_submenu_element("Hierarchical", "%R/%s/%s", g.zPath, zName);
    }                          
    forum_display_chronological(froot, fpid);
  }else{
    if( g.perm.Debug ){
      style_submenu_element("Chronological", "%R/%s/%s?t", g.zPath, zName);
    }                          
    forum_display_hierarchical(froot, fpid);
  }
  style_load_js("forum.js");
  style_footer();
}

/*
** Return true if a forum post should be moderated.
*/
static int forum_need_moderation(void){
  if( P("domod") ) return 1;
  if( g.perm.WrTForum ) return 0;
  if( g.perm.ModForum ) return 0;
  return 1;
}

/*
** Add a new Forum Post artifact to the repository.
**
** Return true if a redirect occurs.
*/
static int forum_post(
  const char *zTitle,          /* Title.  NULL for replies */
  int iInReplyTo,              /* Post replying to.  0 for new threads */
  int iEdit,                   /* Post being edited, or zero for a new post */
  const char *zUser,           /* Username.  NULL means use login name */
  const char *zMimetype,       /* Mimetype of content. */
  const char *zContent         /* Content */
){
  char *zDate;
  char *zI;
  char *zG;
  int iBasis;
  Blob x, cksum, formatCheck, errMsg;
  Manifest *pPost;

  schema_forum();
  if( iInReplyTo==0 && iEdit>0 ){
    iBasis = iEdit;
    iInReplyTo = db_int(0, "SELECT firt FROM forumpost WHERE fpid=%d", iEdit);
  }else{
    iBasis = iInReplyTo;
  }
  webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 );
  blob_init(&x, 0, 0);
  zDate = date_in_standard_format("now");
  blob_appendf(&x, "D %s\n", zDate);
  fossil_free(zDate);
  zG = db_text(0, 
     "SELECT uuid FROM blob, forumpost"
     " WHERE blob.rid==forumpost.froot"
     "   AND forumpost.fpid=%d", iBasis);
  if( zG ){
    blob_appendf(&x, "G %s\n", zG);
    fossil_free(zG);
  }
  if( zTitle ){
    blob_appendf(&x, "H %F\n", zTitle);
  }
  zI = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iInReplyTo);
  if( zI ){



    blob_appendf(&x, "I %s\n", zI);
    fossil_free(zI);
  }
  if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){
    blob_appendf(&x, "N %s\n", zMimetype);
  }
  if( iEdit>0 ){

    char *zP = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iEdit);
    if( zP==0 ) webpage_error("missing edit artifact %d", iEdit);
    blob_appendf(&x, "P %s\n", zP);
    fossil_free(zP);
  }
  if( zUser==0 ){
    if( login_is_nobody() ){
      zUser = "anonymous";
    }else{
      zUser = login_name();
    }
  }
  blob_appendf(&x, "U %F\n", zUser);
  blob_appendf(&x, "W %d\n%s\n", strlen(zContent), zContent);
  md5sum_blob(&x, &cksum);
  blob_appendf(&x, "Z %b\n", &cksum);
  blob_reset(&cksum);

  /* Verify that the artifact we are creating is well-formed */
  blob_init(&formatCheck, 0, 0);
  blob_init(&errMsg, 0, 0);
  blob_copy(&formatCheck, &x);
  pPost = manifest_parse(&formatCheck, 0, &errMsg);
  if( pPost==0 ){
    webpage_error("malformed forum post artifact - %s", blob_str(&errMsg));
  }
  webpage_assert( pPost->type==CFTYPE_FORUM );
  manifest_destroy(pPost);

  if( P("dryrun") ){
    @ <div class='debug'>
    @ This is the artifact that would have been generated:
    @ <pre>%h(blob_str(&x))</pre>
    @ </div>
    blob_reset(&x);
    return 0;
  }else{
    int nrid = wiki_put(&x, 0, forum_need_moderation());
    cgi_redirectf("%R/forumpost/%S", rid_to_uuid(nrid));
    return 1;
  }
}

/*
** Paint the form elements for entering a Forum post
*/
static void forum_entry_widget(
  const char *zTitle,
  const char *zMimetype,
  const char *zContent
){
  if( zTitle ){
    @ Title: <input type="input" name="title" value="%h(zTitle)" size="50"><br>
  }
  @ Markup style:
  mimetype_option_menu(zMimetype);
  @ <br><textarea name="content" class="wikiedit" cols="80" \
  @ rows="25" wrap="virtual">%h(zContent)</textarea><br>
}

/*
** WEBPAGE: forumnew
** WEBPAGE: forumedit
**
** Start a new thread on the forum or reply to an existing thread.
** But first prompt to see if the user would like to log in.
*/
void forum_page_init(void){
  int isEdit;
  char *zGoto;
  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  if( sqlite3_strglob("*edit*", g.zPath)==0 ){
    zGoto = mprintf("%R/forume2?fpid=%S",PD("fpid",""));
    isEdit = 1;
  }else{
    zGoto = mprintf("%R/forume1");
    isEdit = 0;
  }
  if( login_is_individual() ){
    if( isEdit ){
      forumedit_page();
    }else{
      forumnew_page();
    }
    return;
  }
  style_header("%h As Anonymous?", isEdit ? "Reply" : "Post");
  @ <p>You are not logged in.
  @ <p><table border="0" cellpadding="10">
  @ <tr><td>
  @ <form action="%s(zGoto)" method="POST">
  @ <input type="submit" value="Remain Anonymous">
  @ </form>
  @ <td>Post to the forum anonymously
  if( login_self_register_available(0) ){
    @ <tr><td>
    @ <form action="%R/register" method="POST">
    @ <input type="hidden" name="g" value="%s(zGoto)">
    @ <input type="submit" value="Create An Account">
    @ </form>
    @ <td>Create a new account and post using that new account
  }
  @ <tr><td>
  @ <form action="%R/login" method="POST">
  @ <input type="hidden" name="g" value="%s(zGoto)">
  @ <input type="hidden" name="noanon" value="1">
  @ <input type="submit" value="Login">
  @ </form>
  @ <td>Log into an existing account
  @ </table>
  style_footer();
  fossil_free(zGoto);
}

/*
** Write the "From: USER" line on the webpage.
*/
static void forum_from_line(void){
  if( login_is_nobody() ){
    @ From: anonymous<br>
  }else{
    @ From: %h(login_name())<br>
  }
}

/*
** WEBPAGE: forume1
**
** Start a new forum thread.
*/
void forumnew_page(void){
  const char *zTitle = PDT("title","");
  const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
  const char *zContent = PDT("content","");
  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  if( P("submit") ){
    if( forum_post(zTitle, 0, 0, 0, zMimetype, zContent) ) return;
  }
  if( P("preview") ){
    @ <h1>Preview:</h1>
    forum_render(zTitle, zMimetype, zContent, "forumEdit");
  }
  style_header("New Forum Thread");
  @ <form action="%R/forume1" method="POST">
  @ <h1>New Message:</h1>
  forum_from_line();
  forum_entry_widget(zTitle, zMimetype, zContent);
  @ <input type="submit" name="preview" value="Preview">
  if( P("preview") ){
    @ <input type="submit" name="submit" value="Submit">
  }else{
    @ <input type="submit" name="submit" value="Submit" disabled>
  }
  if( g.perm.Debug ){
    /* For the test-forumnew page add these extra debugging controls */
    @ <div class="debug">
    @ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
    @ Dry run</label>
    @ <br><label><input type="checkbox" name="domod" %s(PCK("domod"))> \
    @ Require moderator approval</label>
    @ <br><label><input type="checkbox" name="showqp" %s(PCK("showqp"))> \
    @ Show query parameters</label>
    @ </div>
  }
  @ </form>
  style_footer();
}

/*
** WEBPAGE: forume2
**
** Edit an existing forum message.
** Query parameters:
**
**   fpid=X        Hash of the post to be editted.  REQUIRED
*/
void forumedit_page(void){
  int fpid;
  Manifest *pPost = 0;
  const char *zMimetype = 0;
  const char *zContent = 0;
  const char *zTitle = 0;
  int isCsrfSafe;
  int isDelete = 0;

  login_check_credentials();
  if( !g.perm.WrForum ){
    login_needed(g.anon.WrForum);
    return;
  }
  fpid = symbolic_name_to_rid(PD("fpid",""), "f");
  if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){
    webpage_error("Missing or invalid fpid query parameter");
  }
  if( P("cancel") ){
    cgi_redirectf("%R/forumpost/%S",P("fpid"));
    return;
  }
  isCsrfSafe = cgi_csrf_safe(1);
  if( g.perm.ModForum && isCsrfSafe ){
    if( P("approve") ){
      moderation_approve(fpid);
      cgi_redirectf("%R/forumpost/%S",P("fpid"));
      return;
    }
    if( P("reject") ){
      char *zParent = 
        db_text(0,
          "SELECT uuid FROM forumpost, blob"
          " WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt",
          fpid
        );
      moderation_disapprove(fpid);
      if( zParent ){
        cgi_redirectf("%R/forumpost/%S",zParent);
      }else{
        cgi_redirectf("%R/forum");
      }
      return;
    }
  }
  isDelete = P("nullout")!=0;
  if( P("submit") && isCsrfSafe ){
    int done = 1;
    const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
    const char *zContent = PDT("content","");
    if( P("reply") ){
      done = forum_post(0, fpid, 0, 0, zMimetype, zContent);
    }else if( P("edit") || isDelete ){
      done = forum_post(P("title"), 0, fpid, 0, zMimetype, zContent);
    }else{
      webpage_error("Missing 'reply' query parameter");
    }
    if( done ) return;
  }
  if( isDelete ){
    zMimetype = "text/x-fossil-wiki";
    zContent = "";
    if( pPost->zThreadTitle ) zTitle = "";
    style_header("Delete %s", zTitle ? "Post" : "Reply");
    @ <h1>Original Post:</h1>
    forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
                 "forumEdit");
    @ <h1>Change Into:</h1>
    forum_render(zTitle, zMimetype, zContent,"forumEdit");
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="nullout" value="1">
    @ <input type="hidden" name="mimetype" value="%h(zMimetype)">
    @ <input type="hidden" name="content" value="%h(zContent)">
    if( zTitle ){
      @ <input type="hidden" name="title" value="%h(zTitle)">
    }
  }else if( P("edit") ){
    /* Provide an edit to the fpid post */
    zMimetype = P("mimetype");
    zContent = PT("content");
    zTitle = P("title");
    if( zContent==0 ) zContent = fossil_strdup(pPost->zWiki);
    if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype);
    if( zTitle==0 && pPost->zThreadTitle!=0 ){
      zTitle = fossil_strdup(pPost->zThreadTitle);
    }
    style_header("Edit %s", zTitle ? "Post" : "Reply");
    @ <h1>Original Post:</h1>
    forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
                 "forumEdit");
    if( P("preview") ){
      @ <h1>Preview of Edited Post:</h1>
      forum_render(zTitle, zMimetype, zContent,"forumEdit");
    }
    @ <h1>Revised Message:</h1>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="edit" value="1">
    forum_from_line();
    forum_entry_widget(zTitle, zMimetype, zContent);
  }else{
    /* Reply */
    zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
    zContent = PDT("content","");
    style_header("Reply");
    @ <h1>Replying To:</h1>
    forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit");
    if( P("preview") ){
      @ <h1>Preview:</h1>
      forum_render(0, zMimetype,zContent, "forumEdit");
    }
    @ <h1>Enter Reply:</h1>
    @ <form action="%R/forume2" method="POST">
    @ <input type="hidden" name="fpid" value="%h(P("fpid"))">
    @ <input type="hidden" name="reply" value="1">
    forum_from_line();
    forum_entry_widget(0, zMimetype, zContent);
  }
  if( !isDelete ){
    @ <input type="submit" name="preview" value="Preview">
  }
  @ <input type="submit" name="cancel" value="Cancel">
  if( P("preview") || isDelete ){
    @ <input type="submit" name="submit" value="Submit">
  }
  if( g.perm.Debug ){
    /* For the test-forumnew page add these extra debugging controls */
    @ <div class="debug">
    @ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
    @ Dry run</label>
    @ <br><label><input type="checkbox" name="domod" %s(PCK("domod"))> \
    @ Require moderator approval</label>
    @ <br><label><input type="checkbox" name="showqp" %s(PCK("showqp"))> \
    @ Show query parameters</label>
    @ </div>
  }
  @ </form>
  style_footer();
}

/*


** WEBPAGE: forum
**

** The main page for the forum feature.  Show a list of recent forum









** threads.  Also show a search box at the top if search is enabled,
























































** and a button for creating a new thread, if enabled.


**
** Query parameters:
**

**    n=N             The number of threads to show on each page



**    x=X             Skip the first X threads

*/
void forum_main_page(void){
  Stmt q;
  int iLimit, iOfst, iCnt;
  int srchFlags;
  login_check_credentials();
  srchFlags = search_restrict(SRCH_FORUM);
  if( !g.perm.RdForum ){
    login_needed(g.anon.RdForum);
    return;
  }
  style_header("Forum");
  if( g.perm.WrForum ){
    style_submenu_element("New Message","%R/forumnew");
  }
  if( g.perm.ModForum && moderation_needed() ){
    style_submenu_element("Moderation Requests", "%R/modreq");
  }
  if( (srchFlags & SRCH_FORUM)!=0 ){
    if( search_screen(SRCH_FORUM, 0) ){
      style_submenu_element("Recent Threads","%R/forum");
      style_footer();
      return;
    }
  }
  iLimit = atoi(PD("n","25"));
  iOfst = atoi(PD("x","0"));
  iCnt = 0;
  if( db_table_exists("repository","forumpost") ){
    db_prepare(&q,
      "WITH thread(age,duration,cnt,root,last) AS ("
      "  SELECT"
      "    julianday('now') - max(fmtime),"
      "    max(fmtime) - min(fmtime),"
      "    sum(fprev IS NULL),"
      "    froot,"
      "    (SELECT fpid FROM forumpost AS y"
      "      WHERE y.froot=x.froot %s"
      "      ORDER BY y.fmtime DESC LIMIT 1)"
      "  FROM forumpost AS x"
      "  WHERE %s"
      "  GROUP BY froot"
      "  ORDER BY 1 LIMIT %d OFFSET %d"
      ")"
      "SELECT"
      "  thread.age,"                                         /* 0 */
      "  thread.duration,"                                    /* 1 */
      "  thread.cnt,"                                         /* 2 */
      "  blob.uuid,"                                          /* 3 */
      "  substr(event.comment,instr(event.comment,':')+1),"   /* 4 */
      "  thread.last"                                         /* 5 */
      " FROM thread, blob, event"
      " WHERE blob.rid=thread.last"
      "  AND event.objid=thread.last"
      " ORDER BY 1;",
      g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/,
      g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/,
      iLimit+1, iOfst
    );
    while( db_step(&q)==SQLITE_ROW ){
      char *zAge = human_readable_age(db_column_double(&q,0));
      int nMsg = db_column_int(&q, 2);
      const char *zUuid = db_column_text(&q, 3);
      const char *zTitle = db_column_text(&q, 4);
      if( iCnt==0 ){

        if( iOfst>0 ){
          @ <h1>Threads at least %s(zAge) old</h1>
        }else{
          @ <h1>Most recent threads</h1>
        }

        @ <div class='forumPosts fileage'><table width="100%%">
        if( iOfst>0 ){
          if( iOfst>iLimit ){
            @ <tr><td colspan="3">\
            @ %z(href("%R/forum?x=%d&n=%d",iOfst-iLimit,iLimit))\
            @ &uarr; Newer...</a></td></tr>
          }else{
            @ <tr><td colspan="3">%z(href("%R/forum?n=%d",iLimit))\
            @ &uarr; Newer...</a></td></tr>
          }
        }

      }
      iCnt++;
      if( iCnt>iLimit ){
        @ <tr><td colspan="3">\
        @ %z(href("%R/forum?x=%d&n=%d",iOfst+iLimit,iLimit))\

        @ &darr; Older...</a></td></tr>
        fossil_free(zAge);
        break;
      }
      @ <tr><td>%h(zAge) ago</td>
      @ <td>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a></td>
      @ <td>\
      if( g.perm.ModForum && moderation_pending(db_column_int(&q,5)) ){
        @ <span class="modpending">\
        @ Awaiting Moderator Approval</span><br>
      }
      if( nMsg<2 ){

        @ no replies</td>
      }else{
        char *zDuration = human_readable_age(db_column_double(&q,1));
        @ %d(nMsg) posts spanning %h(zDuration)</td>
        fossil_free(zDuration);
      }
      @ </tr>
      fossil_free(zAge);
    }
    db_finalize(&q);
  }

  if( iCnt>0 ){



    @ </table></div>
  }else{













    @ <h1>No forum posts found</h1>
  }








































  style_footer();
}

Added src/forum.js.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function(){
  function absoluteY(obj){
    var top = 0;
    if( obj.offsetParent ){
      do{
        top += obj.offsetTop;
      }while( obj = obj.offsetParent );
    }
    return top;
  }
  var x = document.getElementsByClassName('forumSel');
  if(x[0]){
    var w = window.innerHeight;
    var h = x[0].scrollHeight;
    var y = absoluteY(x[0]);
    if( w>h ) y = y + (h-w)/2;
    if( y>0 ) window.scrollTo(0, y);
  }
}())

Changes to src/graph.js.

105
106
107
108
109
110
111



112
113
114
115
116
117
118

119
120
121
122
123
124
125
    line = elems.line;
    mArrow = elems.arrow_merge_r;
    mLine = elems.line_merge;
    wArrow = elems.arrow_warp;
    wLine = elems.line_warp;
  
    var minRailPitch = Math.ceil((node.w+line.w)/2 + mArrow.w + 1);



    if( tx.iRailPitch>0 ){
      railPitch = tx.iRailPitch;
    }else{
      railPitch = elems.rail.w;
      railPitch -= Math.floor((tx.nrail-1)*(railPitch-minRailPitch)/21);
    }
    railPitch = Math.max(railPitch, minRailPitch);

  
    if( tx.nomo ){
      mergeOffset = 0;
    }else{
      mergeOffset = railPitch-minRailPitch-mLine.w;
      mergeOffset = Math.min(mergeOffset, elems.mergeoffset.w);
      mergeOffset = mergeOffset>0 ? mergeOffset + line.w/2 : 0;







>
>
>
|
|
|
|
|
|
|
>







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    line = elems.line;
    mArrow = elems.arrow_merge_r;
    mLine = elems.line_merge;
    wArrow = elems.arrow_warp;
    wLine = elems.line_warp;
  
    var minRailPitch = Math.ceil((node.w+line.w)/2 + mArrow.w + 1);
    if( window.innerWidth<400 ){
      railPitch = minRailPitch;
    }else{
      if( tx.iRailPitch>0 ){
        railPitch = tx.iRailPitch;
      }else{
        railPitch = elems.rail.w;
        railPitch -= Math.floor((tx.nrail-1)*(railPitch-minRailPitch)/21);
      }
      railPitch = Math.max(railPitch, minRailPitch);
    }
  
    if( tx.nomo ){
      mergeOffset = 0;
    }else{
      mergeOffset = railPitch-minRailPitch-mLine.w;
      mergeOffset = Math.min(mergeOffset, elems.mergeoffset.w);
      mergeOffset = mergeOffset>0 ? mergeOffset + line.w/2 : 0;
373
374
375
376
377
378
379












380
381
382
383
384
385
386
  var i;
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;
  }
  lx = topObj.getElementsByClassName('timelineCompactComment');
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;












  }
}
  
/* Look for all timeline-data-NN objects.  Load each one and draw
** a graph for each one.
*/
(function(){







>
>
>
>
>
>
>
>
>
>
>
>







377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
  var i;
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;
  }
  lx = topObj.getElementsByClassName('timelineCompactComment');
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;
  }
  if( window.innerWidth<400 ){
    /* On narrow displays, shift the date from the first column to the
    ** third column, to make the first column narrower */
    lx = topObj.getElementsByClassName('timelineDateRow');
    for(i=0; i<lx.length; i++){
      var rx = lx[i];
      if( rx.getAttribute('data-reordered') ) break;
      rx.setAttribute('data-reordered',1);
      rx.appendChild(rx.firstChild);
      rx.insertBefore(rx.childNodes[1],rx.firstChild);
    }
  }
}
  
/* Look for all timeline-data-NN objects.  Load each one and draw
** a graph for each one.
*/
(function(){

Changes to src/http_socket.c.

22
23
24
25
26
27
28



29
30
31
32
33
34
35
** at a time.  State information is stored in static variables.  The identity
** of the server is held in global variables that are set by url_parse().
**
** Low-level sockets are abstracted out into this module because they
** are handled different on Unix and windows.
*/
#if defined(_WIN32)



# define _WIN32_WINNT 0x501
#endif
#ifndef __EXTENSIONS__
# define __EXTENSIONS__ 1  /* IPv6 won't compile on Solaris without this */
#endif
#include "config.h"
#include "http_socket.h"







>
>
>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
** at a time.  State information is stored in static variables.  The identity
** of the server is held in global variables that are set by url_parse().
**
** Low-level sockets are abstracted out into this module because they
** are handled different on Unix and windows.
*/
#if defined(_WIN32)
# if defined(_WIN32_WINNT)
#  undef _WIN32_WINNT
# endif
# define _WIN32_WINNT 0x501
#endif
#ifndef __EXTENSIONS__
# define __EXTENSIONS__ 1  /* IPv6 won't compile on Solaris without this */
#endif
#include "config.h"
#include "http_socket.h"

Changes to src/http_transport.c.

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
** it is time to being receiving a reply.
*/
void transport_flip(UrlData *pUrlData){
  if( pUrlData->isFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http --in \"%s\" --out \"%s\" --ipaddr 127.0.0.1"
                   " \"%s\" --localauth --nodelay",
       g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
    );
    fossil_system(zCmd);
    free(zCmd);
    transport.pFile = fossil_fopen(transport.zInFile, "rb");
  }
}







|







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
** it is time to being receiving a reply.
*/
void transport_flip(UrlData *pUrlData){
  if( pUrlData->isFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http --in \"%s\" --out \"%s\" --ipaddr 127.0.0.1"
                   " \"%s\" --localauth",
       g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
    );
    fossil_system(zCmd);
    free(zCmd);
    transport.pFile = fossil_fopen(transport.zInFile, "rb");
  }
}

Changes to src/info.c.

933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){
    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
  }
  @ </td></tr>
  @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
  @ <tr><th>Date:</th><td>
  hyperlink_to_date(zDate, "</td></tr>");
  @ <tr><th>Original&nbsp;User:</th><td>
  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->zMimetype ){







|
<
<
<







933
934
935
936
937
938
939
940



941
942
943
944
945
946
947
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending_www(rid);



  @ </td></tr>
  @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
  @ <tr><th>Date:</th><td>
  hyperlink_to_date(zDate, "</td></tr>");
  @ <tr><th>Original&nbsp;User:</th><td>
  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->zMimetype ){
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004


  @ <div class="section">Content</div>
  blob_init(&wiki, pWiki->zWiki, -1);
  wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
  blob_reset(&wiki);
  manifest_destroy(pWiki);
  style_footer();
}

/*
** Show a webpage error message
*/
void webpage_error(const char *zFormat, ...){
  va_list ap;
  const char *z;
  va_start(ap, zFormat);
  z = vmprintf(zFormat, ap);
  va_end(ap);
  style_header("URL Error");
  @ <h1>Error</h1>
  @ <p>%h(z)</p>
  style_footer();
}

/*
** Find an check-in based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







973
974
975
976
977
978
979















980
981
982
983
984
985
986


  @ <div class="section">Content</div>
  blob_init(&wiki, pWiki->zWiki, -1);
  wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
  blob_reset(&wiki);
  manifest_destroy(pWiki);















  style_footer();
}

/*
** Find an check-in based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/
1244
1245
1246
1247
1248
1249
1250

1251
1252
1253
1254
1255
1256
1257
#define OBJTYPE_WIKI       0x0004
#define OBJTYPE_TICKET     0x0008
#define OBJTYPE_ATTACHMENT 0x0010
#define OBJTYPE_EVENT      0x0020
#define OBJTYPE_TAG        0x0040
#define OBJTYPE_SYMLINK    0x0080
#define OBJTYPE_EXE        0x0100


/*
** Possible flags for the second parameter to
** object_description()
*/
#define OBJDESC_DETAIL      0x0001   /* more detail */
#endif







>







1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
#define OBJTYPE_WIKI       0x0004
#define OBJTYPE_TICKET     0x0008
#define OBJTYPE_ATTACHMENT 0x0010
#define OBJTYPE_EVENT      0x0020
#define OBJTYPE_TAG        0x0040
#define OBJTYPE_SYMLINK    0x0080
#define OBJTYPE_EXE        0x0100
#define OBJTYPE_FORUM      0x0200

/*
** Possible flags for the second parameter to
** object_description()
*/
#define OBJDESC_DETAIL      0x0001   /* more detail */
#endif
1436
1437
1438
1439
1440
1441
1442



1443
1444
1445
1446
1447
1448
1449
        if( eventTagId != 0) {
          @ Instance of technote
          objType |= OBJTYPE_EVENT;
          hyperlink_to_event_tagid(db_column_int(&q, 5));
        }else{
          @ Attachment to technote
        }



      }else{
        @ Tag referencing
      }
      if( zType[0]!='e' || eventTagId == 0){
        hyperlink_to_uuid(zUuid);
      }
      @ - %!W(zCom) by







>
>
>







1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
        if( eventTagId != 0) {
          @ Instance of technote
          objType |= OBJTYPE_EVENT;
          hyperlink_to_event_tagid(db_column_int(&q, 5));
        }else{
          @ Attachment to technote
        }
      }else if( zType[0]=='f' ){
        objType |= OBJTYPE_FORUM;
        @ Forum post
      }else{
        @ Tag referencing
      }
      if( zType[0]!='e' || eventTagId == 0){
        hyperlink_to_uuid(zUuid);
      }
      @ - %!W(zCom) by
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending(rid);
  if( modPending ){
    @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
  }
  @ <tr><th>Ticket:</th>
  @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a>
  if( zTktTitle ){
        @<br />%h(zTktTitle)
  }
  @</td></tr>
  @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>







|
<
<
<







2232
2233
2234
2235
2236
2237
2238
2239



2240
2241
2242
2243
2244
2245
2246
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
  }
  modPending = moderation_pending_www(rid);



  @ <tr><th>Ticket:</th>
  @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a>
  if( zTktTitle ){
        @<br />%h(zTktTitle)
  }
  @</td></tr>
  @ <tr><th>User&nbsp;&amp;&nbsp;Date:</th><td>
2362
2363
2364
2365
2366
2367
2368





2369
2370
2371
2372
2373
2374
2375
    ci_page();
  }else
  if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
    ci_page();
  }else
  if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
    ainfo_page();





  }else
  {
    artifact_page();
  }
}

/*







>
>
>
>
>







2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
    ci_page();
  }else
  if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
    ci_page();
  }else
  if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
    ainfo_page();
  }else
  if( db_table_exists("repository","forumpost")
   && db_exists("SELECT 1 FROM forumpost WHERE fpid=%d", rid)
  ){
    forumthread_page();
  }else
  {
    artifact_page();
  }
}

/*

Changes to src/json.c.

1888
1889
1890
1891
1892
1893
1894









1895
1896
1897
1898
1899
1900
1901
  ADD(WrTkt,"editTicket");
  ADD(ModTkt,"moderateTicket");
  ADD(Attach,"attachFile");
  ADD(TktFmt,"createTicketReport");
  ADD(RdAddr,"readPrivate");
  ADD(Zip,"zip");
  ADD(Private,"xferPrivate");









#undef ADD
  return payload;
}

/*
** Implementation of the /json/stat page/command.
**







>
>
>
>
>
>
>
>
>







1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
  ADD(WrTkt,"editTicket");
  ADD(ModTkt,"moderateTicket");
  ADD(Attach,"attachFile");
  ADD(TktFmt,"createTicketReport");
  ADD(RdAddr,"readPrivate");
  ADD(Zip,"zip");
  ADD(Private,"xferPrivate");
  ADD(WrUnver,"writeUnversioned");
  ADD(RdForum,"readForum");
  ADD(WrForum,"writeForum");
  ADD(WrTForum,"writeTrustedForum");
  ADD(ModForum,"moderateForum");
  ADD(AdminForum,"adminForum");
  ADD(EmailAlert,"emailAlert");
  ADD(Announce,"announce");
  ADD(Debug,"debug");
#undef ADD
  return payload;
}

/*
** Implementation of the /json/stat page/command.
**

Changes to src/json_login.c.

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    }
  }

#if 0
  {
    /* only for debugging the PD()-incorrect-result problem */
    cson_object * o = NULL;
    uid = login_search_uid( name, pw );
    payload = cson_value_new_object();
    o = cson_value_get_object(payload);
    cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
    cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
    return payload;
  }
#endif
  uid = anonSeed
    ? login_is_valid_anonymous(name, pw, anonSeed)
    : login_search_uid(name, pw)
    ;
  if( !uid ){
    g.json.resultCode = preciseErrors
      ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
      : FSL_JSON_E_LOGIN_FAILED;
    return NULL;
  }else{







|









|







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    }
  }

#if 0
  {
    /* only for debugging the PD()-incorrect-result problem */
    cson_object * o = NULL;
    uid = login_search_uid( &name, pw );
    payload = cson_value_new_object();
    o = cson_value_get_object(payload);
    cson_object_set( o, "n", cson_value_new_string(name,strlen(name)));
    cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw)));
    return payload;
  }
#endif
  uid = anonSeed
    ? login_is_valid_anonymous(name, pw, anonSeed)
    : login_search_uid(&name, pw)
    ;
  if( !uid ){
    g.json.resultCode = preciseErrors
      ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND
      : FSL_JSON_E_LOGIN_FAILED;
    return NULL;
  }else{

Changes to src/json_timeline.c.

33
34
35
36
37
38
39

40
41
42
43
44
45
46
static const JsonPageDef JsonPageDefs_Timeline[] = {
/* the short forms are only enabled in CLI mode, to avoid
   that we end up with HTTP clients using 3 different names
   for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},

{"ticket", json_timeline_ticket, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};









>







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static const JsonPageDef JsonPageDefs_Timeline[] = {
/* the short forms are only enabled in CLI mode, to avoid
   that we end up with HTTP clients using 3 different names
   for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},
{"event", json_timeline_event, 0},
{"ticket", json_timeline_ticket, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};


517
518
519
520
521
522
523
























































524
525
526
527
528
529
530
  assert( 0 != g.json.resultCode );
  cson_value_free(payV);
  payV = NULL;
  ok:
  db_finalize(&q);
  return payV;
}

























































/*
** Implementation of /json/timeline/wiki.
**
*/
cson_value * json_timeline_wiki(){
  /* This code is 95% the same as json_timeline_ci(), by the way. */







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
  assert( 0 != g.json.resultCode );
  cson_value_free(payV);
  payV = NULL;
  ok:
  db_finalize(&q);
  return payV;
}

/*
** Implementation of /json/timeline/event.
**
*/
cson_value * json_timeline_event(){
  /* This code is 95% the same as json_timeline_ci(), by the way. */
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  cson_array * list = NULL;
  int check = 0;
  Stmt q = empty_Stmt;
  Blob sql = empty_blob;
  if( !g.perm.RdWiki ){
    json_set_err( FSL_JSON_E_DENIED, "Event timeline requires 'j' access.");
    return NULL;
  }
  payV = cson_value_new_object();
  pay = cson_value_get_object(payV);
  check = json_timeline_setup_sql( "e", &sql, pay );
  if(check){
    json_set_err(check, "Query initialization failed.");
    goto error;
  }

#if 0
  /* only for testing! */
  cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))));
#endif
  db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/);
  blob_reset(&sql);
  db_prepare(&q, "SELECT"
             /* For events, the name is generally more useful than
                the uuid, but the uuid is unambiguous and can be used
                with commands like 'artifact'. */
             " substr((SELECT tagname FROM tag AS tn WHERE tn.tagid=json_timeline.tagId AND tagname LIKE 'event-%%'),7) AS name,"
             " uuid as uuid,"
             " mtime AS timestamp,"
             " comment AS comment, "
             " user AS user,"
             " eventType AS eventType"
             " FROM json_timeline"
             " ORDER BY rowid");
  list = cson_new_array();
  json_stmt_to_array_of_obj(&q, list);
  cson_object_set(pay, "timeline", cson_array_value(list));
  goto ok;
  error:
  assert( 0 != g.json.resultCode );
  cson_value_free(payV);
  payV = NULL;
  ok:
  db_finalize(&q);
  blob_reset(&sql);
  return payV;
}

/*
** Implementation of /json/timeline/wiki.
**
*/
cson_value * json_timeline_wiki(){
  /* This code is 95% the same as json_timeline_ci(), by the way. */

Changes to src/login.c.

204
205
206
207
208
209
210








211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226























227
228
229
230
231
232
233
}

/*
** Searches for the user ID matching the given name and password.
** On success it returns a positive value. On error it returns 0.
** On serious (DB-level) error it will probably exit.
**








** zPassword may be either the plain-text form or the encrypted
** form of the user's password.
*/
int login_search_uid(const char *zUsername, const char *zPasswd){
  char *zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
  int const uid =
      db_int(0,
             "SELECT uid FROM user"
             " WHERE login=%Q"
             "   AND length(cap)>0 AND length(pw)>0"
             "   AND login NOT IN ('anonymous','nobody','developer','reader')"
             "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))"
             "   AND (info NOT LIKE '%%expires 20%%'"
             "      OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))",
             zUsername, zSha1Pw, zPasswd
             );























  free(zSha1Pw);
  return uid;
}

/*
** Generates a login cookie value for a non-anonymous user.
**







>
>
>
>
>
>
>
>



|
|
<
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
}

/*
** Searches for the user ID matching the given name and password.
** On success it returns a positive value. On error it returns 0.
** On serious (DB-level) error it will probably exit.
**
** zUsername uses double indirection because we may re-point *zUsername
** at a C string allocated with fossil_strdup() if you pass an email
** address instead and we find that address in the user table's info
** field, which is expected to contain a string of the form "Human Name
** <human@example.com>".  In that case, *zUsername will point to that
** user's actual login name on return, causing a leak unless the caller
** is diligent enough to check whether its pointer was re-pointed.
**
** zPassword may be either the plain-text form or the encrypted
** form of the user's password.
*/
int login_search_uid(const char **pzUsername, const char *zPasswd){
  char *zSha1Pw = sha1_shared_secret(zPasswd, *pzUsername, 0);

  int uid = db_int(0,
    "SELECT uid FROM user"
    " WHERE login=%Q"
    "   AND length(cap)>0 AND length(pw)>0"
    "   AND login NOT IN ('anonymous','nobody','developer','reader')"
    "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))"
    "   AND (info NOT LIKE '%%expires 20%%'"
    "      OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))",
    *pzUsername, zSha1Pw, zPasswd
  );

  /* If we did not find a login on the first attempt, and the username
  ** looks like an email address, then perhaps the user entered their
  ** email address instead of their login.  Try again to match the user
  ** against email addresses contained in the "info" field.
  */
  if( uid==0 && strchr(*pzUsername,'@')!=0 ){
    Stmt q;
    db_prepare(&q,
      "SELECT login FROM user"
      " WHERE find_emailaddr(info)=%Q"
      "   AND instr(login,'@')==0",
      *pzUsername
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zLogin = db_column_text(&q,0);
      if( (uid = login_search_uid(&zLogin, zPasswd) ) != 0 ){
        *pzUsername = fossil_strdup(zLogin);
        break;
      }
    }
    db_finalize(&q);
  }    
  free(zSha1Pw);
  return uid;
}

/*
** Generates a login cookie value for a non-anonymous user.
**
468
469
470
471
472
473
474


















475
476
477
478
479
480
481
  int rc;
  if( zReferer==0 ) return 0;
  zPattern = mprintf("%s/login*", g.zBaseURL);
  rc = sqlite3_strglob(zPattern, zReferer)==0;
  fossil_free(zPattern);
  return rc;
}



















/*
** There used to be a page named "my" that was designed to show information
** about a specific user.  The "my" page was linked from the "Logged in as USER"
** line on the title bar.  The "my" page was never completed so it is now
** removed.  Use this page as a placeholder in older installations.
**







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
  int rc;
  if( zReferer==0 ) return 0;
  zPattern = mprintf("%s/login*", g.zBaseURL);
  rc = sqlite3_strglob(zPattern, zReferer)==0;
  fossil_free(zPattern);
  return rc;
}

/*
** Return TRUE if self-registration is available.  If the zNeeded
** argument is not NULL, then only return true if self-registration is
** available and any of the capabilities named in zNeeded are available
** to self-registered users.
*/
int login_self_register_available(const char *zNeeded){
  CapabilityString *pCap;
  int rc;
  if( !db_get_boolean("self-register",0) ) return 0;
  if( zNeeded==0 ) return 1;
  pCap = capability_add(0, db_get("default-perms",""));
  capability_expand(pCap);
  rc = capability_has_any(pCap, zNeeded);
  capability_free(pCap);
  return rc;
}

/*
** There used to be a page named "my" that was designed to show information
** about a specific user.  The "my" page was linked from the "Logged in as USER"
** line on the title bar.  The "my" page was never completed so it is now
** removed.  Use this page as a placeholder in older installations.
**
496
497
498
499
500
501
502

503
504
505
506
507
508
509
  const char *zGoto = P("g");
  int anonFlag;                /* Login as "anonymous" would be useful */
  char *zErrMsg = "";
  int uid;                     /* User id logged in user */
  char *zSha1Pw;
  const char *zIpAddr;         /* IP address of requestor */
  const char *zReferer;


  login_check_credentials();
  if( login_wants_https_redirect() ){
    const char *zQS = P("QUERY_STRING");
    if( P("redir")!=0 ){
      style_header("Insecure Connection");
      @ <h1>Unable To Establish An Encrypted Connection</h1>







>







544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  const char *zGoto = P("g");
  int anonFlag;                /* Login as "anonymous" would be useful */
  char *zErrMsg = "";
  int uid;                     /* User id logged in user */
  char *zSha1Pw;
  const char *zIpAddr;         /* IP address of requestor */
  const char *zReferer;
  int noAnon = P("noanon")!=0;

  login_check_credentials();
  if( login_wants_https_redirect() ){
    const char *zQS = P("QUERY_STRING");
    if( P("redir")!=0 ){
      style_header("Insecure Connection");
      @ <h1>Unable To Establish An Encrypted Connection</h1>
534
535
536
537
538
539
540






541
542
543
544
545
546
547

  /* Handle log-out requests */
  if( P("out") ){
    login_clear_login_data();
    redirect_to_g();
    return;
  }







  /* Deal with password-change requests */
  if( g.perm.Password && zPasswd
   && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
  ){
    /* If there is not a "real" login, we cannot change any password. */
    if( g.zLogin ){







>
>
>
>
>
>







583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602

  /* Handle log-out requests */
  if( P("out") ){
    login_clear_login_data();
    redirect_to_g();
    return;
  }

  /* Redirect for create-new-account requests */
  if( P("self") ){
    cgi_redirectf("%R/register");
    return;
  }

  /* Deal with password-change requests */
  if( g.perm.Password && zPasswd
   && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
  ){
    /* If there is not a "real" login, we cannot change any password. */
    if( g.zLogin ){
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    login_set_anon_cookie(zIpAddr, NULL);
    record_login_attempt("anonymous", zIpAddr, 1);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);
      zErrMsg =
         @ <p><span class="loginError">
         @ You entered an unknown user or an incorrect password.
         @ </span></p>
      ;







|







660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    login_set_anon_cookie(zIpAddr, NULL);
    record_login_attempt("anonymous", zIpAddr, 1);
    redirect_to_g();
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(&zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);
      zErrMsg =
         @ <p><span class="loginError">
         @ You entered an unknown user or an incorrect password.
         @ </span></p>
      ;
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
      login_set_user_cookie(zUsername, uid, NULL);
      redirect_to_g();
    }
  }
  style_header("Login/Logout");
  style_adunit_config(ADUNIT_OFF);
  @ %s(zErrMsg)
  if( zGoto ){
    char *zAbbrev = fossil_strdup(zGoto);
    int i;
    for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
    zAbbrev[i] = 0;
    if( g.zLogin ){
      @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
      @ to access <b>%h(zAbbrev)</b>.







|







684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
      login_set_user_cookie(zUsername, uid, NULL);
      redirect_to_g();
    }
  }
  style_header("Login/Logout");
  style_adunit_config(ADUNIT_OFF);
  @ %s(zErrMsg)
  if( zGoto && !noAnon ){
    char *zAbbrev = fossil_strdup(zGoto);
    int i;
    for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
    zAbbrev[i] = 0;
    if( g.zLogin ){
      @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
      @ to access <b>%h(zAbbrev)</b>.
672
673
674
675
676
677
678





679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705

706
707
708
709
710
711

712
713
714
715
716
717
718
719
720
721

722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739

740
741
742
743
744
745
746
747
748
749
750
751
752
753

754
755
756
757
758
759
760
    @ <tr>
    @   <td class="form_label">User ID:</td>
    if( anonFlag ){
      @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
    }else{
      @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
    }





    if( P("HTTPS")==0 ){
      @ <td width="15"><td rowspan="3">
      @ <p class='securityWarning'>
      @ Warning: Your password will be sent in the clear over an
      @ unencrypted connection.
      if( g.sslNotAvailable ){
        @ No encrypted connection is available on this server.
      }else{
        @ Consider logging in at
        @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
      }
      @ </p>
    }
    @ </tr>
    @ <tr>
    @  <td class="form_label">Password:</td>
    @   <td><input type="password" id="p" name="p" value="" size="30" /></td>
    @ </tr>
    if( g.zLogin==0 && (anonFlag || zGoto==0) ){
      zAnonPw = db_text(0, "SELECT pw FROM user"
                           " WHERE login='anonymous'"
                           "   AND cap!=''");
    }
    @ <tr>
    @   <td></td>
    @   <td><input type="submit" name="in" value="Login">
    @ </tr>

    @ </table>
    @ <p>Pressing the Login button grants permission to store a cookie.</p>
    if( db_get_boolean("self-register", 0) ){
      @ <p>If you do not have an account, you can
      @ <a href="%R/register?g=%T(P("G"))">create one</a>.
    }

    if( zAnonPw ){
      unsigned int uSeed = captcha_seed();
      const char *zDecoded = captcha_decode(uSeed);
      int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
      char *zCaptcha = captcha_render(zDecoded);
  
      @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
      @ Visitors may enter <b>anonymous</b> as the user-ID with
      @ the 8-character hexadecimal password shown below:</p>
      @ <div class="captcha"><table class="captcha"><tr><td><pre>

      @ %h(zCaptcha)
      @ </pre></td></tr></table>
      if( bAutoCaptcha ) {
         @ <input type="button" value="Fill out captcha" id='autofillButton' \
         @ data-af='%s(zDecoded)' />
         style_load_one_js_file("login.js");
      }
      @ </div>
      free(zCaptcha);
    }
    @ </form>
  }
  if( login_is_individual() && g.perm.Password ){
    if( email_enabled() ){
      @ <hr>
      @ <p>Configure <a href="%R/alerts">Email Alerts</a>
      @ for user <b>%h(g.zLogin)</b></p>
    }

    @ <hr />
    @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
    form_begin(0, "%R/login");
    @ <table>
    @ <tr><td class="form_label">Old Password:</td>
    @ <td><input type="password" name="p" size="30" /></td></tr>
    @ <tr><td class="form_label">New Password:</td>
    @ <td><input type="password" name="n1" size="30" /></td></tr>
    @ <tr><td class="form_label">Repeat New Password:</td>
    @ <td><input type="password" name="n2" size="30" /></td></tr>
    @ <tr><td></td>
    @ <td><input type="submit" value="Change Password" /></td></tr>
    @ </table>
    @ </form>

  }
  style_footer();
}

/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode.  Transfer those credentials to the local







>
>
>
>
>

|
|
|







|

<
<
<
<
<







|

>
|
<
|
|
|

>
|








|
>












|
|




>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>







727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751





752
753
754
755
756
757
758
759
760
761
762

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
    @ <tr>
    @   <td class="form_label">User ID:</td>
    if( anonFlag ){
      @ <td><input type="text" id="u" name="u" value="anonymous" size="30"></td>
    }else{
      @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
    }
    @ </tr>
    @ <tr>
    @  <td class="form_label">Password:</td>
    @  <td><input type="password" id="p" name="p" value="" size="30" /></td>
    @ </tr>
    if( P("HTTPS")==0 ){
      @ <tr><td class="form_label">Warning:</td>
      @ <td><span class='securityWarning'>
      @ Your password will be sent in the clear over an
      @ unencrypted connection.
      if( g.sslNotAvailable ){
        @ No encrypted connection is available on this server.
      }else{
        @ Consider logging in at
        @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
      }
      @ </span></td></tr>
    }





    if( g.zLogin==0 && (anonFlag || zGoto==0) ){
      zAnonPw = db_text(0, "SELECT pw FROM user"
                           " WHERE login='anonymous'"
                           "   AND cap!=''");
    }
    @ <tr>
    @   <td></td>
    @   <td><input type="submit" name="in" value="Login"></td>
    @ </tr>
    if( !noAnon && login_self_register_available(0) ){
      @ <tr>

      @   <td></td>
      @   <td><input type="submit" name="self" value="Create A New Account">
      @ </tr>
    }
    @ </table>
    if( zAnonPw && !noAnon ){
      unsigned int uSeed = captcha_seed();
      const char *zDecoded = captcha_decode(uSeed);
      int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
      char *zCaptcha = captcha_render(zDecoded);
  
      @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
      @ Visitors may enter <b>anonymous</b> as the user-ID with
      @ the 8-character hexadecimal password shown below:</p>
      @ <div class="captcha"><table class="captcha"><tr><td>\
      @ <pre class="captcha">
      @ %h(zCaptcha)
      @ </pre></td></tr></table>
      if( bAutoCaptcha ) {
         @ <input type="button" value="Fill out captcha" id='autofillButton' \
         @ data-af='%s(zDecoded)' />
         style_load_one_js_file("login.js");
      }
      @ </div>
      free(zCaptcha);
    }
    @ </form>
  }
  if( login_is_individual() ){
    if( g.perm.EmailAlert && alert_enabled() ){
      @ <hr>
      @ <p>Configure <a href="%R/alerts">Email Alerts</a>
      @ for user <b>%h(g.zLogin)</b></p>
    }
    if( g.perm.Password ){
      @ <hr>
      @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
      form_begin(0, "%R/login");
      @ <table>
      @ <tr><td class="form_label">Old Password:</td>
      @ <td><input type="password" name="p" size="30" /></td></tr>
      @ <tr><td class="form_label">New Password:</td>
      @ <td><input type="password" name="n1" size="30" /></td></tr>
      @ <tr><td class="form_label">Repeat New Password:</td>
      @ <td><input type="password" name="n2" size="30" /></td></tr>
      @ <tr><td></td>
      @ <td><input type="submit" value="Change Password" /></td></tr>
      @ </table>
      @ </form>
    }
  }
  style_footer();
}

/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode.  Transfer those credentials to the local
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
    zDecode[i] = 0;
    zUsername = zDecode;
    zPasswd = &zDecode[i+1];

    /* Attempting to log in as the user provided by HTTP
    ** basic auth
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid>0 ){
      record_login_attempt(zUsername, zIpAddr, 1);
    }else{
      record_login_attempt(zUsername, zIpAddr, 0);

      /* The user attempted to login specifically with HTTP basic
      ** auth, but provided invalid credentials. Inform them of







|







960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
    zDecode[i] = 0;
    zUsername = zDecode;
    zPasswd = &zDecode[i+1];

    /* Attempting to log in as the user provided by HTTP
    ** basic auth
    */
    uid = login_search_uid(&zUsername, zPasswd);
    if( uid>0 ){
      record_login_attempt(zUsername, zIpAddr, 1);
    }else{
      record_login_attempt(zUsername, zIpAddr, 0);

      /* The user attempted to login specifically with HTTP basic
      ** auth, but provided invalid credentials. Inform them of
1132
1133
1134
1135
1136
1137
1138


1139
1140
1141
1142
1143
1144
1145
1146
  /* If the public-pages glob pattern is defined and REQUEST_URI matches
  ** one of the globs in public-pages, then also add in all default-perms
  ** permissions.
  */
  zPublicPages = db_get("public-pages",0);
  if( zPublicPages!=0 ){
    Glob *pGlob = glob_create(zPublicPages);


    if( glob_match(pGlob, PD("REQUEST_URI","no-match")) ){
      login_set_capabilities(db_get("default-perms","u"), 0);
    }
    glob_free(pGlob);
  }
}

/*







>
>
|







1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
  /* If the public-pages glob pattern is defined and REQUEST_URI matches
  ** one of the globs in public-pages, then also add in all default-perms
  ** permissions.
  */
  zPublicPages = db_get("public-pages",0);
  if( zPublicPages!=0 ){
    Glob *pGlob = glob_create(zPublicPages);
    const char *zUri = PD("REQUEST_URI","");
    zUri += (int)strlen(g.zTop);
    if( glob_match(pGlob, zUri) ){
      login_set_capabilities(db_get("default-perms","u"), 0);
    }
    glob_free(pGlob);
  }
}

/*
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
                             p->RdWiki = p->WrWiki = p->NewWiki =
                             p->ApndWiki = p->Hyperlink = p->Clone =
                             p->NewTkt = p->Password = p->RdAddr =
                             p->TktFmt = p->Attach = p->ApndTkt =
                             p->ModWiki = p->ModTkt = p->Delete =
                             p->RdForum = p->WrForum = p->ModForum =
                             p->WrTForum = p->AdminForum =
                             p->EmailAlert = p->Announce =
                             p->WrUnver = p->Private = 1;
                             /* Fall thru into Read/Write */
      case 'i':   p->Read = p->Write = 1;                      break;
      case 'o':   p->Read = 1;                                 break;
      case 'z':   p->Zip = 1;                                  break;

      case 'd':   p->Delete = 1;                               break;







|







1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
                             p->RdWiki = p->WrWiki = p->NewWiki =
                             p->ApndWiki = p->Hyperlink = p->Clone =
                             p->NewTkt = p->Password = p->RdAddr =
                             p->TktFmt = p->Attach = p->ApndTkt =
                             p->ModWiki = p->ModTkt = p->Delete =
                             p->RdForum = p->WrForum = p->ModForum =
                             p->WrTForum = p->AdminForum =
                             p->EmailAlert = p->Announce = p->Debug =
                             p->WrUnver = p->Private = 1;
                             /* Fall thru into Read/Write */
      case 'i':   p->Read = p->Write = 1;                      break;
      case 'o':   p->Read = 1;                                 break;
      case 'z':   p->Zip = 1;                                  break;

      case 'd':   p->Delete = 1;                               break;
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251

1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262

1263
1264
1265
1266
1267
1268
1269
1270
1271
      case '3':   p->WrForum = 1;
      case '2':   p->RdForum = 1;                              break;

      case '7':   p->EmailAlert = 1;                           break;
      case 'A':   p->Announce = 1;                             break;
      case 'D':   p->Debug = 1;                                break;

      /* The "u" privileges is a little different.  It recursively
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zUser;

          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser, flags | LOGIN_IGNORE_UV);
        }
        break;
      }

      /* The "v" privileges is a little different.  It recursively
      ** inherits all privileges of the user named "developer" */
      case 'v': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zDev;

          zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
          login_set_capabilities(zDev, flags | LOGIN_IGNORE_UV);
        }
        break;
      }
    }
  }
}








|


|

>

|




|


|

>

|







1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
      case '3':   p->WrForum = 1;
      case '2':   p->RdForum = 1;                              break;

      case '7':   p->EmailAlert = 1;                           break;
      case 'A':   p->Announce = 1;                             break;
      case 'D':   p->Debug = 1;                                break;

      /* The "u" privilege recursively
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( p->XReader==0 ){
          const char *zUser;
          p->XReader = 1;
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser, flags);
        }
        break;
      }

      /* The "v" privilege recursively
      ** inherits all privileges of the user named "developer" */
      case 'v': {
        if( p->XDeveloper==0 ){
          const char *zDev;
          p->XDeveloper = 1;
          zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
          login_set_capabilities(zDev, flags);
        }
        break;
      }
    }
  }
}

1457
1458
1459
1460
1461
1462
1463

1464
1465
1466
1467





1468
1469
1470
1471
1472
1473
1474

1475






1476
1477
1478
1479
1480
1481
1482












1483
1484
1485

1486
1487
1488
1489
1490
1491

1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503

1504




1505
1506


1507








1508
1509
1510




1511
1512
1513
1514
1515
1516
1517
1518
1519





1520
1521








1522
1523

1524
1525








1526
1527
1528



1529
1530
1531

1532
1533























1534



1535




1536
1537
1538
1539
1540
1541
1542

1543
1544
1545
1546
1547
1548
1549
1550
1551
1552







1553




1554
1555
1556
1557





1558
1559







1560
1561



1562
1563
1564
1565



1566
1567
1568
1569



1570
1571
1572
1573
1574


1575
1576
1577
1578
1579
1580
1581
1582
/*
** WEBPAGE: register
**
** Page to allow users to self-register.  The "self-register" setting
** must be enabled for this page to operate.
*/
void register_page(void){

  const char *zUsername, *zPasswd, *zConfirm, *zContact, *zCS, *zPw, *zCap;
  unsigned int uSeed;
  const char *zDecoded;
  char *zCaptcha;





  if( !db_get_boolean("self-register", 0) ){
    style_header("Registration not possible");
    @ <p>This project does not allow user self-registration. Please contact the
    @ project administrator to obtain an account.</p>
    style_footer();
    return;
  }








  style_header("Register");
  zUsername = P("u");
  zPasswd = P("p");
  zConfirm = P("cp");
  zContact = P("c");
  zCap = P("cap");
  zCS = P("cs"); /* Captcha Secret */













  /* Try to make any sense from user input. */
  if( P("new") ){

    if( zCS==0 ) fossil_redirect_home();  /* Forged request */
    zPw = captcha_decode((unsigned int)atoi(zCS));
    if( !(zUsername && zPasswd && zConfirm && zContact) ){
      @ <p><span class="loginError">
      @ All fields are obligatory.
      @ </span></p>

    }else if( strlen(zPasswd) < 6){
      @ <p><span class="loginError">
      @ Password too weak.
      @ </span></p>
    }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
      @ <p><span class="loginError">
      @ The two copies of your new passwords do not match.
      @ </span></p>
    }else if( fossil_stricmp(zPw, zCap)!=0 ){
      @ <p><span class="loginError">
      @ Captcha text invalid.
      @ </span></p>

    }else{




      /* This almost is stupid copy-paste of code from user.c:user_cmd(). */
      Blob passwd, login, caps, contact;











      blob_init(&login, zUsername, -1);
      blob_init(&contact, zContact, -1);
      blob_init(&caps, db_get("default-perms", "u"), -1);




      blob_init(&passwd, zPasswd, -1);

      if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
        /* Here lies the reason I don't use zErrMsg - it would not substitute
         * this %s(zUsername), or at least I don't know how to force it to.*/
        @ <p><span class="loginError">
        @ %h(zUsername) already exists.
        @ </span></p>
      }else{





        char *zPw = sha1_shared_secret(blob_str(&passwd), blob_str(&login), 0);
        int uid;








        db_multi_exec(
            "INSERT INTO user(login,pw,cap,info,mtime)"

            "VALUES(%B,%Q,%B,%B,strftime('%%s','now'))",
            &login, zPw, &caps, &contact








            );
        free(zPw);




        /* The user is registered, now just log him in. */
        uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUsername);
        login_set_user_cookie( zUsername, uid, NULL );

        redirect_to_g();
























      }



    }




  }

  /* Prepare the captcha. */
  uSeed = captcha_seed();
  zDecoded = captcha_decode(uSeed);
  zCaptcha = captcha_render(zDecoded);


  /* Print out the registration form. */
  form_begin(0, "%R/register");
  if( P("g") ){
    @ <input type="hidden" name="g" value="%h(P("g"))" />
  }
  @ <p><input type="hidden" name="cs" value="%u(uSeed)" />
  @ <table class="login_out">
  @ <tr>
  @   <td class="form_label" align="right">User ID:</td>
  @   <td><input type="text" id="u" name="u" value="" size="30" /></td>







  @ </tr>




  @ <tr>
  @   <td class="form_label" align="right">Password:</td>
  @   <td><input type="password" id="p" name="p" value="" size="30" /></td>
  @ </tr>





  @ <tr>
  @   <td class="form_label" align="right">Confirm password:</td>







  @   <td><input type="password" id="cp" name="cp" value="" size="30" /></td>
  @ </tr>



  @ <tr>
  @   <td class="form_label" align="right">Contact info:</td>
  @   <td><input type="text" id="c" name="c" value="" size="30" /></td>
  @ </tr>



  @ <tr>
  @   <td class="form_label" align="right">Captcha text (below):</td>
  @   <td><input type="text" id="cap" name="cap" value="" size="30" /></td>
  @ </tr>



  @ <tr><td></td>
  @ <td><input type="submit" name="new" value="Register" /></td></tr>
  @ </table>
  @ <div class="captcha"><table class="captcha"><tr><td><pre>
  @ %h(zCaptcha)


  @ </pre></td></tr></table></div>
  @ </form>
  style_footer();

  free(zCaptcha);
}

/*







>
|



>
>
>
>
>







>

>
>
>
>
>
>
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
|
|
<
|
|
|
>
|
|
|
<
|
|
|
<
|
|
<
<
>
|
>
>
>
>
|
<
>
>
|
>
>
>
>
>
>
>
>
|
|
|
>
>
>
>
|
|
|
<
<
<
|
<
|
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
|
|
>
|
<
>
>
>
>
>
>
>
>
|
<
|
>
>
>
|
<
<
>

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
|
>
>
>
>







>





|



|
>
>
>
>
>
>
>

>
>
>
>

|
|

>
>
>
>
>
|
|
>
>
>
>
>
>
>
|
|
>
>
>

|
|

>
>
>

|
|

>
>
>



|

>
>
|







1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576

1577
1578
1579
1580
1581
1582
1583

1584
1585
1586

1587
1588


1589
1590
1591
1592
1593
1594
1595

1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616



1617

1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637

1638
1639
1640
1641
1642
1643
1644
1645
1646

1647
1648
1649
1650
1651


1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
/*
** WEBPAGE: register
**
** Page to allow users to self-register.  The "self-register" setting
** must be enabled for this page to operate.
*/
void register_page(void){
  const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
  const char *zDName;
  unsigned int uSeed;
  const char *zDecoded;
  char *zCaptcha;
  int iErrLine = -1;
  const char *zErr = 0;
  char *zPerms;             /* Permissions for the default user */
  int canDoAlerts = 0;      /* True if receiving email alerts is possible */
  int doAlerts = 0;         /* True if subscription is wanted too */
  if( !db_get_boolean("self-register", 0) ){
    style_header("Registration not possible");
    @ <p>This project does not allow user self-registration. Please contact the
    @ project administrator to obtain an account.</p>
    style_footer();
    return;
  }
  zPerms = db_get("default-perms","u");

  /* Prompt the user for email alerts if this repository is configured for
  ** email alerts and if the default permissions include "7" */
  canDoAlerts = alert_tables_exist() && db_int(0,
    "SELECT fullcap(%Q) GLOB '*7*'", zPerms
  );
  doAlerts = canDoAlerts && atoi(PD("alerts","1"))!=0;

  zUserID = PDT("u","");
  zPasswd = PDT("p","");
  zConfirm = PDT("cp","");
  zEAddr = PDT("ea","");
  zDName = PDT("dn","");

  /* Verify user imputs */
  if( P("new")==0 || !cgi_csrf_safe(1) ){
    /* This is not a valid form submission.  Fall through into
    ** the form display */
  }else if( !captcha_is_correct(1) ){
    iErrLine = 6;
    zErr = "Incorrect CAPTCHA";
  }else if( strlen(zUserID)<3 ){
    iErrLine = 1;
    zErr = "User ID too short. Must be at least 3 characters.";
  }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){
    iErrLine = 1;
    zErr = "User ID may not contain spaces or special characters.";
  }else if( zDName[0]==0 ){
    iErrLine = 2;
    zErr = "Required";
  }else if( zEAddr[0]==0 ){
    iErrLine = 3;

    zErr = "Required";
  }else if( email_copy_addr(zEAddr,0)==0 ){
    iErrLine = 3;
    zErr = "Not a valid email address";
  }else if( strlen(zPasswd)<6 ){
    iErrLine = 4;
    zErr = "Password must be at least 6 characters long";

  }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
    iErrLine = 5;
    zErr = "Passwords do not match";

  }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zUserID) ){
    iErrLine = 1;


    zErr = "This User ID is already taken. Choose something different.";
  }else if(
      /* If the email is found anywhere in USER.INFO... */
      db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr)
    ||
      /* Or if the email is a verify subscriber email with an associated
      ** user... */

      db_exists(
        "SELECT 1 FROM subscriber WHERE semail=%Q AND suname IS NOT NULL"
        " AND sverified",zEAddr)
   ){
    iErrLine = 3;
    zErr = "This email address is already claimed by another user";
  }else{
    /* If all of the tests above have passed, that means that the submitted
    ** form contains valid data and we can proceed to create the new login */
    Blob sql;
    int uid;
    char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
    blob_init(&sql, 0, 0);
    blob_append_sql(&sql,
       "INSERT INTO user(login,pw,cap,info,mtime)\n"
       "VALUES(%Q,%Q,%Q,"
       "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
       zUserID, zPass, zPerms, zDName, zEAddr, g.zIpAddr);
    fossil_free(zPass);
    db_multi_exec("%s", blob_sql_text(&sql));
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);



    login_set_user_cookie(zUserID, uid, NULL);

    if( doAlerts ){
      /* Also make the new user a subscriber. */
      Blob hdr, body;
      AlertSender *pSender;
      sqlite3_int64 id;   /* New subscriber Id */
      const char *zCode;  /* New subscriber code (in hex) */
      const char *zGoto = P("g");
      int nsub = 0;
      char ssub[20];
      ssub[nsub++] = 'a';
      if( g.perm.Read )    ssub[nsub++] = 'c';
      if( g.perm.RdForum ) ssub[nsub++] = 'f';
      if( g.perm.RdTkt )   ssub[nsub++] = 't';
      if( g.perm.RdWiki )  ssub[nsub++] = 'w';
      ssub[nsub] = 0;
      /* Also add the user to the subscriber table. */
      db_multi_exec(
        "INSERT INTO subscriber(semail,suname,"
        "  sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
        " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"

        " ON CONFLICT(semail) DO UPDATE"
        "   SET suname=excluded.suname",
        /* semail */    zEAddr,
        /* suname */    zUserID,
        /* sverified */ 0,
        /* sdigest */   0,
        /* ssub */      ssub,
        /* smip */      g.zIpAddr
      );

      id = db_last_insert_rowid();
      if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q"
                    "  AND sverified", zEAddr) ){
        /* This the case where the user was formerly a verified subscriber
        ** and here they have also registered as a user as well.  It is


        ** not necessary to repeat the verfication step */
        redirect_to_g();
      }
      zCode = db_text(0,
           "SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld",
           id);
      /* A verification email */
      pSender = alert_sender_new(0,0);
      blob_init(&hdr,0,0);
      blob_init(&body,0,0);
      blob_appendf(&hdr, "To: <%s>\n", zEAddr);
      blob_appendf(&hdr, "Subject: Subscription verification\n");
      alert_append_confirmation_message(&body, zCode);
      alert_send(pSender, &hdr, &body, 0);
      style_header("Email Verification");
      if( pSender->zErr ){
        @ <h1>Internal Error</h1>
        @ <p>The following internal error was encountered while trying
        @ to send the confirmation email:
        @ <blockquote><pre>
        @ %h(pSender->zErr)
        @ </pre></blockquote>
      }else{
        @ <p>An email has been sent to "%h(zEAddr)". That email contains a
        @ hyperlink that you must click on in order to activate your
        @ subscription.</p>
      }
      alert_sender_free(pSender);
      if( zGoto ){
        @ <p><a href='%h(zGoto)'>Continue</a>
      }
      style_footer();
      return;
    }
    redirect_to_g();
  }

  /* Prepare the captcha. */
  uSeed = captcha_seed();
  zDecoded = captcha_decode(uSeed);
  zCaptcha = captcha_render(zDecoded);

  style_header("Register");
  /* Print out the registration form. */
  form_begin(0, "%R/register");
  if( P("g") ){
    @ <input type="hidden" name="g" value="%h(P("g"))" />
  }
  @ <p><input type="hidden" name="captchaseed" value="%u(uSeed)" />
  @ <table class="login_out">
  @ <tr>
  @   <td class="form_label" align="right">User ID:</td>
  @   <td><input type="text" name="u" value="%h(zUserID)" size="30"></td>
  @
  if( iErrLine==1 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ <tr>
  @   <td class="form_label" align="right">Display Name:</td>
  @   <td><input type="text" name="dn" value="%h(zDName)" size="30"></td>
  @ </tr>
  if( iErrLine==2 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ </tr>
  @ <tr>
  @   <td class="form_label" align="right">Email Address:</td>
  @   <td><input type="text" name="ea" value="%h(zEAddr)" size="30"></td>
  @ </tr>
  if( iErrLine==3 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  if( canDoAlerts ){
    int a = atoi(PD("alerts","1"));
    @ <tr>
    @   <td class="form_label" align="right">Email&nbsp;Alerts?</td>
    @   <td><select size='1' name='alerts'>
    @       <option value="1" %s(a?"selected":"")>Yes</option>
    @       <option value="0" %s(!a?"selected":"")>No</option>
    @   </select></td></tr>
  }
  @ <tr>
  @   <td class="form_label" align="right">Password:</td>
  @   <td><input type="password" name="p" value="%h(zPasswd)" size="30"></td>
  @ <tr>
  if( iErrLine==4 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ <tr>
  @   <td class="form_label" align="right">Confirm:</td>
  @   <td><input type="password" name="cp" value="%h(zConfirm)" size="30"></td>
  @ </tr>
  if( iErrLine==5 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ <tr>
  @   <td class="form_label" align="right">Captcha:</td>
  @   <td><input type="text" name="captcha" value="" size="30"></td>
  @ </tr>
  if( iErrLine==6 ){
    @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
  }
  @ <tr><td></td>
  @ <td><input type="submit" name="new" value="Register" /></td></tr>
  @ </table>
  @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
  @ %h(zCaptcha)
  @ </pre>
  @ Enter this 8-letter code in the "Captcha" box above.
  @ </td></tr></table></div>
  @ </form>
  style_footer();

  free(zCaptcha);
}

/*

Changes to src/main.c.

18
19
20
21
22
23
24



25
26
27
28
29
30
31
32
33
34



35
36
37
38
39
40
41
** This module codes the main() procedure that runs first when the
** program is invoked.
*/
#include "VERSION.h"
#include "config.h"
#if defined(_WIN32)
#  include <windows.h>



#endif
#include "main.h"
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h> /* atexit() */
#if !defined(_WIN32)
#  include <errno.h> /* errno global */



#endif
#ifdef FOSSIL_ENABLE_SSL
#  include "openssl/crypto.h"
#endif
#if defined(FOSSIL_ENABLE_MINIZ)
#  define MINIZ_HEADER_FILE_ONLY
#  include "miniz.c"







>
>
>










>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
** This module codes the main() procedure that runs first when the
** program is invoked.
*/
#include "VERSION.h"
#include "config.h"
#if defined(_WIN32)
#  include <windows.h>
#  include <io.h>
#  define isatty(h) _isatty(h)
#  define GETPID (int)GetCurrentProcessId
#endif
#include "main.h"
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h> /* atexit() */
#if !defined(_WIN32)
#  include <errno.h> /* errno global */
#  include <unistd.h>
#  include <signal.h>
#  define GETPID getpid
#endif
#ifdef FOSSIL_ENABLE_SSL
#  include "openssl/crypto.h"
#endif
#if defined(FOSSIL_ENABLE_MINIZ)
#  define MINIZ_HEADER_FILE_ONLY
#  include "miniz.c"
88
89
90
91
92
93
94
95
96
97
98



99
100
101
102
103
104
105
  char Zip;              /* z: download zipped artifact via /zip URL */
  char Private;          /* x: can send and receive private content */
  char WrUnver;          /* y: can push unversioned content */
  char RdForum;          /* 2: Read forum posts */
  char WrForum;          /* 3: Create new forum posts */
  char WrTForum;         /* 4: Post to forums not subject to moderation */
  char ModForum;         /* 5: Moderate (approve or reject) forum posts */
  char AdminForum;       /* 6: Edit forum posts by other users */
  char EmailAlert;       /* 7: Sign up for email notifications */
  char Announce;         /* A: Send announcements */
  char Debug;            /* D: show extra Fossil debugging features */



};

#ifdef FOSSIL_ENABLE_TCL
/*
** All Tcl related context information is in this structure.  This structure
** definition has been copied from and should be kept in sync with the one in
** "th_tcl.c".







|



>
>
>







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  char Zip;              /* z: download zipped artifact via /zip URL */
  char Private;          /* x: can send and receive private content */
  char WrUnver;          /* y: can push unversioned content */
  char RdForum;          /* 2: Read forum posts */
  char WrForum;          /* 3: Create new forum posts */
  char WrTForum;         /* 4: Post to forums not subject to moderation */
  char ModForum;         /* 5: Moderate (approve or reject) forum posts */
  char AdminForum;       /* 6: Set or remove capability 4 on other users */
  char EmailAlert;       /* 7: Sign up for email notifications */
  char Announce;         /* A: Send announcements */
  char Debug;            /* D: show extra Fossil debugging features */
  /* These last two are included to block infinite recursion */
  char XReader;          /* u: Inherit all privileges of "reader" */
  char XDeveloper;       /* v: Inherit all privileges of "developer" */
};

#ifdef FOSSIL_ENABLE_TCL
/*
** All Tcl related context information is in this structure.  This structure
** definition has been copied from and should be kept in sync with the one in
** "th_tcl.c".
597
598
599
600
601
602
603

















604
605
606
607
608
609
610
#endif
int main(int argc, char **argv)
#endif
{
  const char *zCmdName = "unknown";
  const CmdOrPage *pCmd = 0;
  int rc;


















  fossil_limit_memory(1);
  if( sqlite3_libversion_number()<3014000 ){
    fossil_panic("Unsuitable SQLite version %s, must be at least 3.14.0",
                 sqlite3_libversion());
  }
  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
#endif
int main(int argc, char **argv)
#endif
{
  const char *zCmdName = "unknown";
  const CmdOrPage *pCmd = 0;
  int rc;

#if !defined(_WIN32_WCE)
  if( fossil_getenv("FOSSIL_BREAK") ){
    if( isatty(0) && isatty(2) ){
      fprintf(stderr,
          "attach debugger to process %d and press any key to continue.\n",
          GETPID());
      fgetc(stdin);
    }else{
#if defined(_WIN32) || defined(WIN32)
      DebugBreak();
#elif defined(SIGTRAP)
      raise(SIGTRAP);
#endif
    }
  }
#endif

  fossil_limit_memory(1);
  if( sqlite3_libversion_number()<3014000 ){
    fossil_panic("Unsuitable SQLite version %s, must be at least 3.14.0",
                 sqlite3_libversion());
  }
  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    g.zVfsName = fossil_getenv("FOSSIL_VFS");
  }
  if( g.zVfsName ){
    sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
    if( pVfs ){
      sqlite3_vfs_register(pVfs, 1);
    }else{
      fossil_panic("no such VFS: \"%s\"", g.zVfsName);
    }
  }
  if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
    zCmdName = "cgi";
    g.isHTTP = 1;
  }else if( g.argc<2 && !fossilExeHasAppendedRepo() ){
    fossil_print(







|







664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    g.zVfsName = fossil_getenv("FOSSIL_VFS");
  }
  if( g.zVfsName ){
    sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
    if( pVfs ){
      sqlite3_vfs_register(pVfs, 1);
    }else{
      fossil_fatal("no such VFS: \"%s\"", g.zVfsName);
    }
  }
  if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
    zCmdName = "cgi";
    g.isHTTP = 1;
  }else if( g.argc<2 && !fossilExeHasAppendedRepo() ){
    fossil_print(
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    fossil_init_flags_from_options();
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;
    if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
    if( zChdir && file_chdir(zChdir, 0) ){
      fossil_panic("unable to change directories to %s", zChdir);
    }
    if( find_option("help",0,0)!=0 ){
      /* If --help is found anywhere on the command line, translate the command
       * to "fossil help cmdname" where "cmdname" is the first argument that
       * does not begin with a "-" character.  If all arguments start with "-",
       * translate to "fossil help argv[1] argv[2]...". */
      int i, nNewArgc;







|







715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    fossil_init_flags_from_options();
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;
    if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
    if( zChdir && file_chdir(zChdir, 0) ){
      fossil_fatal("unable to change directories to %s", zChdir);
    }
    if( find_option("help",0,0)!=0 ){
      /* If --help is found anywhere on the command line, translate the command
       * to "fossil help cmdname" where "cmdname" is the first argument that
       * does not begin with a "-" character.  If all arguments start with "-",
       * translate to "fossil help argv[1] argv[2]...". */
      int i, nNewArgc;
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
      rc = Th_CommandHook(zCmdName, 0);
    }else{
      rc = TH_OK;
    }
    if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
      if( rc==TH_OK || rc==TH_RETURN ){
#endif
        fossil_panic("%s: unknown command: %s\n"
                     "%s: use \"help\" for more information",
                     g.argv[0], zCmdName, g.argv[0]);
#ifdef FOSSIL_ENABLE_TH1_HOOKS
      }
      if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
        Th_CommandNotify(zCmdName, 0);
      }







|







780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
      rc = Th_CommandHook(zCmdName, 0);
    }else{
      rc = TH_OK;
    }
    if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
      if( rc==TH_OK || rc==TH_RETURN ){
#endif
        fossil_fatal("%s: unknown command: %s\n"
                     "%s: use \"help\" for more information",
                     g.argv[0], zCmdName, g.argv[0]);
#ifdef FOSSIL_ENABLE_TH1_HOOKS
      }
      if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
        Th_CommandNotify(zCmdName, 0);
      }
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
  return 0;
}

/*
** Print a usage comment and quit
*/
void usage(const char *zFormat){
  fossil_panic("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
}

/*
** Remove n elements from g.argv beginning with the i-th element.
*/
static void remove_from_argv(int i, int n){
  int j;







|







845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
  return 0;
}

/*
** Print a usage comment and quit
*/
void usage(const char *zFormat){
  fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
}

/*
** Remove n elements from g.argv beginning with the i-th element.
*/
static void remove_from_argv(int i, int n){
  int j;
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
** Any remaining command-line argument begins with "-" print
** an error message and quit.
*/
void verify_all_options(void){
  int i;
  for(i=1; i<g.argc; i++){
    if( g.argv[i][0]=='-' && g.argv[i][1]!=0 ){
      fossil_panic(
        "unrecognized command-line option, or missing argument: %s",
        g.argv[i]);
    }
  }
}

/*







|







963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
** Any remaining command-line argument begins with "-" print
** an error message and quit.
*/
void verify_all_options(void){
  int i;
  for(i=1; i<g.argc; i++){
    if( g.argv[i][0]=='-' && g.argv[i][1]!=0 ){
      fossil_fatal(
        "unrecognized command-line option, or missing argument: %s",
        g.argv[i]);
    }
  }
}

/*
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
    if( strncmp(g.zTop, "http://", 7)==0 ){
      /* it is HTTP, replace prefix with HTTPS. */
      g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
    }else if( strncmp(g.zTop, "https://", 8)==0 ){
      /* it is already HTTPS, use it. */
      g.zHttpsURL = mprintf("%s", g.zTop);
    }else{
      fossil_panic("argument to --baseurl should be 'http://host/path'"
                   " or 'https://host/path'");
    }
    for(i=n=0; (c = g.zTop[i])!=0; i++){
      if( c=='/' ){
        n++;
        if( n==3 ){
          g.zTop += i;
          break;
        }
      }
    }
    if( g.zTop==g.zBaseURL ){
      fossil_panic("argument to --baseurl should be 'http://host/path'"
                   " or 'https://host/path'");
    }
    if( g.zTop[1]==0 ) g.zTop++;
  }else{
    zHost = PD("HTTP_HOST","");
    zMode = PD("HTTPS","off");
    zCur = PD("SCRIPT_NAME","/");







|












|







1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
    if( strncmp(g.zTop, "http://", 7)==0 ){
      /* it is HTTP, replace prefix with HTTPS. */
      g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
    }else if( strncmp(g.zTop, "https://", 8)==0 ){
      /* it is already HTTPS, use it. */
      g.zHttpsURL = mprintf("%s", g.zTop);
    }else{
      fossil_fatal("argument to --baseurl should be 'http://host/path'"
                   " or 'https://host/path'");
    }
    for(i=n=0; (c = g.zTop[i])!=0; i++){
      if( c=='/' ){
        n++;
        if( n==3 ){
          g.zTop += i;
          break;
        }
      }
    }
    if( g.zTop==g.zBaseURL ){
      fossil_fatal("argument to --baseurl should be 'http://host/path'"
                   " or 'https://host/path'");
    }
    if( g.zTop[1]==0 ) g.zTop++;
  }else{
    zHost = PD("HTTP_HOST","");
    zMode = PD("HTTPS","off");
    zCur = PD("SCRIPT_NAME","/");
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
          }
          zDir[i] = '/';
        }
        zRepo = &zDir[i];
      }
    }
    if( stat(zRepo, &sStat)!=0 ){
      fossil_panic("cannot stat() repository: %s", zRepo);
    }
    i = setgid(sStat.st_gid);
    i = i || setuid(sStat.st_uid);
    if(i){
      fossil_panic("setgid/uid() failed with errno %d", errno);
    }
    if( g.db==0 && file_isfile(zRepo, ExtFILE) ){
      db_open_repository(zRepo);
    }
  }
#endif
  return zRepo;







|




|







1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
          }
          zDir[i] = '/';
        }
        zRepo = &zDir[i];
      }
    }
    if( stat(zRepo, &sStat)!=0 ){
      fossil_fatal("cannot stat() repository: %s", zRepo);
    }
    i = setgid(sStat.st_gid);
    i = i || setuid(sStat.st_uid);
    if(i){
      fossil_fatal("setgid/uid() failed with errno %d", errno);
    }
    if( g.db==0 && file_isfile(zRepo, ExtFILE) ){
      db_open_repository(zRepo);
    }
  }
#endif
  return zRepo;
1327
1328
1329
1330
1331
1332
1333

1334
1335
1336
1337
1338
1339
1340
    vfile_scan(&base, blob_size(&base), 0, 0, 0);
    db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
    allRepo = 0;
  }
  @ <html>
  @ <head>
  @ <base href="%s(g.zBaseURL)/" />

  @ <title>Repository List</title>
  @ </head>
  @ <body>
  n = db_int(0, "SELECT count(*) FROM sfile");
  if( n>0 ){
    Stmt q;
    sqlite3_int64 iNow, iMTime;







>







1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
    vfile_scan(&base, blob_size(&base), 0, 0, 0);
    db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
    allRepo = 0;
  }
  @ <html>
  @ <head>
  @ <base href="%s(g.zBaseURL)/" />
  @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
  @ <title>Repository List</title>
  @ </head>
  @ <body>
  n = db_int(0, "SELECT count(*) FROM sfile");
  if( n>0 ){
    Stmt q;
    sqlite3_int64 iNow, iMTime;
1646
1647
1648
1649
1650
1651
1652




1653

1654
1655
1656
1657
1658
1659
1660
        }else{
#ifdef FOSSIL_ENABLE_JSON
          if(g.json.isJsonMode){
            json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
            return;
          }
#endif




          @ <h1>Not Found</h1>

          cgi_set_status(404, "not found");
          cgi_reply();
        }
        return;
      }
      break;
    }







>
>
>
>

>







1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
        }else{
#ifdef FOSSIL_ENABLE_JSON
          if(g.json.isJsonMode){
            json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
            return;
          }
#endif
          @ <html><head>
          @ <meta name="viewport" \
          @ content="width=device-width, initial-scale=1.0">
          @ </head><body>
          @ <h1>Not Found</h1>
          @ </body>
          cgi_set_status(404, "not found");
          cgi_reply();
        }
        return;
      }
      break;
    }
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
  LPVOID *ppAddress,   /* The extracted pointer value. */
  SIZE_T *pnSize       /* The extracted size value. */
){
  unsigned int nSize = 0;
  if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
    *pnSize = (SIZE_T)nSize;
  }else{
    fossil_panic("failed to parse pid key");
  }
}
#endif

/*
** COMMAND: http*
**







|







2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
  LPVOID *ppAddress,   /* The extracted pointer value. */
  SIZE_T *pnSize       /* The extracted size value. */
){
  unsigned int nSize = 0;
  if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
    *pnSize = (SIZE_T)nSize;
  }else{
    fossil_fatal("failed to parse pid key");
  }
}
#endif

/*
** COMMAND: http*
**
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
                            zBrowser, zIpAddr, zInitPage);
    }
  }
  if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
  if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  db_close(1);
  if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
    fossil_panic("unable to listen on TCP socket %d", iPort);
  }
  if( zMaxLatency ){
    signal(SIGALRM, sigalrm_handler);
    alarm(atoi(zMaxLatency));
  }
  g.httpIn = stdin;
  g.httpOut = stdout;







|







2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
                            zBrowser, zIpAddr, zInitPage);
    }
  }
  if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
  if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  db_close(1);
  if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
    fossil_fatal("unable to listen on TCP socket %d", iPort);
  }
  if( zMaxLatency ){
    signal(SIGALRM, sigalrm_handler);
    alarm(atoi(zMaxLatency));
  }
  g.httpIn = stdin;
  g.httpOut = stdout;
2804
2805
2806
2807
2808
2809
2810


2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
** the administrator only.
**
**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
**     case=4           Error during SQL processing
**     case=5           Call the segfault handler


*/
void test_warning_page(void){
  int iCase = atoi(PD("case","0"));
  int i;
  login_check_credentials();
  if( !g.perm.Setup && !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_header("Warning Test Page");
  style_submenu_element("Error Log","%R/errorlog");
  if( iCase<1 || iCase>4 ){
    @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
    @ by clicking on one of the following cases:
  }else{
    @ <p>This is the test page for case=%d(iCase).  All possible cases:
  }
  for(i=1; i<=5; i++){
    @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
  }
  @ </p>
  @ <p><ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");







>
>

















|







2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
** the administrator only.
**
**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
**     case=4           Error during SQL processing
**     case=5           Call the segfault handler
**     case=6           Call webpage_assert()
**     case=7           Call webpage_error()
*/
void test_warning_page(void){
  int iCase = atoi(PD("case","0"));
  int i;
  login_check_credentials();
  if( !g.perm.Setup && !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_header("Warning Test Page");
  style_submenu_element("Error Log","%R/errorlog");
  if( iCase<1 || iCase>4 ){
    @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
    @ by clicking on one of the following cases:
  }else{
    @ <p>This is the test page for case=%d(iCase).  All possible cases:
  }
  for(i=1; i<=7; i++){
    @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
  }
  @ </p>
  @ <p><ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");
2850
2851
2852
2853
2854
2855
2856









2857
2858
2859
2860
    sqlite3_log(SQLITE_ERROR, "Test warning message during SQL");
    db_finalize(&q);
  }
  @ <li value='5'> simulate segfault handling
  if( iCase==5 ){
    sigsegv_handler(0);
  }









  @ </ol>
  @ <p>End of test</p>
  style_footer();
}







>
>
>
>
>
>
>
>
>




2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
    sqlite3_log(SQLITE_ERROR, "Test warning message during SQL");
    db_finalize(&q);
  }
  @ <li value='5'> simulate segfault handling
  if( iCase==5 ){
    sigsegv_handler(0);
  }
  @ <li value='6'> call webpage_assert(0)
  if( iCase==6 ){
    webpage_assert( 5==7 );
  }
  @ <li value='7'> call webpage_error()"
  if( iCase==7 ){
    cgi_reset_content();
    webpage_error("Case 7 from /test-warning");
  }
  @ </ol>
  @ <p>End of test</p>
  style_footer();
}

Changes to src/main.mk.

9
10
11
12
13
14
15

16
17
18

19
20
21
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#
# This file is included by primary Makefile.
#

XBCC = $(BCC) $(BCCFLAGS)
XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS)



SRC = \
  $(SRCDIR)/add.c \

  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \

  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \
  $(SRCDIR)/email.c \
  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \







>



>











>


















<







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
#
# This file is included by primary Makefile.
#

XBCC = $(BCC) $(BCCFLAGS)
XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS)

TESTFLAGS := -quiet

SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/alerts.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \
  $(SRCDIR)/capabilities.c \
  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \

  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \
106
107
108
109
110
111
112

113
114
115
116
117
118
119
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \

  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \







>







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/setupuser.c \
  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \
173
174
175
176
177
178
179

180
181
182
183
184
185
186
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \

  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \







>







176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \
  $(SRCDIR)/../skins/default/js.txt \
  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \
203
204
205
206
207
208
209

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

225
226
227
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \

  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \

  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \

  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \
  $(OBJDIR)/email_.c \
  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \







>















>











>


















<







207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/alerts_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \
  $(OBJDIR)/capabilities_.c \
  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \

  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \
312
313
314
315
316
317
318

319
320
321
322
323
324
325
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \

  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \







>







318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \
  $(OBJDIR)/setupuser_.c \
  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \
353
354
355
356
357
358
359

360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \

 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/descendants.o \
 $(OBJDIR)/diff.o \
 $(OBJDIR)/diffcmd.o \
 $(OBJDIR)/dispatch.o \
 $(OBJDIR)/doc.o \
 $(OBJDIR)/email.o \
 $(OBJDIR)/encode.o \
 $(OBJDIR)/etag.o \
 $(OBJDIR)/event.o \
 $(OBJDIR)/export.o \
 $(OBJDIR)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \







>











>


















<







360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397

398
399
400
401
402
403
404
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/alerts.o \
 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \
447
448
449
450
451
452
453

454
455
456
457
458
459
460
 $(OBJDIR)/regexp.o \
 $(OBJDIR)/report.o \
 $(OBJDIR)/rss.o \
 $(OBJDIR)/schema.o \
 $(OBJDIR)/search.o \
 $(OBJDIR)/security_audit.o \
 $(OBJDIR)/setup.o \

 $(OBJDIR)/sha1.o \
 $(OBJDIR)/sha1hard.o \
 $(OBJDIR)/sha3.o \
 $(OBJDIR)/shun.o \
 $(OBJDIR)/sitemap.o \
 $(OBJDIR)/skins.o \
 $(OBJDIR)/smtp.o \







>







455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
 $(OBJDIR)/regexp.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 \
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) -quiet $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid  $(SRCDIR)/../manifest  $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

$(OBJDIR)/default_css.h:	$(SRCDIR)/default_css.txt $(OBJDIR)/mkcss
	$(OBJDIR)/mkcss $(SRCDIR)/default_css.txt $(OBJDIR)/default_css.h








|







546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid  $(SRCDIR)/../manifest  $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

$(OBJDIR)/default_css.h:	$(SRCDIR)/default_css.txt $(OBJDIR)/mkcss
	$(OBJDIR)/mkcss $(SRCDIR)/default_css.txt $(OBJDIR)/default_css.h

686
687
688
689
690
691
692

693
694
695
696
697
698
699
700
701
702
703

704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
	$(OBJDIR)/mkindex $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h: $(OBJDIR)/mkbuiltin $(EXTRA_FILES)
	$(OBJDIR)/mkbuiltin --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
	$(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h \

	$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
	$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
	$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
	$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
	$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
	$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
	$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
	$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
	$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
	$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
	$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \

	$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
	$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
	$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
	$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
	$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
	$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
	$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
	$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
	$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
	$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
	$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
	$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
	$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
	$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
	$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
	$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
	$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
	$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \
	$(OBJDIR)/email_.c:$(OBJDIR)/email.h \
	$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
	$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
	$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
	$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
	$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
	$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
	$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \







>











>


















<







695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732

733
734
735
736
737
738
739
	$(OBJDIR)/mkindex $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h: $(OBJDIR)/mkbuiltin $(EXTRA_FILES)
	$(OBJDIR)/mkbuiltin --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
	$(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
	$(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
	$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
	$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
	$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
	$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
	$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
	$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
	$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
	$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
	$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
	$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
	$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \
	$(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \
	$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
	$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
	$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
	$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
	$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
	$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
	$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
	$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
	$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
	$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
	$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
	$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
	$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
	$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
	$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
	$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
	$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
	$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \

	$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
	$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
	$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
	$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
	$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
	$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
	$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \
780
781
782
783
784
785
786

787
788
789
790
791
792
793
	$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
	$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
	$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
	$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
	$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
	$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
	$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \

	$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
	$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
	$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
	$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
	$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
	$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
	$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \







>







790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
	$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
	$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
	$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
	$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
	$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
	$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
	$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \
	$(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \
	$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
	$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
	$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
	$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
	$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
	$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
	$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
832
833
834
835
836
837
838








839
840
841
842
843
844
845
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers









$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c








>
>
>
>
>
>
>
>







843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers

$(OBJDIR)/alerts_.c:	$(SRCDIR)/alerts.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/alerts.c >$@

$(OBJDIR)/alerts.o:	$(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c

$(OBJDIR)/alerts.h:	$(OBJDIR)/headers

$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c

920
921
922
923
924
925
926








927
928
929
930
931
932
933
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers









$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c








>
>
>
>
>
>
>
>







939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers

$(OBJDIR)/capabilities_.c:	$(SRCDIR)/capabilities.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/capabilities.c >$@

$(OBJDIR)/capabilities.o:	$(OBJDIR)/capabilities_.c $(OBJDIR)/capabilities.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/capabilities.o -c $(OBJDIR)/capabilities_.c

$(OBJDIR)/capabilities.h:	$(OBJDIR)/headers

$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c

1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
	$(OBJDIR)/translate $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers

$(OBJDIR)/email_.c:	$(SRCDIR)/email.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/email.c >$@

$(OBJDIR)/email.o:	$(OBJDIR)/email_.c $(OBJDIR)/email.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/email.o -c $(OBJDIR)/email_.c

$(OBJDIR)/email.h:	$(OBJDIR)/headers

$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<
<







1092
1093
1094
1095
1096
1097
1098








1099
1100
1101
1102
1103
1104
1105
	$(OBJDIR)/translate $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers









$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers
1584
1585
1586
1587
1588
1589
1590








1591
1592
1593
1594
1595
1596
1597
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers









$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c








>
>
>
>
>
>
>
>







1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers

$(OBJDIR)/setupuser_.c:	$(SRCDIR)/setupuser.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/setupuser.c >$@

$(OBJDIR)/setupuser.o:	$(OBJDIR)/setupuser_.c $(OBJDIR)/setupuser.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setupuser.o -c $(OBJDIR)/setupuser_.c

$(OBJDIR)/setupuser.h:	$(OBJDIR)/headers

$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c

Changes to src/makemake.tcl.

24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# project, simply add the basename to this list and rerun this script.
#
# Set the separate extra_files variable further down for how to add non-C
# files, such as string and BLOB resources.
#
set src {
  add

  allrepo
  attach
  backoffice
  bag
  bisect
  blob
  branch
  browse
  builtin
  bundle
  cache

  captcha
  cgi
  checkin
  checkout
  clearsign
  clone
  comformat
  configure
  content
  cookies
  db
  delta
  deltacmd
  descendants
  diff
  diffcmd
  dispatch
  doc
  email
  encode
  etag
  event
  export
  file
  finfo
  foci







>











>


















<







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

62
63
64
65
66
67
68
# project, simply add the basename to this list and rerun this script.
#
# Set the separate extra_files variable further down for how to add non-C
# files, such as string and BLOB resources.
#
set src {
  add
  alerts
  allrepo
  attach
  backoffice
  bag
  bisect
  blob
  branch
  browse
  builtin
  bundle
  cache
  capabilities
  captcha
  cgi
  checkin
  checkout
  clearsign
  clone
  comformat
  configure
  content
  cookies
  db
  delta
  deltacmd
  descendants
  diff
  diffcmd
  dispatch
  doc

  encode
  etag
  event
  export
  file
  finfo
  foci
117
118
119
120
121
122
123

124
125
126
127
128
129
130
  regexp
  report
  rss
  schema
  search
  security_audit
  setup

  sha1
  sha1hard
  sha3
  shun
  sitemap
  skins
  smtp







>







118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  regexp
  report
  rss
  schema
  search
  security_audit
  setup
  setupuser
  sha1
  sha1hard
  sha3
  shun
  sitemap
  skins
  smtp
282
283
284
285
286
287
288

289
290
291
292
293
294
295
#
# This file is included by primary Makefile.
#

XBCC = $(BCC) $(BCCFLAGS)
XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS)


}
writeln -nonewline "SRC ="
foreach s [lsort $src] {
  writeln -nonewline " \\\n  \$(SRCDIR)/$s.c"
}
writeln "\n"
writeln -nonewline "EXTRA_FILES ="







>







284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#
# This file is included by primary Makefile.
#

XBCC = $(BCC) $(BCCFLAGS)
XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS)

TESTFLAGS := -quiet
}
writeln -nonewline "SRC ="
foreach s [lsort $src] {
  writeln -nonewline " \\\n  \$(SRCDIR)/$s.c"
}
writeln "\n"
writeln -nonewline "EXTRA_FILES ="
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) -quiet $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
		$(SRCDIR)/../manifest \
		$(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

$(OBJDIR)/default_css.h:	$(SRCDIR)/default_css.txt $(OBJDIR)/mkcss







|







360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
		$(SRCDIR)/../manifest \
		$(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

$(OBJDIR)/default_css.h:	$(SRCDIR)/default_css.txt $(OBJDIR)/mkcss
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2o
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If







|













|







694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-weak-ssl-ciphers no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2p
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654

# Enable support for the SQLite Encryption Extension?
!ifndef USE_SEE
USE_SEE = 0
!endif

!if $(FOSSIL_ENABLE_SSL)!=0
SSLDIR    = $(B)\compat\openssl-1.0.2o
SSLINCDIR = $(SSLDIR)\inc32
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLLIBDIR = $(SSLDIR)\out32dll
!else
SSLLIBDIR = $(SSLDIR)\out32
!endif
SSLLFLAGS = /nologo /opt:ref /debug
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib crypt32.lib
!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64"
!message Using 'x64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN64A no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64a.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!elseif "$(PLATFORM)"=="ia64"
!message Using 'ia64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN64I no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64i.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!else
!message Assuming 'x86' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN32 no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_ms.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!endif
!endif

!if $(FOSSIL_ENABLE_TCL)!=0
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)







|











|














|




|














|




|














|







1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657

# Enable support for the SQLite Encryption Extension?
!ifndef USE_SEE
USE_SEE = 0
!endif

!if $(FOSSIL_ENABLE_SSL)!=0
SSLDIR    = $(B)\compat\openssl-1.0.2p
SSLINCDIR = $(SSLDIR)\inc32
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLLIBDIR = $(SSLDIR)\out32dll
!else
SSLLIBDIR = $(SSLDIR)\out32
!endif
SSLLFLAGS = /nologo /opt:ref /debug
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib crypt32.lib
!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64"
!message Using 'x64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN64A no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64a.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!elseif "$(PLATFORM)"=="ia64"
!message Using 'ia64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN64I no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64i.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!else
!message Assuming 'x86' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN32 no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_ms.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!endif
!endif

!if $(FOSSIL_ENABLE_TCL)!=0
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
writeln -nonewline "EXTRA_FILES   = "
set i 0
foreach s [lsort $extra_files] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  set s [file nativename $s]
  writeln -nonewline "\"\$(SRCDIR)\\${s}\""; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation]
writeln -nonewline "OBJ   = "
set i 0
foreach s [lsort [concat $src $AdditionalObj]] {







|







1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
writeln -nonewline "EXTRA_FILES   = "
set i 0
foreach s [lsort $extra_files] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  set s [regsub -all / $s \\]
  writeln -nonewline "\"\$(SRCDIR)\\${s}\""; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation]
writeln -nonewline "OBJ   = "
set i 0
foreach s [lsort [concat $src $AdditionalObj]] {

Changes to src/manifest.c.

32
33
34
35
36
37
38

39
40
41
42
43
44
45
#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5
#define CFTYPE_ATTACHMENT 6
#define CFTYPE_EVENT      7


/*
** File permissions used by Fossil internally.
*/
#define PERM_REG          0     /*  regular file  */
#define PERM_EXE          1     /*  executable    */
#define PERM_LNK          2     /*  symlink       */







>







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5
#define CFTYPE_ATTACHMENT 6
#define CFTYPE_EVENT      7
#define CFTYPE_FORUM      8

/*
** File permissions used by Fossil internally.
*/
#define PERM_REG          0     /*  regular file  */
#define PERM_EXE          1     /*  executable    */
#define PERM_LNK          2     /*  symlink       */
74
75
76
77
78
79
80

81
82
83
84
85
86


87
88
89
90
91
92
93
  char *zComment;       /* Decoded comment.  The C card. */
  double rDate;         /* Date and time from D card.  0.0 if no D card. */
  char *zUser;          /* Name of the user from the U card. */
  char *zRepoCksum;     /* MD5 checksum of the baseline content.  R card. */
  char *zWiki;          /* Text of the wiki page.  W card. */
  char *zWikiTitle;     /* Name of the wiki page. L card. */
  char *zMimetype;      /* Mime type of wiki or comment text.  N card.  */

  double rEventDate;    /* Date of an event.  E card. */
  char *zEventId;       /* Artifact hash for an event.  E card. */
  char *zTicketUuid;    /* UUID for a ticket. K card. */
  char *zAttachName;    /* Filename of an attachment. A card. */
  char *zAttachSrc;     /* Artifact hash for document being attached. A card. */
  char *zAttachTarget;  /* Ticket or wiki that attachment applies to.  A card */


  int nFile;            /* Number of F cards */
  int nFileAlloc;       /* Slots allocated in aFile[] */
  int iFile;            /* Index of current file in iterator */
  ManifestFile *aFile;  /* One entry for each F-card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* Hashes of parents.  One for each P card argument */







>






>
>







75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  char *zComment;       /* Decoded comment.  The C card. */
  double rDate;         /* Date and time from D card.  0.0 if no D card. */
  char *zUser;          /* Name of the user from the U card. */
  char *zRepoCksum;     /* MD5 checksum of the baseline content.  R card. */
  char *zWiki;          /* Text of the wiki page.  W card. */
  char *zWikiTitle;     /* Name of the wiki page. L card. */
  char *zMimetype;      /* Mime type of wiki or comment text.  N card.  */
  char *zThreadTitle;   /* The forum thread title. H card */
  double rEventDate;    /* Date of an event.  E card. */
  char *zEventId;       /* Artifact hash for an event.  E card. */
  char *zTicketUuid;    /* UUID for a ticket. K card. */
  char *zAttachName;    /* Filename of an attachment. A card. */
  char *zAttachSrc;     /* Artifact hash for document being attached. A card. */
  char *zAttachTarget;  /* Ticket or wiki that attachment applies to.  A card */
  char *zThreadRoot;    /* Thread root artifact.  G card */
  char *zInReplyTo;     /* Forum in-reply-to artifact.  I card */
  int nFile;            /* Number of F cards */
  int nFileAlloc;       /* Slots allocated in aFile[] */
  int iFile;            /* Index of current file in iterator */
  ManifestFile *aFile;  /* One entry for each F-card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* Hashes of parents.  One for each P card argument */
110
111
112
113
114
115
116


































117
118
119
120
121
122
123
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct {
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;            /* One for each J card */
};
#endif



































/*
** A cache of parsed manifests.  This reduces the number of
** calls to manifest_parse() when doing a rebuild.
*/
#define MX_MANIFEST_CACHE 6
static struct {







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct {
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;            /* One for each J card */
};
#endif

/*
** Allowed and required card types in each style of artifact
*/
static struct {
  const char *zAllowed;     /* Allowed cards.  Human-readable */
  const char *zRequired;    /* Required cards.  Human-readable */
} manifestCardTypes[] = {
  /*                           Allowed          Required    */
  /* CFTYPE_MANIFEST   1 */ { "BCDFNPQRTUZ",   "DZ"          },
                     /* Wants to be "CDUZ" ----^^^^
                     ** but we must limit for historical compatibility */
  /* CFTYPE_CLUSTER    2 */ { "MZ",            "MZ"          },
  /* CFTYPE_CONTROL    3 */ { "DTUZ",          "DTUZ"        },
  /* CFTYPE_WIKI       4 */ { "DLNPUWZ",       "DLUWZ"       },
  /* CFTYPE_TICKET     5 */ { "DJKUZ",         "DJKUZ"       },
  /* CFTYPE_ATTACHMENT 6 */ { "ACDNUZ",        "ADZ"         },
  /* CFTYPE_EVENT      7 */ { "CDENPTUWZ",     "DEWZ"        },
  /* CFTYPE_FORUM      8 */ { "DGHINPUWZ",     "DUWZ"        },
};

/*
** Names of manifest types
*/
static const char *azNameOfMType[] = {
  "manifest",
  "cluster",
  "tag",
  "wiki",
  "ticket",
  "attachment",
  "technote",
  "forum post"
};

/*
** A cache of parsed manifests.  This reduces the number of
** calls to manifest_parse() when doing a rebuild.
*/
#define MX_MANIFEST_CACHE 6
static struct {
145
146
147
148
149
150
151

























152
153
154
155
156
157
158
    fossil_free(p->aField);
    fossil_free(p->aCherrypick);
    if( p->pBaseline ) manifest_destroy(p->pBaseline);
    memset(p, 0, sizeof(*p));
    fossil_free(p);
  }
}


























/*
** Add an element to the manifest cache using LRU replacement.
*/
void manifest_cache_insert(Manifest *p){
  while( p ){
    int i;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    fossil_free(p->aField);
    fossil_free(p->aCherrypick);
    if( p->pBaseline ) manifest_destroy(p->pBaseline);
    memset(p, 0, sizeof(*p));
    fossil_free(p);
  }
}

/*
** Given a string of upper-case letters, compute a mask of the letters
** present.  For example,  "ABC" computes 0x0007.  "DE" gives 0x0018".
*/
static unsigned int manifest_card_mask(const char *z){
  unsigned int m = 0;
  char c;
  while( (c = *(z++))>='A' && c<='Z' ){
    m |= 1 << (c - 'A');
  }
  return m;
}

/*
** Given an integer mask representing letters A-Z, return the
** letter which is the first bit set in the mask.  Example:
** 0x03520 gives 'F' since the F-bit is the lowest.
*/
static char maskToType(unsigned int x){
  char c = 'A';
  if( x==0 ) return '?';
  while( (x&1)==0 ){ x >>= 1; c++; }
  return c;
}

/*
** Add an element to the manifest cache using LRU replacement.
*/
void manifest_cache_insert(Manifest *p){
  while( p ){
    int i;
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366


367
368



369
370
371
372
373
374
375
** Each card is divided into tokens by a single space character.
** The first token is a single upper-case letter which is the card type.
** The card type determines the other parameters to the card.
** Cards must occur in lexicographical order.
*/
Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){
  Manifest *p;
  int seenZ = 0;
  int i, lineNo=0;
  ManifestText x;
  char cPrevType = 0;
  char cType;
  char *z;
  int n;
  char *zUuid;
  int sz = 0;
  int isRepeat, hasSelfRefTag = 0;


  static Bag seen;
  const char *zErr = 0;




  if( rid==0 ){
    isRepeat = 1;
  }else if( bag_find(&seen, rid) ){
    isRepeat = 1;
  }else{
    isRepeat = 0;







<








|
>
>


>
>
>







413
414
415
416
417
418
419

420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
** Each card is divided into tokens by a single space character.
** The first token is a single upper-case letter which is the card type.
** The card type determines the other parameters to the card.
** Cards must occur in lexicographical order.
*/
Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){
  Manifest *p;

  int i, lineNo=0;
  ManifestText x;
  char cPrevType = 0;
  char cType;
  char *z;
  int n;
  char *zUuid;
  int sz = 0;
  int isRepeat;
  int nSelfTag = 0;     /* Number of T cards referring to this manifest */
  int nSimpleTag = 0;   /* Number of T cards with "+" prefix */
  static Bag seen;
  const char *zErr = 0;
  unsigned int m;
  unsigned int seenCard = 0;   /* Which card types have been seen */
  char zErrBuf[100];           /* Write error messages here */

  if( rid==0 ){
    isRepeat = 1;
  }else if( bag_find(&seen, rid) ){
    isRepeat = 1;
  }else{
    isRepeat = 0;
420
421
422
423
424
425
426


427
428
429
430
431
432
433
  /* Begin parsing, card by card.
  */
  x.z = z;
  x.zEnd = &z[n];
  x.atEol = 1;
  while( (cType = next_card(&x))!=0 && cType>=cPrevType ){
    lineNo++;


    switch( cType ){
      /*
      **     A <filename> <target> ?<source>?
      **
      ** Identifies an attachment to either a wiki page or a ticket.
      ** <source> is the artifact that is the attachment.  <source>
      ** is omitted to delete an attachment.  <target> is the name of







>
>







487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
  /* Begin parsing, card by card.
  */
  x.z = z;
  x.zEnd = &z[n];
  x.atEol = 1;
  while( (cType = next_card(&x))!=0 && cType>=cPrevType ){
    lineNo++;
    if( cType<'A' || cType>'Z' ) SYNTAX("bad card type");
    seenCard |= 1 << (cType-'A');
    switch( cType ){
      /*
      **     A <filename> <target> ?<source>?
      **
      ** Identifies an attachment to either a wiki page or a ticket.
      ** <source> is the artifact that is the attachment.  <source>
      ** is omitted to delete an attachment.  <target> is the name of
452
453
454
455
456
457
458

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473

474
475
476
477
478
479
480
        }
        if( zSrc && !hname_validate(zSrc,nSrc) ){
          SYNTAX("invalid source on A-card");
        }
        p->zAttachName = (char*)file_tail(zName);
        p->zAttachSrc = zSrc;
        p->zAttachTarget = zTarget;

        break;
      }

      /*
      **    B <uuid>
      **
      ** A B-line gives the artifact hash for the baseline of a delta-manifest.
      */
      case 'B': {
        if( p->zBaseline ) SYNTAX("more than one B-card");
        p->zBaseline = next_token(&x, &sz);
        if( p->zBaseline==0 ) SYNTAX("missing hash on B-card");
        if( !hname_validate(p->zBaseline,sz) ){
          SYNTAX("invalid hash on B-card");
        }

        break;
      }


      /*
      **     C <comment>
      **







>















>







521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
        }
        if( zSrc && !hname_validate(zSrc,nSrc) ){
          SYNTAX("invalid source on A-card");
        }
        p->zAttachName = (char*)file_tail(zName);
        p->zAttachSrc = zSrc;
        p->zAttachTarget = zTarget;
        p->type = CFTYPE_ATTACHMENT;
        break;
      }

      /*
      **    B <uuid>
      **
      ** A B-line gives the artifact hash for the baseline of a delta-manifest.
      */
      case 'B': {
        if( p->zBaseline ) SYNTAX("more than one B-card");
        p->zBaseline = next_token(&x, &sz);
        if( p->zBaseline==0 ) SYNTAX("missing hash on B-card");
        if( !hname_validate(p->zBaseline,sz) ){
          SYNTAX("invalid hash on B-card");
        }
        p->type = CFTYPE_MANIFEST;
        break;
      }


      /*
      **     C <comment>
      **
518
519
520
521
522
523
524

525
526
527
528
529
530
531
        if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
        p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
        if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
        p->zEventId = next_token(&x, &sz);
        if( !hname_validate(p->zEventId, sz) ){
          SYNTAX("malformed hash on E-card");
        }

        break;
      }

      /*
      **     F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
      **
      ** Identifies a file in a manifest.  Multiple F lines are







>







589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
        if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
        p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
        if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
        p->zEventId = next_token(&x, &sz);
        if( !hname_validate(p->zEventId, sz) ){
          SYNTAX("malformed hash on E-card");
        }
        p->type = CFTYPE_EVENT;
        break;
      }

      /*
      **     F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
      **
      ** Identifies a file in a manifest.  Multiple F lines are
563
564
565
566
567
568
569

















































570
571
572
573
574
575
576
        p->aFile[i].zName = zName;
        p->aFile[i].zUuid = zUuid;
        p->aFile[i].zPerm = zPerm;
        p->aFile[i].zPrior = zPriorName;
        if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
          SYNTAX("incorrect F-card sort order");
        }

















































        break;
      }

      /*
      **     J <name> ?<value>?
      **
      ** Specifies a name value pair for ticket.  If the first character







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
        p->aFile[i].zName = zName;
        p->aFile[i].zUuid = zUuid;
        p->aFile[i].zPerm = zPerm;
        p->aFile[i].zPrior = zPriorName;
        if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
          SYNTAX("incorrect F-card sort order");
        }
        p->type = CFTYPE_MANIFEST;
        break;
      }

      /*
      **    G <hash>
      **
      ** A G-card identifies the initial root forum post for the thread
      ** of which this post is a part.  Forum posts only.
      */
      case 'G': {
        if( p->zThreadRoot!=0 ) SYNTAX("more than one G-card");
        p->zThreadRoot = next_token(&x, &sz);
        if( p->zThreadRoot==0 ) SYNTAX("missing hash on G-card");
        if( !hname_validate(p->zThreadRoot,sz) ){
          SYNTAX("Invalid hash on G-card");
        }
        p->type = CFTYPE_FORUM;
        break;
      }

      /*
      **     H <threadtitle>
      **
      ** The title for a forum thread.
      */
      case 'H': {
        if( p->zThreadTitle!=0 ) SYNTAX("more than one H-card");
        p->zThreadTitle = next_token(&x,0);
        if( p->zThreadTitle==0 ) SYNTAX("missing title on H-card");
        defossilize(p->zThreadTitle);
        p->type = CFTYPE_FORUM;
        break;
      }

      /*
      **    I <hash>
      **
      ** A I-card identifies another forum post that the current forum post
      ** is in reply to.
      */
      case 'I': {
        if( p->zInReplyTo!=0 ) SYNTAX("more than one I-card");
        p->zInReplyTo = next_token(&x, &sz);
        if( p->zInReplyTo==0 ) SYNTAX("missing hash on I-card");
        if( !hname_validate(p->zInReplyTo,sz) ){
          SYNTAX("Invalid hash on I-card");
        }
        p->type = CFTYPE_FORUM;
        break;
      }

      /*
      **     J <name> ?<value>?
      **
      ** Specifies a name value pair for ticket.  If the first character
592
593
594
595
596
597
598

599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632

633
634
635
636
637
638
639
        }
        i = p->nField++;
        p->aField[i].zName = zName;
        p->aField[i].zValue = zValue;
        if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){
          SYNTAX("incorrect J-card sort order");
        }

        break;
      }


      /*
      **    K <uuid>
      **
      ** A K-line gives the UUID for the ticket which this control file
      ** is amending.
      */
      case 'K': {
        if( p->zTicketUuid!=0 ) SYNTAX("more than one K-card");
        p->zTicketUuid = next_token(&x, &sz);
        if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size");
        if( !validate16(p->zTicketUuid, sz) ){
          SYNTAX("invalid K-card UUID");
        }

        break;
      }

      /*
      **     L <wikititle>
      **
      ** The wiki page title is fossil-encoded.  There may be no more than
      ** one L line.
      */
      case 'L': {
        if( p->zWikiTitle!=0 ) SYNTAX("more than one L-card");
        p->zWikiTitle = next_token(&x,0);
        if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card");
        defossilize(p->zWikiTitle);
        if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){
          SYNTAX("L-card has malformed wiki name");
        }

        break;
      }

      /*
      **    M <hash>
      **
      ** An M-line identifies another artifact by its hash.  M-lines







>

















>

















>







713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
        }
        i = p->nField++;
        p->aField[i].zName = zName;
        p->aField[i].zValue = zValue;
        if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){
          SYNTAX("incorrect J-card sort order");
        }
        p->type = CFTYPE_TICKET;
        break;
      }


      /*
      **    K <uuid>
      **
      ** A K-line gives the UUID for the ticket which this control file
      ** is amending.
      */
      case 'K': {
        if( p->zTicketUuid!=0 ) SYNTAX("more than one K-card");
        p->zTicketUuid = next_token(&x, &sz);
        if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size");
        if( !validate16(p->zTicketUuid, sz) ){
          SYNTAX("invalid K-card UUID");
        }
        p->type = CFTYPE_TICKET;
        break;
      }

      /*
      **     L <wikititle>
      **
      ** The wiki page title is fossil-encoded.  There may be no more than
      ** one L line.
      */
      case 'L': {
        if( p->zWikiTitle!=0 ) SYNTAX("more than one L-card");
        p->zWikiTitle = next_token(&x,0);
        if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card");
        defossilize(p->zWikiTitle);
        if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){
          SYNTAX("L-card has malformed wiki name");
        }
        p->type = CFTYPE_WIKI;
        break;
      }

      /*
      **    M <hash>
      **
      ** An M-line identifies another artifact by its hash.  M-lines
651
652
653
654
655
656
657

658
659
660
661
662
663
664
                                 , p->nCChildAlloc*sizeof(p->azCChild[0]) );
        }
        i = p->nCChild++;
        p->azCChild[i] = zUuid;
        if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){
          SYNTAX("M-card in the wrong order");
        }

        break;
      }

      /*
      **    N <uuid>
      **
      ** An N-line identifies the mimetype of wiki or comment text.







>







775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
                                 , p->nCChildAlloc*sizeof(p->azCChild[0]) );
        }
        i = p->nCChild++;
        p->azCChild[i] = zUuid;
        if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){
          SYNTAX("M-card in the wrong order");
        }
        p->type = CFTYPE_CLUSTER;
        break;
      }

      /*
      **    N <uuid>
      **
      ** An N-line identifies the mimetype of wiki or comment text.
715
716
717
718
719
720
721

722
723
724
725
726
727
728
729
730
731
732
733
734
735

736
737
738
739
740
741
742
        p->aCherrypick = fossil_realloc(p->aCherrypick,
                                 p->nCherrypick*sizeof(p->aCherrypick[0]));
        p->aCherrypick[n].zCPTarget = zUuid;
        p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz);
        if( zUuid && !hname_validate(zUuid,sz) ){
          SYNTAX("invalid second hash on Q-card");
        }

        break;
      }

      /*
      **     R <md5sum>
      **
      ** Specify the MD5 checksum over the name and content of all files
      ** in the manifest.
      */
      case 'R': {
        if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card");
        p->zRepoCksum = next_token(&x, &sz);
        if( sz!=32 ) SYNTAX("wrong size cksum on R-card");
        if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum");

        break;
      }

      /*
      **    T (+|*|-)<tagname> <uuid> ?<value>?
      **
      ** Create or cancel a tag or property.  The tagname is fossil-encoded.







>














>







840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
        p->aCherrypick = fossil_realloc(p->aCherrypick,
                                 p->nCherrypick*sizeof(p->aCherrypick[0]));
        p->aCherrypick[n].zCPTarget = zUuid;
        p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz);
        if( zUuid && !hname_validate(zUuid,sz) ){
          SYNTAX("invalid second hash on Q-card");
        }
        p->type = CFTYPE_MANIFEST;
        break;
      }

      /*
      **     R <md5sum>
      **
      ** Specify the MD5 checksum over the name and content of all files
      ** in the manifest.
      */
      case 'R': {
        if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card");
        p->zRepoCksum = next_token(&x, &sz);
        if( sz!=32 ) SYNTAX("wrong size cksum on R-card");
        if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum");
        p->type = CFTYPE_MANIFEST;
        break;
      }

      /*
      **    T (+|*|-)<tagname> <uuid> ?<value>?
      **
      ** Create or cancel a tag or property.  The tagname is fossil-encoded.
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777

778
779
780
781
782
783
784
        if( zName==0 ) SYNTAX("missing name on T-card");
        zUuid = next_token(&x, &sz);
        if( zUuid==0 ) SYNTAX("missing artifact hash on T-card");
        zValue = next_token(&x, 0);
        if( zValue ) defossilize(zValue);
        if( hname_validate(zUuid, sz) ){
          /* A valid artifact hash */
          if( p->zEventId ) SYNTAX("non-self-referential T-card in event");
        }else if( sz==1 && zUuid[0]=='*' ){
          zUuid = 0;
          hasSelfRefTag = 1;
          if( p->zEventId && zName[0]!='+' ){
            SYNTAX("propagating T-card in event");
          }
        }else{
          SYNTAX("malformed artifact hash on T-card");
        }
        defossilize(zName);
        if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){
          SYNTAX("T-card name does not begin with '-', '+', or '*'");
        }

        if( validate16(&zName[1], strlen(&zName[1])) ){
          /* Do not allow tags whose names look like a hash */
          SYNTAX("T-card name looks like a hexadecimal hash");
        }
        if( p->nTag>=p->nTagAlloc ){
          p->nTagAlloc = p->nTagAlloc*2 + 10;
          p->aTag = fossil_realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) );







<


|
<
<
<







>







884
885
886
887
888
889
890

891
892
893



894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
        if( zName==0 ) SYNTAX("missing name on T-card");
        zUuid = next_token(&x, &sz);
        if( zUuid==0 ) SYNTAX("missing artifact hash on T-card");
        zValue = next_token(&x, 0);
        if( zValue ) defossilize(zValue);
        if( hname_validate(zUuid, sz) ){
          /* A valid artifact hash */

        }else if( sz==1 && zUuid[0]=='*' ){
          zUuid = 0;
          nSelfTag++;



        }else{
          SYNTAX("malformed artifact hash on T-card");
        }
        defossilize(zName);
        if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){
          SYNTAX("T-card name does not begin with '-', '+', or '*'");
        }
        if( zName[0]=='+' ) nSimpleTag++;
        if( validate16(&zName[1], strlen(&zName[1])) ){
          /* Do not allow tags whose names look like a hash */
          SYNTAX("T-card name looks like a hexadecimal hash");
        }
        if( p->nTag>=p->nTagAlloc ){
          p->nTagAlloc = p->nTagAlloc*2 + 10;
          p->aTag = fossil_realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) );
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873

874
875
876
877
878
879
880
881


882
883
884
885
886
887
888
889
890
891

892
893

894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909



910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925


926
927
928
929

930
931
932
933
934
935



936
937
938

939
940
941
942
943
944
945
946
947


948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
      ** Manifest.  It is not required for manifest only for historical
      ** compatibility reasons.
      */
      case 'Z': {
        zUuid = next_token(&x, &sz);
        if( sz!=32 ) SYNTAX("wrong size for Z-card cksum");
        if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum");
        seenZ = 1;
        break;
      }
      default: {
        SYNTAX("unrecognized card");
      }
    }
  }
  if( x.z<x.zEnd ) SYNTAX("extra characters at end of card");

  if( p->nCChild>0 ){

    if( p->zAttachName
     || p->zBaseline
     || p->zComment
     || p->rDate>0.0
     || p->zEventId
     || p->nFile>0
     || p->nField>0
     || p->zTicketUuid


     || p->zWikiTitle
     || p->zMimetype
     || p->nParent>0
     || p->nCherrypick>0
     || p->zRepoCksum
     || p->nTag>0
     || p->zUser
     || p->zWiki
    ){
      SYNTAX("cluster contains a card other than M- or Z-");

    }
    if( !seenZ ) SYNTAX("missing Z-card on cluster");

    p->type = CFTYPE_CLUSTER;
  }else if( p->zEventId ){
    if( p->zAttachName ) SYNTAX("A-card in event");
    if( p->zBaseline ) SYNTAX("B-card in event");
    if( p->rDate<=0.0 ) SYNTAX("missing date on event");
    if( p->nFile>0 ) SYNTAX("F-card in event");
    if( p->nField>0 ) SYNTAX("J-card in event");
    if( p->zTicketUuid ) SYNTAX("K-card in event");
    if( p->zWikiTitle!=0 ) SYNTAX("L-card in event");
    if( p->zRepoCksum ) SYNTAX("R-card in event");
    if( p->zWiki==0 ) SYNTAX("missing W-card on event");
    if( !seenZ ) SYNTAX("missing Z-card on event");
    p->type = CFTYPE_EVENT;
  }else if( p->zWiki!=0 || p->zWikiTitle!=0 ){
    if( p->zAttachName ) SYNTAX("A-card in wiki");
    if( p->zBaseline ) SYNTAX("B-card in wiki");



    if( p->rDate<=0.0 ) SYNTAX("missing date on wiki");
    if( p->nFile>0 ) SYNTAX("F-card in wiki");
    if( p->nField>0 ) SYNTAX("J-card in wiki");
    if( p->zTicketUuid ) SYNTAX("K-card in wiki");
    if( p->zWikiTitle==0 ) SYNTAX("missing L-card on wiki");
    if( p->zRepoCksum ) SYNTAX("R-card in wiki");
    if( p->nTag>0 ) SYNTAX("T-card in wiki");
    if( p->zWiki==0 ) SYNTAX("missing W-card on wiki");
    if( !seenZ ) SYNTAX("missing Z-card on wiki");
    p->type = CFTYPE_WIKI;
  }else if( hasSelfRefTag || p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline
      || p->nParent>0 ){
    if( p->zAttachName ) SYNTAX("A-card in manifest");
    if( p->rDate<=0.0 ) SYNTAX("missing date on manifest");
    if( p->nField>0 ) SYNTAX("J-card in manifest");
    if( p->zTicketUuid ) SYNTAX("K-card in manifest");


    p->type = CFTYPE_MANIFEST;
  }else if( p->nField>0 || p->zTicketUuid!=0 ){
    if( p->zAttachName ) SYNTAX("A-card in ticket");
    if( p->rDate<=0.0 ) SYNTAX("missing date on ticket");

    if( p->nField==0 ) SYNTAX("missing J-card on ticket");
    if( p->zTicketUuid==0 ) SYNTAX("missing K-card on ticket");
    if( p->zMimetype) SYNTAX("N-card in ticket");
    if( p->nTag>0 ) SYNTAX("T-card in ticket");
    if( p->zUser==0 ) SYNTAX("missing U-card on ticket");
    if( !seenZ ) SYNTAX("missing Z-card on ticket");



    p->type = CFTYPE_TICKET;
  }else if( p->zAttachName ){
    if( p->rDate<=0.0 ) SYNTAX("missing date on attachment");

    if( p->nTag>0 ) SYNTAX("T-card in attachment");
    if( !seenZ ) SYNTAX("missing Z-card on attachment");
    p->type = CFTYPE_ATTACHMENT;
  }else{
    if( p->rDate<=0.0 ) SYNTAX("missing date on control");
    if( p->zMimetype ) SYNTAX("N-card in control");
    if( !seenZ ) SYNTAX("missing Z-card on control");
    p->type = CFTYPE_CONTROL;
  }


  md5sum_init();
  if( !isRepeat ) g.parseCnt[p->type]++;
  return p;

manifest_syntax_error:
  {
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( zUuid ){
      blob_appendf(pErr, "manifest [%s] ", zUuid);
      fossil_free(zUuid);
    }
  }
  if( zErr ){
    blob_appendf(pErr, "line %d: %s", lineNo, zErr);
  }else{
    blob_appendf(pErr, "unknown error on line %d", lineNo);







<









|
>
|
<
|
|
<
<
|
|
>
>
|
|
|
|
|
|
|
|
<
<
>
|
|
>
|
|
<
<
|
|
<
<
<
<
<
<
|
<
<
<
>
>
>
|
|
<
<
<
<
|
<
<
|
<
<
|
<
<
<
>
>
|
|
|
<
>
|
<
<
|
<
<
>
>
>
|
|
|
>
|
<
<
|
<
<
<
<
|
>
>








|







980
981
982
983
984
985
986

987
988
989
990
991
992
993
994
995
996
997
998

999
1000


1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012


1013
1014
1015
1016
1017
1018


1019
1020






1021



1022
1023
1024
1025
1026




1027


1028


1029



1030
1031
1032
1033
1034

1035
1036


1037


1038
1039
1040
1041
1042
1043
1044
1045


1046




1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
      ** Manifest.  It is not required for manifest only for historical
      ** compatibility reasons.
      */
      case 'Z': {
        zUuid = next_token(&x, &sz);
        if( sz!=32 ) SYNTAX("wrong size for Z-card cksum");
        if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum");

        break;
      }
      default: {
        SYNTAX("unrecognized card");
      }
    }
  }
  if( x.z<x.zEnd ) SYNTAX("extra characters at end of card");

  /* If the artifact type has not yet been determined, then compute
  ** it now. */
  if( p->type==0 ){

    if( p->zComment!=0 || p->nFile>0 || p->nParent>0 ){
      p->type = CFTYPE_MANIFEST;


    }else{
      p->type = CFTYPE_CONTROL;
    }
  }

  /* Verify that no disallowed cards are present for this artifact type */
  m = manifest_card_mask(manifestCardTypes[p->type-1].zAllowed);
  if( seenCard & ~m ){
    sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card in %s",
                     maskToType(seenCard & ~m),
                     azNameOfMType[p->type-1]);
    zErr = zErrBuf;


    goto manifest_syntax_error;
  }

  /* Verify that all required cards are present for this artifact type */
  m = manifest_card_mask(manifestCardTypes[p->type-1].zRequired);
  if( ~seenCard & m ){


    sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card missing in %s",
                     maskToType(~seenCard & m),






                     azNameOfMType[p->type-1]);



    zErr = zErrBuf;
    goto manifest_syntax_error;
  }

  /* Additional checks based on artifact type */




  switch( p->type ){


    case CFTYPE_CONTROL: {


      if( nSelfTag ) SYNTAX("self-referential T-card in control artifact");



      break;
    }
    case CFTYPE_EVENT: {
      if( p->nTag!=nSelfTag ){
        SYNTAX("non-self-referential T-card in technote");

      }
      if( p->nTag!=nSimpleTag ){


        SYNTAX("T-card with '*' or '-' in technote");


      }
      break;
    }
    case CFTYPE_FORUM: {
      if( p->zThreadTitle && p->zInReplyTo ){
        SYNTAX("cannot have I-card and H-card in a forum post");
      }
      if( p->nParent>1 ) SYNTAX("too many arguments to P-card");


      break;




    }
  }

  md5sum_init();
  if( !isRepeat ) g.parseCnt[p->type]++;
  return p;

manifest_syntax_error:
  {
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( zUuid ){
      blob_appendf(pErr, "artifact [%s] ", zUuid);
      fossil_free(zUuid);
    }
  }
  if( zErr ){
    blob_appendf(pErr, "line %d: %s", lineNo, zErr);
  }else{
    blob_appendf(pErr, "unknown error on line %d", lineNo);
1013
1014
1015
1016
1017
1018
1019
1020

1021
1022
1023
1024
1025
1026
1027

1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043





































1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
}

/*
** COMMAND: test-parse-manifest
**
** Usage: %fossil test-parse-manifest FILENAME ?N?
**
** Parse the manifest and discarded.  Use for testing only.

*/
void manifest_test_parse_cmd(void){
  Manifest *p;
  Blob b;
  int i;
  int n = 1;
  sqlite3_open(":memory:", &g.db);

  if( g.argc!=3 && g.argc!=4 ){
    usage("FILENAME");
  }
  blob_read_from_file(&b, g.argv[2], ExtFILE);
  if( g.argc>3 ) n = atoi(g.argv[3]);
  for(i=0; i<n; i++){
    Blob b2;
    Blob err;
    blob_copy(&b2, &b);
    blob_zero(&err);
    p = manifest_parse(&b2, 0, &err);
    if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err));
    blob_reset(&err);
    manifest_destroy(p);
  }
}






































/*
** Fetch the baseline associated with the delta-manifest p.
** Return 0 on success.  If unable to parse the baseline,
** throw an error.  If the baseline is a manifest, throw an
** error if throwError is true, or record that p is an orphan
** and return 1 if throwError is false.
*/
static int fetch_baseline(Manifest *p, int throwError){
  if( p->zBaseline!=0 && p->pBaseline==0 ){
    int rid = uuid_to_rid(p->zBaseline, 1);
    p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST, 0);
    if( p->pBaseline==0 ){
      if( !throwError ){
        db_multi_exec(
           "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
           p->rid, rid
        );
        return 1;
      }
      fossil_panic("cannot access baseline manifest %S", p->zBaseline);
    }
  }
  return 0;
}

/*
** Rewind a manifest-file iterator back to the beginning of the manifest.







|
>






|
>
















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




















|







1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
}

/*
** COMMAND: test-parse-manifest
**
** Usage: %fossil test-parse-manifest FILENAME ?N?
**
** Parse the manifest(s) given on the command-line and report any
** errors.  If the N argument is given, run the parsing N times.
*/
void manifest_test_parse_cmd(void){
  Manifest *p;
  Blob b;
  int i;
  int n = 1;
  db_find_and_open_repository(0,0);
  verify_all_options();
  if( g.argc!=3 && g.argc!=4 ){
    usage("FILENAME");
  }
  blob_read_from_file(&b, g.argv[2], ExtFILE);
  if( g.argc>3 ) n = atoi(g.argv[3]);
  for(i=0; i<n; i++){
    Blob b2;
    Blob err;
    blob_copy(&b2, &b);
    blob_zero(&err);
    p = manifest_parse(&b2, 0, &err);
    if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err));
    blob_reset(&err);
    manifest_destroy(p);
  }
}

/*
** COMMAND: test-parse-all-blobs
**
** Usage: %fossil test-parse-all-blobs
**
** Parse all entries in the BLOB table that are believed to be non-data
** artifacts and report any errors.  Run this test command on historical
** repositories after making any changes to the manifest_parse()
** implementation to confirm that the changes did not break anything.
*/
void manifest_test_parse_all_blobs_cmd(void){
  Manifest *p;
  Blob err;
  Stmt q;
  int nTest = 0;
  int nErr = 0;
  db_find_and_open_repository(0, 0);
  verify_all_options();
  db_prepare(&q, "SELECT DISTINCT objid FROM EVENT");
  while( db_step(&q)==SQLITE_ROW ){
    int id = db_column_int(&q,0);
    fossil_print("Checking %d       \r", id);
    nTest++;
    fflush(stdout);
    blob_init(&err, 0, 0);
    p = manifest_get(id, CFTYPE_ANY, &err);
    if( p==0 ){
      fossil_print("%d ERROR: %s\n", id, blob_str(&err));
      nErr++;
    }
    blob_reset(&err);
    manifest_destroy(p);
  }
  db_finalize(&q);
  fossil_print("%d tests with %d errors\n", nTest, nErr);
}

/*
** Fetch the baseline associated with the delta-manifest p.
** Return 0 on success.  If unable to parse the baseline,
** throw an error.  If the baseline is a manifest, throw an
** error if throwError is true, or record that p is an orphan
** and return 1 if throwError is false.
*/
static int fetch_baseline(Manifest *p, int throwError){
  if( p->zBaseline!=0 && p->pBaseline==0 ){
    int rid = uuid_to_rid(p->zBaseline, 1);
    p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST, 0);
    if( p->pBaseline==0 ){
      if( !throwError ){
        db_multi_exec(
           "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
           p->rid, rid
        );
        return 1;
      }
      fossil_fatal("cannot access baseline manifest %S", p->zBaseline);
    }
  }
  return 0;
}

/*
** Rewind a manifest-file iterator back to the beginning of the manifest.
2376
2377
2378
2379
2380
2381
2382





























































2383
2384
2385
2386
2387
2388
2389
    if( blob_size(&comment)==0 ) blob_append(&comment, " ", 1);
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment)"
      "VALUES('g',%.17g,%d,%Q,%Q)",
      p->rDate, rid, p->zUser, blob_str(&comment)+1
    );
    blob_reset(&comment);





























































  }
  db_end_transaction(0);
  if( permitHooks ){
    rc = xfer_run_common_script();
    if( rc==TH_OK ){
      rc = xfer_run_script(zScript, zUuid, 0);
    }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
    if( blob_size(&comment)==0 ) blob_append(&comment, " ", 1);
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment)"
      "VALUES('g',%.17g,%d,%Q,%Q)",
      p->rDate, rid, p->zUser, blob_str(&comment)+1
    );
    blob_reset(&comment);
  }
  if( p->type==CFTYPE_FORUM ){
    int froot, fprev, firt;
    char *zFType;
    char *zTitle;
    schema_forum();
    froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid;
    fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0;
    firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0;
    db_multi_exec(
      "INSERT INTO forumpost(fpid,froot,fprev,firt,fmtime)"
      "VALUES(%d,%d,nullif(%d,0),nullif(%d,0),%.17g)",
      p->rid, froot, fprev, firt, p->rDate
    );
    if( firt==0 ){
      /* This is the start of a new thread, either the initial entry
      ** or an edit of the initial entry. */
      zTitle = p->zThreadTitle;
      if( zTitle==0 || zTitle[0]==0 ){
        zTitle = "(Deleted)";
      }
      zFType = fprev ? "Edit" : "Post";
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('f',%.17g,%d,%Q,'%q: %q')",
        p->rDate, rid, p->zUser, zFType, zTitle
      );
      /*
      ** If this edit is the most recent, then make it the title for
      ** all other entries for the same thread
      */
      if( !db_exists("SELECT 1 FROM forumpost WHERE froot=%d AND firt=0"
                     "   AND fpid!=%d AND fmtime>%.17g", froot, rid, p->rDate)
      ){
        /* This entry establishes a new title for all entries on the thread */
        db_multi_exec(
          "UPDATE event"
          " SET comment=substr(comment,1,instr(comment,':')) || ' %q'"
          " WHERE objid IN (SELECT fpid FROM forumpost WHERE froot=%d)",
          zTitle, froot
        );
      }
    }else{
      /* This is a reply to a prior post.  Take the title from the root. */
      zTitle = db_text(0, "SELECT substr(comment,instr(comment,':')+2)"
                          "  FROM event WHERE objid=%d", froot);
      if( zTitle==0 ) zTitle = fossil_strdup("<i>Unknown</i>");
      if( p->zWiki[0]==0 ){
        zFType = "Delete reply";
      }else if( fprev ){
        zFType = "Edit reply";
      }else{
        zFType = "Reply";
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('f',%.17g,%d,%Q,'%q: %q')",
        p->rDate, rid, p->zUser, zFType, zTitle
      );
      fossil_free(zTitle);
    }
  }
  db_end_transaction(0);
  if( permitHooks ){
    rc = xfer_run_common_script();
    if( rc==TH_OK ){
      rc = xfer_run_script(zScript, zUuid, 0);
    }

Changes to src/markdown.md.

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

> The first row is a header if followed by a horizontal rule or a blank line.

> Placing **:** at the left, both, or right sides of a cell gives left-aligned,
> centered, or right-aligned text, respectively.  By default, header cells are
> centered, and body cells are left-aligned.

> The leftmost **\|** is required if the first column contains at least one
> blank cell.  The rightmost **\|** is optional.

## Miscellaneous ##

> *   In-line images are made using **\!\[alt-text\]\(image-URL\)**.
> *   Use HTML for advanced formatting such as forms.
> *   **\<!--** HTML-style comments **-->** are supported.
> *   Escape special characters (ex: **\[** **\(** **\|** **\***)







|
|







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

> The first row is a header if followed by a horizontal rule or a blank line.

> Placing **:** at the left, both, or right sides of a cell gives left-aligned,
> centered, or right-aligned text, respectively.  By default, header cells are
> centered, and body cells are left-aligned.

> The leftmost or rightmost **\|** is required only if the first or last column,
> respectively, contains at least one blank cell.

## Miscellaneous ##

> *   In-line images are made using **\!\[alt-text\]\(image-URL\)**.
> *   Use HTML for advanced formatting such as forms.
> *   **\<!--** HTML-style comments **-->** are supported.
> *   Escape special characters (ex: **\[** **\(** **\|** **\***)

Changes to src/mkbuiltin.c.

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
}

/*
** There is an instance of the following for each file translated.
*/
typedef struct Resource Resource;
struct Resource {
  const char *zName;
  int nByte;
  int idx;
};

typedef struct ResourceList ResourceList;
struct ResourceList {
    Resource *aRes;







|







66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
}

/*
** There is an instance of the following for each file translated.
*/
typedef struct Resource Resource;
struct Resource {
  char *zName;
  int nByte;
  int idx;
};

typedef struct ResourceList ResourceList;
struct ResourceList {
    Resource *aRes;
337
338
339
340
341
342
343
344
345
346
347




348
349
350
351
352
353
354
355
356
357
  printf("struct BuiltinFileTable {\n");
  printf("  const char *zName;\n");
  printf("  const unsigned char *pData;\n");
  printf("  int nByte;\n");
  printf("};\n");
  printf("static const BuiltinFileTable aBuiltinFiles[] = {\n");
  for(i=0; i<nRes; i++){
    const char *z = aRes[i].zName;
    if( strlen(z)>=nPrefix ) z += nPrefix;
    while( z[0]=='.' || z[0]=='/' ){ z++; }
    aRes[i].zName = z;




  }
  qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource);
  for(i=0; i<nRes; i++){
    printf("  { \"%s\", bidata%d, %d },\n",
           aRes[i].zName, aRes[i].idx, aRes[i].nByte);
  }
  printf("};\n");
  free_reslist(&resList);
  return nErr;
}







|

|

>
>
>
>










337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
  printf("struct BuiltinFileTable {\n");
  printf("  const char *zName;\n");
  printf("  const unsigned char *pData;\n");
  printf("  int nByte;\n");
  printf("};\n");
  printf("static const BuiltinFileTable aBuiltinFiles[] = {\n");
  for(i=0; i<nRes; i++){
    char *z = aRes[i].zName;
    if( strlen(z)>=nPrefix ) z += nPrefix;
    while( z[0]=='.' || z[0]=='/' || z[0]=='\\' ){ z++; }
    aRes[i].zName = z;
    while( z[0] ){
      if( z[0]=='\\' ) z[0] = '/';
      z++;
    }
  }
  qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource);
  for(i=0; i<nRes; i++){
    printf("  { \"%s\", bidata%d, %d },\n",
           aRes[i].zName, aRes[i].idx, aRes[i].nByte);
  }
  printf("};\n");
  free_reslist(&resList);
  return nErr;
}

Changes to src/moderate.c.

52
53
54
55
56
57
58
























59
60
61
62
63
64
65
  if( rid==0 || !moderation_table_exists() ) return 0;
  db_static_prepare(&q, "SELECT 1 FROM modreq WHERE objid=:objid");
  db_bind_int(&q, ":objid", rid);
  rc = db_step(&q)==SQLITE_ROW;
  db_reset(&q);
  return rc;
}

























/*
** Check to see if the object identified by RID is used for anything.
*/
static int object_used(int rid){
  static const char *const aTabField[] = {
     "modreq",     "attachRid",







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  if( rid==0 || !moderation_table_exists() ) return 0;
  db_static_prepare(&q, "SELECT 1 FROM modreq WHERE objid=:objid");
  db_bind_int(&q, ":objid", rid);
  rc = db_step(&q)==SQLITE_ROW;
  db_reset(&q);
  return rc;
}

/*
** If the rid object is being held for moderation, write out
** an "awaiting moderation" message and return true.
**
** If the object is not being held for moderation, simply return
** false without generating any output.
*/
int moderation_pending_www(int rid){
  int pending = moderation_pending(rid);
  if( pending ){
    @ <span class="modpending">(Awaiting Moderator Approval)</span>
  }
  return pending;
}


/*
** Return TRUE if there any pending moderation requests.
*/
int moderation_needed(void){
  if( !moderation_table_exists() ) return 0;
  return db_exists("SELECT 1 FROM modreq");
}

/*
** Check to see if the object identified by RID is used for anything.
*/
static int object_used(int rid){
  static const char *const aTabField[] = {
     "modreq",     "attachRid",
99
100
101
102
103
104
105



106
107
108
109
110
111
112
      "DELETE FROM delta WHERE rid=%d;"
      "DELETE FROM event WHERE objid=%d;"
      "DELETE FROM tagxref WHERE rid=%d;"
      "DELETE FROM private WHERE rid=%d;"
      "DELETE FROM attachment WHERE attachid=%d;",
      rid, rid, rid, rid, rid, rid
    );



    zTktid = db_text(0, "SELECT tktid FROM modreq WHERE objid=%d", rid);
    if( zTktid && zTktid[0] ){
      ticket_rebuild_entry(zTktid);
      fossil_free(zTktid);
    }
    attachRid = db_int(0, "SELECT attachRid FROM modreq WHERE objid=%d", rid);
    if( rid==objid ){







>
>
>







123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
      "DELETE FROM delta WHERE rid=%d;"
      "DELETE FROM event WHERE objid=%d;"
      "DELETE FROM tagxref WHERE rid=%d;"
      "DELETE FROM private WHERE rid=%d;"
      "DELETE FROM attachment WHERE attachid=%d;",
      rid, rid, rid, rid, rid, rid
    );
    if( db_table_exists("repository","forumpost") ){
      db_multi_exec("DELETE FROM forumpost WHERE fpid=%d", rid);
    }
    zTktid = db_text(0, "SELECT tktid FROM modreq WHERE objid=%d", rid);
    if( zTktid && zTktid[0] ){
      ticket_rebuild_entry(zTktid);
      fossil_free(zTktid);
    }
    attachRid = db_int(0, "SELECT attachRid FROM modreq WHERE objid=%d", rid);
    if( rid==objid ){
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
** Show all pending moderation request
*/
void modreq_page(void){
  Blob sql;
  Stmt q;

  login_check_credentials();
  if( !g.perm.ModWiki && !g.perm.ModTkt ){
    login_needed(g.anon.ModWiki && g.anon.ModTkt);
    return;
  }
  style_header("Pending Moderation Requests");
  @ <h2>All Pending Moderation Requests</h2>
  if( moderation_table_exists() ){
    blob_init(&sql, timeline_query_for_www(), -1);
    blob_append_sql(&sql,







|
|







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
** Show all pending moderation request
*/
void modreq_page(void){
  Blob sql;
  Stmt q;

  login_check_credentials();
  if( !g.perm.ModWiki && !g.perm.ModTkt && !g.perm.ModForum ){
    login_needed(g.anon.ModWiki && g.anon.ModTkt && g.anon.ModForum);
    return;
  }
  style_header("Pending Moderation Requests");
  @ <h2>All Pending Moderation Requests</h2>
  if( moderation_table_exists() ){
    blob_init(&sql, timeline_query_for_www(), -1);
    blob_append_sql(&sql,

Changes to src/name.c.

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
**   *  "current"
**   *  "prev" or "previous"
**   *  "next"
**
** Return the RID of the matching artifact.  Or return 0 if the name does not
** match any known object.  Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** If zType is "br" then find the first check-in of the named branch
** rather than the last.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
**
** Note that the input zTag for types "t" and "e" is the artifact hash of
** the ticket-change or event-change artifact, not the randomly generated
** hexadecimal identifier assigned to tickets and events.  Those identifiers
** live in a separate namespace.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
  int vid;
  int rid = 0;
  int nTag;







|







|







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
**   *  "current"
**   *  "prev" or "previous"
**   *  "next"
**
** Return the RID of the matching artifact.  Or return 0 if the name does not
** match any known object.  Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g, f.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** If zType is "br" then find the first check-in of the named branch
** rather than the last.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
**
** Note that the input zTag for types "t" and "e" is the artifact hash of
** the ticket-change or technote-change artifact, not the randomly generated
** hexadecimal identifier assigned to tickets and events.  Those identifiers
** live in a separate namespace.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
  int vid;
  int rid = 0;
  int nTag;
601
602
603
604
605
606
607
608

609
610
611
612
613
614
615
     "       coalesce(euser,user), coalesce(ecomment,comment)"
     "  FROM event WHERE objid=%d", rid);
  if( db_step(&q)==SQLITE_ROW ){
    const char *zType;
    switch( db_column_text(&q,0)[0] ){
      case 'c':  zType = "Check-in";       break;
      case 'w':  zType = "Wiki-edit";      break;
      case 'e':  zType = "Event";          break;

      case 't':  zType = "Ticket-change";  break;
      case 'g':  zType = "Tag-change";     break;
      default:   zType = "Unknown";        break;
    }
    fossil_print("type:       %s by %s on %s\n", zType, db_column_text(&q,2),
                 db_column_text(&q, 1));
    fossil_print("comment:    ");







|
>







601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
     "       coalesce(euser,user), coalesce(ecomment,comment)"
     "  FROM event WHERE objid=%d", rid);
  if( db_step(&q)==SQLITE_ROW ){
    const char *zType;
    switch( db_column_text(&q,0)[0] ){
      case 'c':  zType = "Check-in";       break;
      case 'w':  zType = "Wiki-edit";      break;
      case 'e':  zType = "Technote";       break;
      case 'f':  zType = "Forum-post";     break;
      case 't':  zType = "Ticket-change";  break;
      case 'g':  zType = "Tag-change";     break;
      default:   zType = "Unknown";        break;
    }
    fossil_print("type:       %s by %s on %s\n", zType, db_column_text(&q,2),
                 db_column_text(&q, 1));
    fossil_print("comment:    ");
924
925
926
927
928
929
930















931
932
933
934
935
936
937
    "       'attachment '||attachment.filename\n"
    "  FROM attachment, blob\n"
    " WHERE (blob.rid %s)\n"
    "   AND blob.rid NOT IN (SELECT rid FROM description)\n"
    "   AND blob.uuid=attachment.src",
    zWhere /*safe-for-%s*/
  );
















  /* Everything else */
  db_multi_exec(
    "INSERT OR IGNORE INTO description(rid,uuid,type,summary)\n"
    "SELECT blob.rid, blob.uuid,"
    "       CASE WHEN blob.size<0 THEN 'phantom' ELSE '' END,\n"
    "       'unknown'\n"







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
    "       'attachment '||attachment.filename\n"
    "  FROM attachment, blob\n"
    " WHERE (blob.rid %s)\n"
    "   AND blob.rid NOT IN (SELECT rid FROM description)\n"
    "   AND blob.uuid=attachment.src",
    zWhere /*safe-for-%s*/
  );

  /* Forum posts */
  if( db_table_exists("repository","forumpost") ){
    db_multi_exec(
      "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n"
      "SELECT postblob.rid, postblob.uuid, forumpost.fmtime, 'forumpost',\n"
      "       CASE WHEN fpid=froot THEN 'forum-post '\n"
      "            ELSE 'forum-reply-to ' END || substr(rootblob.uuid,1,14)\n"
      "  FROM forumpost, blob AS postblob, blob AS rootblob\n"
      " WHERE (forumpost.fpid %s)\n"
      "   AND postblob.rid=forumpost.fpid"
      "   AND rootblob.rid=forumpost.froot",
      zWhere /*safe-for-%s*/
    );
  }

  /* Everything else */
  db_multi_exec(
    "INSERT OR IGNORE INTO description(rid,uuid,type,summary)\n"
    "SELECT blob.rid, blob.uuid,"
    "       CASE WHEN blob.size<0 THEN 'phantom' ELSE '' END,\n"
    "       'unknown'\n"

Changes to src/popen.c.

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
/*
** Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  fossil_panic("%s", zMsg);
}
#else
#include <signal.h>
#include <sys/wait.h>
#endif

/*







|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
/*
** Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  fossil_fatal("%s", zMsg);
}
#else
#include <signal.h>
#include <sys/wait.h>
#endif

/*

Changes to src/printf.c.

1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089



1090
1091

1092
1093
1094
1095
1096
1097
1098
  int rc = 1;
  char z[1000];
  static int once = 0;

  if( once ) exit(1);
  once = 1;
  mainInFatalError = 1;
  db_force_rollback();
  va_start(ap, zFormat);
  sqlite3_vsnprintf(sizeof(z),z,zFormat, ap);
  va_end(ap);



  fossil_errorlog("panic: %s", z);
  rc = fossil_print_error(rc, z);

  exit(rc);
}
NORETURN void fossil_fatal(const char *zFormat, ...){
  char *z;
  int rc = 1;
  va_list ap;
  mainInFatalError = 1;







|



>
>
>


>







1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
  int rc = 1;
  char z[1000];
  static int once = 0;

  if( once ) exit(1);
  once = 1;
  mainInFatalError = 1;
  /* db_force_rollback(); */
  va_start(ap, zFormat);
  sqlite3_vsnprintf(sizeof(z),z,zFormat, ap);
  va_end(ap);
  if( g.fAnyTrace ){
    fprintf(stderr, "/***** panic on %d *****/\n", getpid());
  }
  fossil_errorlog("panic: %s", z);
  rc = fossil_print_error(rc, z);
  abort();
  exit(rc);
}
NORETURN void fossil_fatal(const char *zFormat, ...){
  char *z;
  int rc = 1;
  va_list ap;
  mainInFatalError = 1;

Changes to src/rebuild.c.

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380

  bag_init(&bagDone);
  ttyOutput = doOut;
  processCnt = 0;
  if (ttyOutput && !g.fQuiet) {
    percent_complete(0);
  }
  email_triggers_disable();
  rebuild_update_schema();
  blob_init(&sql, 0, 0);
  db_prepare(&q,
     "SELECT name FROM sqlite_master /*scan*/"
     " WHERE type='table'"
     " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
                       "'config','shun','private','reportfmt',"
                       "'concealed','accesslog','modreq',"
                       "'purgeevent','purgeitem','unversioned',"
                       "'subscriber','pending_alert','email_bounce')"
     " AND name NOT GLOB 'sqlite_*'"
     " AND name NOT GLOB 'fx_*'"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
  }
  db_finalize(&q);







|









|







356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380

  bag_init(&bagDone);
  ttyOutput = doOut;
  processCnt = 0;
  if (ttyOutput && !g.fQuiet) {
    percent_complete(0);
  }
  alert_triggers_disable();
  rebuild_update_schema();
  blob_init(&sql, 0, 0);
  db_prepare(&q,
     "SELECT name FROM sqlite_master /*scan*/"
     " WHERE type='table'"
     " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
                       "'config','shun','private','reportfmt',"
                       "'concealed','accesslog','modreq',"
                       "'purgeevent','purgeitem','unversioned',"
                       "'subscriber','pending_alert','alert_bounce')"
     " AND name NOT GLOB 'sqlite_*'"
     " AND name NOT GLOB 'fx_*'"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
  }
  db_finalize(&q);
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    percent_complete((processCnt*1000)/totalSize);
  }
  if( doClustering ) create_cluster();
  if( ttyOutput && !g.fQuiet && totalSize>0 ){
    processCnt += incrSize;
    percent_complete((processCnt*1000)/totalSize);
  }
  email_triggers_enable();
  if(!g.fQuiet && ttyOutput ){
    percent_complete(1000);
    fossil_print("\n");
  }
  return errCnt;
}








|







447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    percent_complete((processCnt*1000)/totalSize);
  }
  if( doClustering ) create_cluster();
  if( ttyOutput && !g.fQuiet && totalSize>0 ){
    processCnt += incrSize;
    percent_complete((processCnt*1000)/totalSize);
  }
  alert_triggers_enable();
  if(!g.fQuiet && ttyOutput ){
    percent_complete(1000);
    fossil_print("\n");
  }
  return errCnt;
}

Changes to src/schema.c.

288
289
290
291
292
293
294
295






296
297
298
299
300
301
302
303
304
305
@ -- between the plink and tagxref tables, but it is a slower join for
@ -- very large repositories (repositories with 100,000 or more check-ins)
@ -- and so it makes sense to precompute the set of leaves.  There is
@ -- one entry in the following table for each leaf.
@ --
@ CREATE TABLE leaf(rid INTEGER PRIMARY KEY);
@
@ -- Events used to generate a timeline






@ --
@ CREATE TABLE event(
@   type TEXT,                      -- Type of event: 'ci', 'w', 'e', 't', 'g'
@   mtime DATETIME,                 -- Time of occurrence. Julian day.
@   objid INTEGER PRIMARY KEY,      -- Associated record ID
@   tagid INTEGER,                  -- Associated ticket or wiki name tag
@   uid INTEGER REFERENCES user,    -- User who caused the event
@   bgcolor TEXT,                   -- Color set by 'bgcolor' property
@   euser TEXT,                     -- User set by 'user' property
@   user TEXT,                      -- Name of the user







|
>
>
>
>
>
>


|







288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
@ -- between the plink and tagxref tables, but it is a slower join for
@ -- very large repositories (repositories with 100,000 or more check-ins)
@ -- and so it makes sense to precompute the set of leaves.  There is
@ -- one entry in the following table for each leaf.
@ --
@ CREATE TABLE leaf(rid INTEGER PRIMARY KEY);
@
@ -- Events used to generate a timeline.  Type meanings:
@ --     ci    Check-ins
@ --     e     Technotes
@ --     f     Forum posts
@ --     g     Tags
@ --     t     Ticket changes
@ --     w     Wiki page edit
@ --
@ CREATE TABLE event(
@   type TEXT,                      -- Type of event: ci, e, f, g, t, w
@   mtime DATETIME,                 -- Time of occurrence. Julian day.
@   objid INTEGER PRIMARY KEY,      -- Associated record ID
@   tagid INTEGER,                  -- Associated ticket or wiki name tag
@   uid INTEGER REFERENCES user,    -- User who caused the event
@   bgcolor TEXT,                   -- Color set by 'bgcolor' property
@   euser TEXT,                     -- User set by 'user' property
@   user TEXT,                      -- Name of the user
540
541
542
543
544
545
546























@   UNIQUE(id, merge)
@ );
@
@ -- Identifier for this file type.
@ -- The integer is the same as 'FSLC'.
@ PRAGMA application_id=252006674;
;






























>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
@   UNIQUE(id, merge)
@ );
@
@ -- Identifier for this file type.
@ -- The integer is the same as 'FSLC'.
@ PRAGMA application_id=252006674;
;

/*
** The following table holds information about forum posts.  It
** is created on-demand whenever the manifest parser encounters
** a forum-post artifact.
*/
static const char zForumSchema[] =
@ CREATE TABLE repository.forumpost(
@   fpid INTEGER PRIMARY KEY,  -- BLOB.rid for the artifact
@   froot INT,                 -- fpid of the thread root
@   fprev INT,                 -- Previous version of this same post
@   firt INT,                  -- This post is in-reply-to
@   fmtime REAL                -- When posted.  Julian day
@ );
@ CREATE INDEX repository.forumthread ON forumpost(froot,fmtime);
;

/* Create the forum-post schema if it does not already exist */
void schema_forum(void){
  if( !db_table_exists("repository","forumpost") ){
    db_multi_exec("%s",zForumSchema/*safe-for-%s*/);
  }
}

Changes to src/search.c.

636
637
638
639
640
641
642

643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658

659
660
661
662
663

664
665
666
667
668
669
670
#if INTERFACE
/* What to search for */
#define SRCH_CKIN     0x0001    /* Search over check-in comments */
#define SRCH_DOC      0x0002    /* Search over embedded documents */
#define SRCH_TKT      0x0004    /* Search over tickets */
#define SRCH_WIKI     0x0008    /* Search over wiki */
#define SRCH_TECHNOTE 0x0010    /* Search over tech notes */

#define SRCH_ALL      0x001f    /* Search over everything */
#endif

/*
** Remove bits from srchFlags which are disallowed by either the
** current server configuration or by user permissions.
*/
unsigned int search_restrict(unsigned int srchFlags){
  static unsigned int knownGood = 0;
  static unsigned int knownBad = 0;
  static const struct { unsigned m; const char *zKey; } aSetng[] = {
     { SRCH_CKIN,     "search-ci"   },
     { SRCH_DOC,      "search-doc"  },
     { SRCH_TKT,      "search-tkt"  },
     { SRCH_WIKI,     "search-wiki" },
     { SRCH_TECHNOTE, "search-technote" },

  };
  int i;
  if( g.perm.Read==0 )   srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_TECHNOTE);
  if( g.perm.RdTkt==0 )  srchFlags &= ~(SRCH_TKT);
  if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);

  for(i=0; i<count(aSetng); i++){
    unsigned int m = aSetng[i].m;
    if( (srchFlags & m)==0 ) continue;
    if( ((knownGood|knownBad) & m)!=0 ) continue;
    if( db_get_boolean(aSetng[i].zKey,0) ){
      knownGood |= m;
    }else{







>
|















>





>







636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
#if INTERFACE
/* What to search for */
#define SRCH_CKIN     0x0001    /* Search over check-in comments */
#define SRCH_DOC      0x0002    /* Search over embedded documents */
#define SRCH_TKT      0x0004    /* Search over tickets */
#define SRCH_WIKI     0x0008    /* Search over wiki */
#define SRCH_TECHNOTE 0x0010    /* Search over tech notes */
#define SRCH_FORUM    0x0020    /* Search over forum messages */
#define SRCH_ALL      0x003f    /* Search over everything */
#endif

/*
** Remove bits from srchFlags which are disallowed by either the
** current server configuration or by user permissions.
*/
unsigned int search_restrict(unsigned int srchFlags){
  static unsigned int knownGood = 0;
  static unsigned int knownBad = 0;
  static const struct { unsigned m; const char *zKey; } aSetng[] = {
     { SRCH_CKIN,     "search-ci"   },
     { SRCH_DOC,      "search-doc"  },
     { SRCH_TKT,      "search-tkt"  },
     { SRCH_WIKI,     "search-wiki" },
     { SRCH_TECHNOTE, "search-technote" },
     { SRCH_FORUM,    "search-forum" },
  };
  int i;
  if( g.perm.Read==0 )   srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_TECHNOTE);
  if( g.perm.RdTkt==0 )  srchFlags &= ~(SRCH_TKT);
  if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
  if( g.perm.RdForum==0) srchFlags &= ~(SRCH_FORUM);
  for(i=0; i<count(aSetng); i++){
    unsigned int m = aSetng[i].m;
    if( (srchFlags & m)==0 ) continue;
    if( ((knownGood|knownBad) & m)!=0 ) continue;
    if( db_get_boolean(aSetng[i].zKey,0) ){
      knownGood |= m;
    }else{
791
792
793
794
795
796
797













798
799
800
801
802
803
804
      "         'e'||rid,"
      "         datetime(mtime),"
      "         search_snippet()"
      "    FROM technote"
      "   WHERE search_match('',body('e',rid,NULL));"
    );
  }













}

/*
** Number of significant bits in a u32
*/
static int nbits(u32 x){
  int n = 0;







>
>
>
>
>
>
>
>
>
>
>
>
>







794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
      "         'e'||rid,"
      "         datetime(mtime),"
      "         search_snippet()"
      "    FROM technote"
      "   WHERE search_match('',body('e',rid,NULL));"
    );
  }
  if( (srchFlags & SRCH_FORUM)!=0 ){
    db_multi_exec(
      "INSERT INTO x(label,url,score,id,date,snip)"
      "  SELECT 'Forum '||comment,"
      "         '/forumpost/'||uuid,"
      "         search_score(),"
      "         'f'||rid,"
      "         datetime(event.mtime),"
      "         search_snippet()"
      "    FROM event JOIN blob on event.objid=blob.rid"
      "   WHERE search_match('',body('f',rid,NULL));"
    );
  }
}

/*
** Number of significant bits in a u32
*/
static int nbits(u32 x){
  int n = 0;
911
912
913
914
915
916
917

918
919
920
921
922
923
924
    const char *zSep = " AND (";
    static const struct { unsigned m; char c; } aMask[] = {
       { SRCH_CKIN,     'c' },
       { SRCH_DOC,      'd' },
       { SRCH_TKT,      't' },
       { SRCH_WIKI,     'w' },
       { SRCH_TECHNOTE, 'e' },

    };
    int i;
    for(i=0; i<count(aMask); i++){
      if( srchFlags & aMask[i].m ){
        blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c);
        zSep = " OR ";
      }







>







927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
    const char *zSep = " AND (";
    static const struct { unsigned m; char c; } aMask[] = {
       { SRCH_CKIN,     'c' },
       { SRCH_DOC,      'd' },
       { SRCH_TKT,      't' },
       { SRCH_WIKI,     'w' },
       { SRCH_TECHNOTE, 'e' },
       { SRCH_FORUM,    'f' },
    };
    int i;
    for(i=0; i<count(aMask); i++){
      if( srchFlags & aMask[i].m ){
        blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c);
        zSep = " OR ";
      }
1047
1048
1049
1050
1051
1052
1053


1054
1055




1056
1057
1058
1059
1060
1061
1062
1063

1064
1065
1066
1067
1068
1069
1070

1071
1072

1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095

1096
1097
1098
1099
1100
1101
1102
** categories.  Any srchFlags with two or more bits set
** is treated like SRCH_ALL for display purposes.
**
** This routine automatically restricts srchFlag according to user
** permissions and the server configuration.  The entry box is shown
** disabled if srchFlags is 0 after these restrictions are applied.
**


** If useYparam is true, then this routine also looks at the y= query
** parameter for further search restrictions.




*/
void search_screen(unsigned srchFlags, int useYparam){
  const char *zType = 0;
  const char *zClass = 0;
  const char *zDisable1;
  const char *zDisable2;
  const char *zPattern;
  int fDebug = PB("debug");

  srchFlags = search_restrict(srchFlags);
  switch( srchFlags ){
    case SRCH_CKIN:     zType = " Check-ins";  zClass = "Ckin"; break;
    case SRCH_DOC:      zType = " Docs";       zClass = "Doc";  break;
    case SRCH_TKT:      zType = " Tickets";    zClass = "Tkt";  break;
    case SRCH_WIKI:     zType = " Wiki";       zClass = "Wiki"; break;
    case SRCH_TECHNOTE: zType = " Tech Notes"; zClass = "Note"; break;

  }
  if( srchFlags==0 ){

    zDisable1 = " disabled";
    zDisable2 = " disabled";
    zPattern = "";
  }else{
    zDisable1 = ""; /* Was: " autofocus" */
    zDisable2 = "";
    zPattern = PD("s","");
  }
  @ <form method='GET' action='%R/%T(g.zPath)'>
  if( zClass ){
    @ <div class='searchForm searchForm%s(zClass)'>
  }else{
    @ <div class='searchForm'>
  }
  @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
  if( useYparam && (srchFlags & (srchFlags-1))!=0 && useYparam ){
    static const struct { char *z; char *zNm; unsigned m; } aY[] = {
       { "all",  "All",        SRCH_ALL      },
       { "c",    "Check-ins",  SRCH_CKIN     },
       { "d",    "Docs",       SRCH_DOC      },
       { "t",    "Tickets",    SRCH_TKT      },
       { "w",    "Wiki",       SRCH_WIKI     },
       { "e",    "Tech Notes", SRCH_TECHNOTE },

    };
    const char *zY = PD("y","all");
    unsigned newFlags = srchFlags;
    int i;
    @ <select size='1' name='y'>
    for(i=0; i<count(aY); i++){
      if( (aY[i].m & srchFlags)==0 ) continue;







>
>
|
|
>
>
>
>

|






>







>


>















|







>







1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
** categories.  Any srchFlags with two or more bits set
** is treated like SRCH_ALL for display purposes.
**
** This routine automatically restricts srchFlag according to user
** permissions and the server configuration.  The entry box is shown
** disabled if srchFlags is 0 after these restrictions are applied.
**
** The mFlags value controls options:
**
**     0x01    If the y= query parameter is present, use it as an addition
**             restriction what to search.
**
**     0x02    Show nothing if search is disabled.
**
** Return true if there are search results.
*/
int search_screen(unsigned srchFlags, int mFlags){
  const char *zType = 0;
  const char *zClass = 0;
  const char *zDisable1;
  const char *zDisable2;
  const char *zPattern;
  int fDebug = PB("debug");
  int haveResult = 0;
  srchFlags = search_restrict(srchFlags);
  switch( srchFlags ){
    case SRCH_CKIN:     zType = " Check-ins";  zClass = "Ckin"; break;
    case SRCH_DOC:      zType = " Docs";       zClass = "Doc";  break;
    case SRCH_TKT:      zType = " Tickets";    zClass = "Tkt";  break;
    case SRCH_WIKI:     zType = " Wiki";       zClass = "Wiki"; break;
    case SRCH_TECHNOTE: zType = " Tech Notes"; zClass = "Note"; break;
    case SRCH_FORUM:    zType = " Forum";      zClass = "Frm";  break;
  }
  if( srchFlags==0 ){
    if( mFlags & 0x02 ) return 0;
    zDisable1 = " disabled";
    zDisable2 = " disabled";
    zPattern = "";
  }else{
    zDisable1 = ""; /* Was: " autofocus" */
    zDisable2 = "";
    zPattern = PD("s","");
  }
  @ <form method='GET' action='%R/%T(g.zPath)'>
  if( zClass ){
    @ <div class='searchForm searchForm%s(zClass)'>
  }else{
    @ <div class='searchForm'>
  }
  @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
  if( (mFlags & 0x01)!=0 && (srchFlags & (srchFlags-1))!=0 ){
    static const struct { char *z; char *zNm; unsigned m; } aY[] = {
       { "all",  "All",        SRCH_ALL      },
       { "c",    "Check-ins",  SRCH_CKIN     },
       { "d",    "Docs",       SRCH_DOC      },
       { "t",    "Tickets",    SRCH_TKT      },
       { "w",    "Wiki",       SRCH_WIKI     },
       { "e",    "Tech Notes", SRCH_TECHNOTE },
       { "f",    "Forum",      SRCH_FORUM    },
    };
    const char *zY = PD("y","all");
    unsigned newFlags = srchFlags;
    int i;
    @ <select size='1' name='y'>
    for(i=0; i<count(aY); i++){
      if( (aY[i].m & srchFlags)==0 ) continue;
1125
1126
1127
1128
1129
1130
1131

1132

1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147

1148
1149
1150
1151
1152
1153
1154
    }else{
      @ <div class='searchResult'>
    }
    if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
      @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
    }
    @ </div>

  }

}

/*
** WEBPAGE: search
**
** Search for check-in comments, documents, tickets, or wiki that
** match a user-supplied pattern.
**
**    s=PATTERN       Specify the full-text pattern to search for
**    y=TYPE          What to search.
**                      c -> check-ins
**                      d -> documentation
**                      t -> tickets
**                      w -> wiki
**                      e -> tech notes

**                    all -> everything
*/
void search_page(void){
  login_check_credentials();
  style_header("Search");
  search_screen(SRCH_ALL, 1);
  style_footer();







>

>















>







1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
    }else{
      @ <div class='searchResult'>
    }
    if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
      @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
    }
    @ </div>
    haveResult = 1;
  }
  return haveResult;
}

/*
** WEBPAGE: search
**
** Search for check-in comments, documents, tickets, or wiki that
** match a user-supplied pattern.
**
**    s=PATTERN       Specify the full-text pattern to search for
**    y=TYPE          What to search.
**                      c -> check-ins
**                      d -> documentation
**                      t -> tickets
**                      w -> wiki
**                      e -> tech notes
**                      f -> forum
**                    all -> everything
*/
void search_page(void){
  login_check_credentials();
  style_header("Search");
  search_screen(SRCH_ALL, 1);
  style_footer();
1248
1249
1250
1251
1252
1253
1254

1255
1256
1257
1258
1259
1260
1261
** full text search and/or for constructing a search result snippet.
**
**    cType:            d      Embedded documentation
**                      w      Wiki page
**                      c      Check-in comment
**                      t      Ticket text
**                      e      Tech note

**
**    rid               The RID of an artifact that defines the object
**                      being searched.
**
**    zName             Name of the object being searched.  This is used
**                      only to help figure out the mimetype (text/plain,
**                      test/html, test/x-fossil-wiki, or text/x-markdown)







>







1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
** full text search and/or for constructing a search result snippet.
**
**    cType:            d      Embedded documentation
**                      w      Wiki page
**                      c      Check-in comment
**                      t      Ticket text
**                      e      Tech note
**                      f      Forum
**
**    rid               The RID of an artifact that defines the object
**                      being searched.
**
**    zName             Name of the object being searched.  This is used
**                      only to help figure out the mimetype (text/plain,
**                      test/html, test/x-fossil-wiki, or text/x-markdown)
1273
1274
1275
1276
1277
1278
1279

1280
1281
1282
1283

1284
1285







1286

1287
1288
1289
1290
1291
1292
1293
      Blob doc;
      content_get(rid, &doc);
      blob_to_utf8_no_bom(&doc, 0);
      get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
      blob_reset(&doc);
      break;
    }

    case 'e':     /* Tech Notes */
    case 'w': {   /* Wiki */
      Manifest *pWiki = manifest_get(rid,
          cType == 'e' ? CFTYPE_EVENT : CFTYPE_WIKI, 0);

      Blob wiki;
      if( pWiki==0 ) break;







      blob_init(&wiki, pWiki->zWiki, -1);

      get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype),
                            pOut);
      blob_reset(&wiki);
      manifest_destroy(pWiki);
      break;
    }
    case 'c': {   /* Check-in Comments */







>



|
>


>
>
>
>
>
>
>
|
>







1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
      Blob doc;
      content_get(rid, &doc);
      blob_to_utf8_no_bom(&doc, 0);
      get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
      blob_reset(&doc);
      break;
    }
    case 'f':     /* Forum messages */
    case 'e':     /* Tech Notes */
    case 'w': {   /* Wiki */
      Manifest *pWiki = manifest_get(rid,
          cType == 'e' ? CFTYPE_EVENT :
          cType == 'f' ? CFTYPE_FORUM : CFTYPE_WIKI, 0);
      Blob wiki;
      if( pWiki==0 ) break;
      if( cType=='f' ){
        blob_init(&wiki, 0, 0);
        if( pWiki->zThreadTitle ){
          blob_appendf(&wiki, "<h1>%h</h1>\n", pWiki->zThreadTitle);
        }
        blob_appendf(&wiki, "From %s:\n\n%s", pWiki->zUser, pWiki->zWiki);
      }else{
        blob_init(&wiki, pWiki->zWiki, -1);
      }
      get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype),
                            pOut);
      blob_reset(&wiki);
      manifest_destroy(pWiki);
      break;
    }
    case 'c': {   /* Check-in Comments */
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
  );
  db_multi_exec(
    "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)"
    "  SELECT 't', tkt_id, 0 FROM ticket;"
  );
  db_multi_exec(
    "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)"
    "  SELECT 'e', objid, comment, 0 FROM event WHERE type='e';"
  );
}

/*
** The document described by cType,rid,zName is about to be added or
** updated.  If the document has already been indexed, then unindex it
** now while we still have access to the old content.  Add the document







|







1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
  );
  db_multi_exec(
    "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)"
    "  SELECT 't', tkt_id, 0 FROM ticket;"
  );
  db_multi_exec(
    "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)"
    "  SELECT type, objid, comment, 0 FROM event WHERE type IN ('e','f');"
  );
}

/*
** The document described by cType,rid,zName is about to be added or
** updated.  If the document has already been indexed, then unindex it
** now while we still have access to the old content.  Add the document
1551
1552
1553
1554
1555
1556
1557

1558
1559
1560
1561
1562
1563
1564
        cType, zName
      );
      db_multi_exec(
        "DELETE FROM ftsdocs WHERE type='%c' AND name=%Q AND rid!=%d",
        cType, zName, rid
      );
    }

  }
}

/*
** If the doc-glob and doc-br settings are valid for document search
** and if the latest check-in on doc-br is in the unindexed set of
** check-ins, then update all 'd' entries in FTSDOCS that have







>







1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
        cType, zName
      );
      db_multi_exec(
        "DELETE FROM ftsdocs WHERE type='%c' AND name=%Q AND rid!=%d",
        cType, zName, rid
      );
    }
    /* All forum posts are always indexed */
  }
}

/*
** If the doc-glob and doc-br settings are valid for document search
** and if the latest check-in on doc-br is in the unindexed set of
** check-ins, then update all 'd' entries in FTSDOCS that have
1679
1680
1681
1682
1683
1684
1685























1686
1687
1688
1689
1690
1691
1692
    "            'Wiki: '||ftsdocs.name,"
    "            '/wiki?name='||urlencode(ftsdocs.name),"
    "            tagxref.mtime"
    "       FROM tagxref WHERE tagxref.rid=ftsdocs.rid)"
    " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed"
  );
}
























/*
** Deal with all of the unindexed 'e' terms in FTSDOCS
*/
static void search_update_technote_index(void){
  db_multi_exec(
    "INSERT INTO ftsidx(docid,title,body)"







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
    "            'Wiki: '||ftsdocs.name,"
    "            '/wiki?name='||urlencode(ftsdocs.name),"
    "            tagxref.mtime"
    "       FROM tagxref WHERE tagxref.rid=ftsdocs.rid)"
    " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed"
  );
}

/*
** Deal with all of the unindexed 'f' terms in FTSDOCS
*/
static void search_update_forum_index(void){
  db_multi_exec(
    "INSERT INTO ftsidx(docid,title,body)"
    " SELECT rowid, title('f',rid,NULL),body('f',rid,NULL) FROM ftsdocs"
    "  WHERE type='f' AND NOT idxed;"
  );
  if( db_changes()==0 ) return;
  db_multi_exec(
    "UPDATE ftsdocs SET idxed=1, name=NULL,"
    " (label,url,mtime) = "
    "  (SELECT 'Forum '||event.comment,"
    "          '/forumpost/'||blob.uuid,"
    "          event.mtime"
    "     FROM event, blob"
    "    WHERE event.objid=ftsdocs.rid"
    "      AND blob.rid=ftsdocs.rid)"
    "WHERE ftsdocs.type='f' AND NOT ftsdocs.idxed"
  );
}

/*
** Deal with all of the unindexed 'e' terms in FTSDOCS
*/
static void search_update_technote_index(void){
  db_multi_exec(
    "INSERT INTO ftsidx(docid,title,body)"
1726
1727
1728
1729
1730
1731
1732



1733
1734
1735
1736
1737
1738
1739
  }
  if( srchFlags & SRCH_WIKI ){
    search_update_wiki_index();
  }
  if( srchFlags & SRCH_TECHNOTE ){
    search_update_technote_index();
  }



}

/*
** Construct, prepopulate, and then update the full-text index.
*/
void search_rebuild_index(void){
  fossil_print("rebuilding the search index...");







>
>
>







1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
  }
  if( srchFlags & SRCH_WIKI ){
    search_update_wiki_index();
  }
  if( srchFlags & SRCH_TECHNOTE ){
    search_update_technote_index();
  }
  if( srchFlags & SRCH_FORUM ){
    search_update_forum_index();
  }
}

/*
** Construct, prepopulate, and then update the full-text index.
*/
void search_rebuild_index(void){
  fossil_print("rebuilding the search index...");
1778
1779
1780
1781
1782
1783
1784

1785
1786
1787
1788
1789
1790
1791
  };
  static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
     { "search-ci",       "check-in search:",  "c" },
     { "search-doc",      "document search:",  "d" },
     { "search-tkt",      "ticket search:",    "t" },
     { "search-wiki",     "wiki search:",      "w" },
     { "search-technote", "tech note search:", "e" },

  };
  char *zSubCmd = 0;
  int i, j, n;
  int iCmd = 0;
  int iAction = 0;
  db_find_and_open_repository(0, 0);
  if( g.argc>2 ){







>







1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
  };
  static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = {
     { "search-ci",       "check-in search:",  "c" },
     { "search-doc",      "document search:",  "d" },
     { "search-tkt",      "ticket search:",    "t" },
     { "search-wiki",     "wiki search:",      "w" },
     { "search-technote", "tech note search:", "e" },
     { "search-forum",    "forum search:",     "f" },
  };
  char *zSubCmd = 0;
  int i, j, n;
  int iCmd = 0;
  int iAction = 0;
  db_find_and_open_repository(0, 0);
  if( g.argc>2 ){

Changes to src/security_audit.c.

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

84
85
86
87
88
89
90
91
92
93
94
95



96
97
98
99
100
101
102
  @ <ol>

  /* Step 1:  Determine if the repository is public or private.  "Public"
  ** means that any anonymous user on the internet can access all content.
  ** "Private" repos require (non-anonymous) login to access all content,
  ** though some content may be accessible anonymously.
  */
  zAnonCap = db_text("", "SELECT group_concat(coalesce(cap,'')) FROM user"
                         " WHERE login IN ('anonymous','nobody')");
  zPubPages = db_get("public-pages",0);
  if( hasAnyCap(zAnonCap,"as") ){
    @ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
    @ it grants administrator privileges to anonymous users.  You
    @ should <a href="takeitprivate">take this repository private</a>
    @ immediately!  Or, at least remove the Setup and Admin privileges
    @ for users "anonymous" and "login" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }else if( hasAnyCap(zAnonCap,"y") ){
    @ <li><p>This repository is <big><b>INSECURE</b></big> because
    @ it allows anonymous users to push unversioned files.
    @ <p>Fix this by <a href="takeitprivate">taking the repository private</a>
    @ or by removing the "y" permission from users "anonymous" and
    @ "nobody" on the <a href="setup_ulist">User Configuration</a> page.
  }else if( hasAnyCap(zAnonCap,"goz") ){
    @ <li><p>This repository is <big><b>PUBLIC</b></big>. All
    @ checked-in content can be accessed by anonymous users.
    @ <a href="takeitprivate">Take it private</a>.<p>
  }else if( !hasAnyCap(zAnonCap, "jry") && (zPubPages==0 || zPubPages[0]==0) ){

    @ <li><p>This repository is <big><b>Completely PRIVATE</b></big>.
    @ A valid login and password is required to access any content.
  }else{
    @ <li><p>This repository is <big><b>Mostly PRIVATE</b></big>.
    @ A valid login and password is usually required, however some
    @ content can be accessed anonymously:
    @ <ul>
    if( hasAnyCap(zAnonCap,"j") ){
      @ <li> Wiki pages
    }
    if( hasAnyCap(zAnonCap,"r") ){
      @ <li> Tickets



    }
    if( zPubPages && zPubPages[0] ){
      Glob *pGlob = glob_create(zPubPages);
      int i;
      @ <li> URLs that match any of these GLOB patterns:
      @ <ul>
      for(i=0; i<pGlob->nPattern; i++){







|
<


















|
>












>
>
>







56
57
58
59
60
61
62
63

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
  @ <ol>

  /* Step 1:  Determine if the repository is public or private.  "Public"
  ** means that any anonymous user on the internet can access all content.
  ** "Private" repos require (non-anonymous) login to access all content,
  ** though some content may be accessible anonymously.
  */
  zAnonCap = db_text("", "SELECT fullcap(NULL)");

  zPubPages = db_get("public-pages",0);
  if( hasAnyCap(zAnonCap,"as") ){
    @ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
    @ it grants administrator privileges to anonymous users.  You
    @ should <a href="takeitprivate">take this repository private</a>
    @ immediately!  Or, at least remove the Setup and Admin privileges
    @ for users "anonymous" and "login" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }else if( hasAnyCap(zAnonCap,"y") ){
    @ <li><p>This repository is <big><b>INSECURE</b></big> because
    @ it allows anonymous users to push unversioned files.
    @ <p>Fix this by <a href="takeitprivate">taking the repository private</a>
    @ or by removing the "y" permission from users "anonymous" and
    @ "nobody" on the <a href="setup_ulist">User Configuration</a> page.
  }else if( hasAnyCap(zAnonCap,"goz") ){
    @ <li><p>This repository is <big><b>PUBLIC</b></big>. All
    @ checked-in content can be accessed by anonymous users.
    @ <a href="takeitprivate">Take it private</a>.<p>
  }else if( !hasAnyCap(zAnonCap, "jrwy234567")
         && (zPubPages==0 || zPubPages[0]==0) ){
    @ <li><p>This repository is <big><b>Completely PRIVATE</b></big>.
    @ A valid login and password is required to access any content.
  }else{
    @ <li><p>This repository is <big><b>Mostly PRIVATE</b></big>.
    @ A valid login and password is usually required, however some
    @ content can be accessed anonymously:
    @ <ul>
    if( hasAnyCap(zAnonCap,"j") ){
      @ <li> Wiki pages
    }
    if( hasAnyCap(zAnonCap,"r") ){
      @ <li> Tickets
    }
    if( hasAnyCap(zAnonCap,"234567") ){
      @ <li> Forum posts
    }
    if( zPubPages && zPubPages[0] ){
      Glob *pGlob = glob_create(zPubPages);
      int i;
      @ <li> URLs that match any of these GLOB patterns:
      @ <ul>
      for(i=0; i<pGlob->nPattern; i++){
124
125
126
127
128
129
130
131

132
133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
162
163
  /* Anonymous users should not be able to harvest email addresses
  ** from tickets.
  */
  if( hasAnyCap(zAnonCap, "e") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can view email addresses and other personally
    @ identifiable information on tickets.
    @ <p>Fix this by removing the "Email" privilege from users

    @ "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed to push content
  ** to the repository.
  */
  if( hasAnyCap(zAnonCap, "i") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can push new check-ins into the repository.
    @ <p>Fix this by removing the "Check-in" privilege from users

    @ "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed act as moderators
  ** for wiki or tickets.
  */
  if( hasAnyCap(zAnonCap, "lq") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can act as moderators for wiki and/or tickets.
    @ This defeats the whole purpose of moderation.
    @ <p>Fix this by removing the "Mod-Wiki" and "Mod-Tkt"

    @ privilege from users "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed to delete
  ** wiki or tickets.
  */
  if( hasAnyCap(zAnonCap, "d") ){
    @ <li><p><b>WARNING:</b>







|
>










|
>







|

|
|
|
>
|
|







127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
  /* Anonymous users should not be able to harvest email addresses
  ** from tickets.
  */
  if( hasAnyCap(zAnonCap, "e") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can view email addresses and other personally
    @ identifiable information on tickets.
    @ <p>Fix this by removing the "Email" privilege
    @ (<a href="setup_ucap_list">capability "e"</a>) from users
    @ "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed to push content
  ** to the repository.
  */
  if( hasAnyCap(zAnonCap, "i") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can push new check-ins into the repository.
    @ <p>Fix this by removing the "Check-in" privilege
    @ (<a href="setup_ucap_list">capability</a> "i") from users
    @ "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed act as moderators
  ** for wiki or tickets.
  */
  if( hasAnyCap(zAnonCap, "lq5") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can act as moderators for wiki, tickets, or 
    @ forum posts. This defeats the whole purpose of moderation.
    @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
    @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
    @ from users "anonymous" and "nobody"
    @ on the <a href="setup_ulist">User Configuration</a> page.
  }

  /* Anonymous users probably should not be allowed to delete
  ** wiki or tickets.
  */
  if( hasAnyCap(zAnonCap, "d") ){
    @ <li><p><b>WARNING:</b>
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189


























190
191
192
193
194
195




196
197

198
199
200

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
  */
  if( hasAnyCap(zAnonCap, "fk") ){
    if( db_get_boolean("modreq-wiki",0)==0 ){
      @ <li><p><b>WARNING:</b>
      @ Anonymous users can create or edit wiki without moderation.
      @ This can result in robots inserting lots of wiki spam into
      @ repository.
      @ <p>Fix this by removing the "New-Wiki" and "Write-Wiki"
      @ privileges from users "anonymous" and "nobody" on the
      @ <a href="setup_ulist">User Configuration</a> page or
      @ by enabling wiki moderation on the
      @ <a href="setup_modreq">Moderation Setup</a> page.
    }else{
      @ <li><p>
      @ Anonymous users can create or edit wiki, but moderator
      @ approval is required before the edits become permanent.
    }
  }



























  /* Administrative privilege should only be provided to
  ** specific individuals, not to entire classes of people.
  ** And not too many people should have administrator privilege.
  */
  z = db_text(0, "SELECT group_concat(login,' AND ') FROM user"




                 " WHERE cap GLOB '*[as]*'"
                 "   AND login in ('anonymous','nobody','reader','developer')");

  if( z && z[0] ){
    @ <li><p>
    @ Administrative privilege is granted to an entire class of users

    @ (%h(z)).  Ideally, the Write-Unver privilege should only be
    @ granted to specific individuals.
  }
  n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*[as]*'");
  if( n==0 ){
    @ <li><p>
    @ No users have administrator privilege.
  }else{
    z = db_text(0,
      "SELECT group_concat("
                 "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
             "', ')"
      " FROM user"
      " WHERE cap GLOB '*[as]*'"
    );
    @ <li><p>
    @ Users with administrator privilege are: %s(z)
    fossil_free(z);
    if( n>3 ){
      @ <p><b>Caution</b>:
      @ Administrator privilege is granted to
      @ <a href='setup_ulist?with=as'>%d(n) users</a>.
      @ Ideally, administator privilege ('s' or 'a') should only
      @ be granted to one or two users.
    }
  }

  /* The push-unversioned privilege should only be provided to
  ** specific individuals, not to entire classes of people.
  ** And no too many people should have this privilege.
  */
  z = db_text(0,
    "SELECT group_concat("
                 "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
             "' and ')"
    " FROM user"
    " WHERE cap GLOB '*y*'"
    "   AND login in ('anonymous','nobody','reader','developer')"
  );
  if( z && z[0] ){
    @ <li><p>
    @ The "Write-Unver" privilege is granted to an entire class of users
    @ (%s(z)).  Ideally, the Write-Unver privilege should only be
    @ granted to specific individuals.
    fossil_free(z);
  }
  n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*y*'");
  if( n>0 ){
    z = db_text(0,
       "SELECT group_concat("
          "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),', ')"
       " FROM user WHERE cap GLOB '*y*'"
    );
    @ <li><p>
    @ Users with "Write-Unver" privilege: %s(z)
    fossil_free(z);
    if( n>3 ){
      @ <p><b>Caution:</b>
      @ The "Write-Unver" privilege ('y') is granted to an excessive







|










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|
>
>
>
>
|
|
>

|
|
>
|


|









|





|




















|
|
|
<







|







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  */
  if( hasAnyCap(zAnonCap, "fk") ){
    if( db_get_boolean("modreq-wiki",0)==0 ){
      @ <li><p><b>WARNING:</b>
      @ Anonymous users can create or edit wiki without moderation.
      @ This can result in robots inserting lots of wiki spam into
      @ repository.
      @ Fix this by removing the "New-Wiki" and "Write-Wiki"
      @ privileges from users "anonymous" and "nobody" on the
      @ <a href="setup_ulist">User Configuration</a> page or
      @ by enabling wiki moderation on the
      @ <a href="setup_modreq">Moderation Setup</a> page.
    }else{
      @ <li><p>
      @ Anonymous users can create or edit wiki, but moderator
      @ approval is required before the edits become permanent.
    }
  }

  /* Anonymous users should not be able to create trusted forum
  ** posts.
  */
  if( hasAnyCap(zAnonCap, "456") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can create forum posts that are
    @ accepted into the permanent record without moderation.
    @ This can result in robots generating spam on forum posts.
    @ Fix this by removing the "WriteTrusted-Forum" privilege
    @ (<a href="setup_ucap_list">capabilities</a> "456") from
    @ users "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page or
  }

  /* Anonymous users should not be able to send announcements.
  */
  if( hasAnyCap(zAnonCap, "A") ){
    @ <li><p><b>WARNING:</b>
    @ Anonymous users can send announcements to anybody who is signed
    @ up to receive announcements.  This can result in spam.
    @ Fix this by removing the "Announce" privilege
    @ (<a href="setup_ucap_list">capability</a> "A") from
    @ users "anonymous" and "nobody" on the
    @ <a href="setup_ulist">User Configuration</a> page or
  }

  /* Administrative privilege should only be provided to
  ** specific individuals, not to entire classes of people.
  ** And not too many people should have administrator privilege.
  */
  z = db_text(0,
    "SELECT group_concat("
                 "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
             "' and ')"
    " FROM user"
    " WHERE cap GLOB '*[as]*'"
    "   AND login in ('anonymous','nobody','reader','developer')"
  );
  if( z && z[0] ){
    @ <li><p><b>WARNING:</b>
    @ Administrative privilege ('a' or 's')
    @ is granted to an entire class of users: %s(z).
    @ Administrative privilege should only be
    @ granted to specific individuals.
  }
  n = db_int(0,"SELECT count(*) FROM user WHERE fullcap(cap) GLOB '*[as]*'");
  if( n==0 ){
    @ <li><p>
    @ No users have administrator privilege.
  }else{
    z = db_text(0,
      "SELECT group_concat("
                 "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
             "', ')"
      " FROM user"
      " WHERE fullcap(cap) GLOB '*[as]*'"
    );
    @ <li><p>
    @ Users with administrator privilege are: %s(z)
    fossil_free(z);
    if( n>3 ){
      @ <li><p><b>WARNING:</b>
      @ Administrator privilege is granted to
      @ <a href='setup_ulist?with=as'>%d(n) users</a>.
      @ Ideally, administator privilege ('s' or 'a') should only
      @ be granted to one or two users.
    }
  }

  /* The push-unversioned privilege should only be provided to
  ** specific individuals, not to entire classes of people.
  ** And no too many people should have this privilege.
  */
  z = db_text(0,
    "SELECT group_concat("
                 "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),"
             "' and ')"
    " FROM user"
    " WHERE cap GLOB '*y*'"
    "   AND login in ('anonymous','nobody','reader','developer')"
  );
  if( z && z[0] ){
    @ <li><p><b>WARNING:</b>
    @ The "Write-Unver" privilege is granted to an entire class of users: %s(z).
    @ The Write-Unver privilege should only be granted to specific individuals.

    fossil_free(z);
  }
  n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*y*'");
  if( n>0 ){
    z = db_text(0,
       "SELECT group_concat("
          "printf('<a href=''setup_uedit?id=%%d''>%%s</a>',uid,login),', ')"
       " FROM user WHERE fullcap(cap) GLOB '*y*'"
    );
    @ <li><p>
    @ Users with "Write-Unver" privilege: %s(z)
    fossil_free(z);
    if( n>3 ){
      @ <p><b>Caution:</b>
      @ The "Write-Unver" privilege ('y') is granted to an excessive
347
348
349
350
351
352
353












354
355
356
357
358
359
360
    }else{
      fclose(pTest);
      @ <li><p>
      @ The error log at "<a href='%R/errorlog'>%h(g.zErrlog)</a>" that is
      @ %,lld(file_size(g.zErrlog, ExtFILE)) bytes in size.
    }
  }













  @ </ol>
  style_footer();
}

/*
** WEBPAGE: takeitprivate







>
>
>
>
>
>
>
>
>
>
>
>







384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
    }else{
      fclose(pTest);
      @ <li><p>
      @ The error log at "<a href='%R/errorlog'>%h(g.zErrlog)</a>" that is
      @ %,lld(file_size(g.zErrlog, ExtFILE)) bytes in size.
    }
  }

  @ <li><p> User capability summary:
  capability_summary();

  if( alert_enabled() ){
    @ <li><p> Email alert configuration summary:
    @ <table class="label-value">
    stats_for_email();
    @ </table>
  }else{
    @ <li><p> Email alerts are disabled
  }

  @ </ol>
  style_footer();
}

/*
** WEBPAGE: takeitprivate

Changes to src/setup.c.

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
    "Enter raw SQL commands");
  setup_menu_entry("TH1", "admin_th1",
    "Enter raw TH1 commands");
  @ </table>

  style_footer();
}

/*
** WEBPAGE: setup_ulist
**
** Show a list of users.  Clicking on any user jumps to the edit
** screen for that user.  Requires Admin privileges.
**
** Query parameters:
**
**   with=CAP         Only show users that have one or more capabilities in CAP.
*/
void setup_ulist(void){
  Stmt s;
  double rNow;
  const char *zWith = P("with");

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }

  if( zWith==0 || zWith[0]==0 ){
    style_submenu_element("Add", "setup_uedit");
    style_submenu_element("Log", "access_log");
    style_submenu_element("Help", "setup_ulist_notes");
    style_header("User List");
    @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
    @ <thead><tr>
    @   <th>Category
    @   <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
    @   <th>Info <th>Last Change</tr></thead>
    @ <tbody>
    db_prepare(&s,
       "SELECT uid, login, cap, date(mtime,'unixepoch')"
       "  FROM user"
       " WHERE login IN ('anonymous','nobody','developer','reader')"
       " ORDER BY login"
    );
    while( db_step(&s)==SQLITE_ROW ){
      int uid = db_column_int(&s, 0);
      const char *zLogin = db_column_text(&s, 1);
      const char *zCap = db_column_text(&s, 2);
      const char *zDate = db_column_text(&s, 4);
      @ <tr>
      @ <td><a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
      @ <td>%h(zCap)

      if( fossil_strcmp(zLogin,"anonymous")==0 ){
        @ <td>All logged-in users
      }else if( fossil_strcmp(zLogin,"developer")==0 ){
        @ <td>Users with '<b>v</b>' capability
      }else if( fossil_strcmp(zLogin,"nobody")==0 ){
        @ <td>All users without login
      }else if( fossil_strcmp(zLogin,"reader")==0 ){
        @ <td>Users with '<b>u</b>' capability
      }else{
        @ <td>
      }
      if( zDate && zDate[0] ){
        @ <td>%h(zDate)
      }else{
        @ <td>
      }
      @ </tr>
    }
    db_finalize(&s);
  }else{
    style_header("Users With Capabilities \"%h\"", zWith);
  }
  @ </tbody></table>
  @ <div class='section'>Users</div>
  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
  @  data-column-types='ktxTTK' data-init-sort='2'>
  @ <thead><tr>
  @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
  @ <tbody>
  db_multi_exec(
    "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)"
    "WITHOUT ROWID;"
  );
  if( db_table_exists("repository","accesslog") ){
    db_multi_exec(
      "INSERT INTO lastAccess(uname, atime)"
      " SELECT uname, max(mtime) FROM ("
      "    SELECT uname, mtime FROM accesslog WHERE success"
      "    UNION ALL"
      "    SELECT login AS uname, rcvfrom.mtime AS mtime"
      "      FROM rcvfrom JOIN user USING(uid))"
      " GROUP BY 1;"
    );
  }
  if( zWith && zWith[0] ){
    zWith = mprintf(" AND cap GLOB '*[%q]*'", zWith);
  }else{
    zWith = "";
  }
  db_prepare(&s,
     "SELECT uid, login, cap, info, date(mtime,'unixepoch'),"
     "       lower(login) AS sortkey, "
     "       CASE WHEN info LIKE '%%expires 20%%'"
             "    THEN substr(info,instr(lower(info),'expires')+8,10)"
             "    END AS exp,"
             "atime"
     "  FROM user LEFT JOIN lastAccess ON login=uname"
     " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
     " ORDER BY sortkey", zWith/*safe-for-%s*/
  );
  rNow = db_double(0.0, "SELECT julianday('now');");
  while( db_step(&s)==SQLITE_ROW ){
    int uid = db_column_int(&s, 0);
    const char *zLogin = db_column_text(&s, 1);
    const char *zCap = db_column_text(&s, 2);
    const char *zInfo = db_column_text(&s, 3);
    const char *zDate = db_column_text(&s, 4);
    const char *zSortKey = db_column_text(&s,5);
    const char *zExp = db_column_text(&s,6);
    double rATime = db_column_double(&s,7);
    char *zAge = 0;
    if( rATime>0.0 ){
      zAge = human_readable_age(rNow - rATime);
    }
    @ <tr>
    @ <td data-sortkey='%h(zSortKey)'>\
    @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
    @ <td>%h(zCap)
    @ <td>%h(zInfo)
    @ <td>%h(zDate?zDate:"")
    @ <td>%h(zExp?zExp:"")
    @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
  style_table_sorter();
  style_footer();
}

/*
** Render the user-capability table
*/
static void setup_usercap_table(void){
  @ <table>
     @ <tr><th valign="top">a</th>
     @   <td><i>Admin:</i> Create and delete users</td></tr>
     @ <tr><th valign="top">b</th>
     @   <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr>
     @ <tr><th valign="top">c</th>
     @   <td><i>Append-Tkt:</i> Append to tickets</td></tr>
     @ <tr><th valign="top">d</th>
     @   <td><i>Delete:</i> Delete wiki and tickets</td></tr>
     @ <tr><th valign="top">e</th>
     @   <td><i>View-PII:</i> \
     @ View sensitive data such as email addresses</td></tr>
     @ <tr><th valign="top">f</th>
     @   <td><i>New-Wiki:</i> Create new wiki pages</td></tr>
     @ <tr><th valign="top">g</th>
     @   <td><i>Clone:</i> Clone the repository</td></tr>
     @ <tr><th valign="top">h</th>
     @   <td><i>Hyperlinks:</i> Show hyperlinks to detailed
     @   repository history</td></tr>
     @ <tr><th valign="top">i</th>
     @   <td><i>Check-In:</i> Commit new versions in the repository</td></tr>
     @ <tr><th valign="top">j</th>
     @   <td><i>Read-Wiki:</i> View wiki pages</td></tr>
     @ <tr><th valign="top">k</th>
     @   <td><i>Write-Wiki:</i> Edit wiki pages</td></tr>
     @ <tr><th valign="top">l</th>
     @   <td><i>Mod-Wiki:</i> Moderator for wiki pages</td></tr>
     @ <tr><th valign="top">m</th>
     @   <td><i>Append-Wiki:</i> Append to wiki pages</td></tr>
     @ <tr><th valign="top">n</th>
     @   <td><i>New-Tkt:</i> Create new tickets</td></tr>
     @ <tr><th valign="top">o</th>
     @   <td><i>Check-Out:</i> Check out versions</td></tr>
     @ <tr><th valign="top">p</th>
     @   <td><i>Password:</i> Change your own password</td></tr>
     @ <tr><th valign="top">q</th>
     @   <td><i>Mod-Tkt:</i> Moderator for tickets</td></tr>
     @ <tr><th valign="top">r</th>
     @   <td><i>Read-Tkt:</i> View tickets</td></tr>
     @ <tr><th valign="top">s</th>
     @   <td><i>Setup/Super-user:</i> Setup and configure this website</td></tr>
     @ <tr><th valign="top">t</th>
     @   <td><i>Tkt-Report:</i> Create new bug summary reports</td></tr>
     @ <tr><th valign="top">u</th>
     @   <td><i>Reader:</i> Inherit privileges of
     @   user <tt>reader</tt></td></tr>
     @ <tr><th valign="top">v</th>
     @   <td><i>Developer:</i> Inherit privileges of
     @   user <tt>developer</tt></td></tr>
     @ <tr><th valign="top">w</th>
     @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
     @ <tr><th valign="top">x</th>
     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
     @ <tr><th valign="top">y</th>
     @   <td><i>Write-Unver:</i> Push unversioned files</td></tr>
     @ <tr><th valign="top">z</th>
     @   <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>
     @ <tr><th valign="top">2</th>
     @   <td><i>Forum-Read:</i> Read forum posts by others </td></tr>
     @ <tr><th valign="top">3</th>
     @   <td><i>Forum-Append:</i> Add new forum posts</td></tr>
     @ <tr><th valign="top">4</th>
     @   <td><i>Forum-Trusted:</i> Add pre-approved forum posts </td></tr>
     @ <tr><th valign="top">5</th>
     @   <td><i>Forum-Moderator:</i> Approve or disapprove forum posts</td></tr>
     @ <tr><th valign="top">6</th>
     @   <td><i>Forum-Supervisor:</i> \
     @ Forum administrator
     @ <tr><th valign="top">7</th>
     @   <td><i>Email-Alerts:</i> Sign up for email nofications</td></tr>
     @ <tr><th valign="top">A</th>
     @   <td><i>Announce:</i> Send announcements</td></tr>
     @ <tr><th valign="top">D</th>
     @   <td><i>Debug:</i> Enable debugging features</td></tr>
  @ </table>
}

/*
** WEBPAGE: setup_ulist_notes
**
** A documentation page showing notes about user configuration.  This
** information used to be a side-bar on the user list page, but has been
** factored out for improved presentation.
*/
void setup_ulist_notes(void){
  style_header("User Configuration Notes");
  @ <h1>User Configuration Notes:</h1>
  @ <ol>
  @ <li><p>
  @ Every user, logged in or not, inherits the privileges of
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Any human can login as <span class="usertype">anonymous</span> since the
  @ password is clearly displayed on the login page for them to type. The
  @ purpose of requiring anonymous to log in is to prevent access by spiders.
  @ Every logged-in user inherits the combined privileges of
  @ <span class="usertype">anonymous</span> and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Users with privilege <span class="capability">u</span> inherit the combined
  @ privileges of <span class="usertype">reader</span>,
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Users with privilege <span class="capability">v</span> inherit the combined
  @ privileges of <span class="usertype">developer</span>,
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>The permission flags are as follows:</p>
  setup_usercap_table();
  @ </li>
  @ </ol>
  style_footer();
}

/*
** WEBPAGE: setup_ucap_list
**
** A documentation page showing the meaning of the various user capabilities
** code letters.
*/
void setup_ucap_list(void){
  style_header("User Capability Codes");
  setup_usercap_table();
  style_footer();
}

/*
** Return true if zPw is a valid password string.  A valid
** password string is:
**
**  (1)  A zero-length string, or
**  (2)  a string that contains a character other than '*'.
*/
static int isValidPwString(const char *zPw){
  if( zPw==0 ) return 0;
  if( zPw[0]==0 ) return 1;
  while( zPw[0]=='*' ){ zPw++; }
  return zPw[0]!=0;
}

/*
** WEBPAGE: setup_uedit
**
** Edit information about a user or create a new user.
** Requires Admin privileges.
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  const char *zGroup;
  const char *zOldLogin;
  int doWrite;
  int uid, i;
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */
  const char *inherit[128];
  int a[128];
  const char *oa[128];

  /* Must have ADMIN privileges to access this page
  */
  login_check_credentials();
  if( !g.perm.Admin ){ login_needed(0); return; }

  /* Check to see if an ADMIN user is trying to edit a SETUP account.
  ** Don't allow that.
  */
  zId = PD("id", "0");
  uid = atoi(zId);
  if( zId && !g.perm.Setup && uid>0 ){
    char *zOldCaps;
    zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid);
    higherUser = zOldCaps && strchr(zOldCaps,'s');
  }

  if( P("can") ){
    /* User pressed the cancel button */
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }

  /* If we have all the necessary information, write the new or
  ** modified user record.  After writing the user record, redirect
  ** to the page that displays a list of users.
  */
  doWrite = cgi_all("login","info","pw") && !higherUser && cgi_csrf_safe(1);
  if( doWrite ){
    char c;
    char zCap[70], zNm[4];
    zNm[0] = 'a';
    zNm[2] = 0;
    for(i=0, c='a'; c<='z'; c++){
      zNm[1] = c;
      a[c&0x7f] = (c!='s' || g.perm.Setup) && P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }
    for(c='0'; c<='9'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }
    for(c='A'; c<='Z'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }

    zCap[i] = 0;
    zPw = P("pw");
    zLogin = P("login");
    if( strlen(zLogin)==0 ){
      const char *zRef = cgi_referer("setup_ulist");
      style_header("User Creation Error");
      @ <span class="loginError">Empty login not allowed.</span>
      @
      @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
      @ [Bummer]</a></p>
      style_footer();
      return;
    }
    if( isValidPwString(zPw) ){
      zPw = sha1_shared_secret(zPw, zLogin, 0);
    }else{
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }
    zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid);
    if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d",zLogin,uid) ){
      const char *zRef = cgi_referer("setup_ulist");
      style_header("User Creation Error");
      @ <span class="loginError">Login "%h(zLogin)" is already used by
      @ a different user.</span>
      @
      @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
      @ [Bummer]</a></p>
      style_footer();
      return;
    }
    login_verify_csrf_secret();
    db_multi_exec(
       "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
       "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
      uid, zLogin, P("info"), zPw, zCap
    );
    setup_incr_cfgcnt();
    admin_log( "Updated user [%q] with capabilities [%q].",
               zLogin, zCap );
    if( atoi(PD("all","0"))>0 ){
      Blob sql;
      char *zErr = 0;
      blob_zero(&sql);
      if( zOldLogin==0 ){
        blob_appendf(&sql,
          "INSERT INTO user(login)"
          "  SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
          zLogin, zLogin
        );
        zOldLogin = zLogin;
      }
      blob_appendf(&sql,
        "UPDATE user SET login=%Q,"
        "  pw=coalesce(shared_secret(%Q,%Q,"
                "(SELECT value FROM config WHERE name='project-code')),pw),"
        "  info=%Q,"
        "  cap=%Q,"
        "  mtime=now()"
        " WHERE login=%Q;",
        zLogin, P("pw"), zLogin, P("info"), zCap,
        zOldLogin
      );
      login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
      blob_reset(&sql);
      admin_log( "Updated user [%q] in all login groups "
                 "with capabilities [%q].",
                 zLogin, zCap );
      if( zErr ){
        const char *zRef = cgi_referer("setup_ulist");
        style_header("User Change Error");
        admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
        @ <span class="loginError">%h(zErr)</span>
        @
        @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
        @ [Bummer]</a></p>
        style_footer();
        return;
      }
    }
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  for(i='a'; i<='z'; i++) oa[i] = "";
  for(i='0'; i<='9'; i++) oa[i] = "";
  for(i='A'; i<='Z'; i++) oa[i] = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    for(i=0; zCap[i]; i++){
      char c = zCap[i];
      if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
        oa[c&0x7f] = " checked=\"checked\"";
      }
    }
  }

  /* figure out inherited permissions */
  memset((char *)inherit, 0, sizeof(inherit));
  if( fossil_strcmp(zLogin, "developer") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
         "<span class=\"ueditInheritDeveloper\"><sub>[D]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "reader") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
          "<span class=\"ueditInheritReader\"><sub>[R]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "anonymous") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
           "<span class=\"ueditInheritAnonymous\"><sub>[A]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "nobody") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
           "<span class=\"ueditInheritNobody\"><sub>[N]</sub></span>";
    }
    free(z2);
  }

  /* Begin generating the page
  */
  style_submenu_element("Cancel", "%s", cgi_referer("setup_ulist"));
  if( uid ){
    style_header("Edit User %h", zLogin);
    style_submenu_element("Access Log", "%R/access_log?u=%t", zLogin);
  }else{
    style_header("Add A New User");
  }
  @ <div class="ueditCapBox">
  @ <form action="%s(g.zPath)" method="post"><div>
  login_insert_csrf_secret();
  if( login_is_special(zLogin) ){
    @ <input type="hidden" name="login" value="%s(zLogin)">
    @ <input type="hidden" name="info" value="">
    @ <input type="hidden" name="pw" value="*">
  }
  @ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
  @ <table>
  @ <tr>
  @   <td class="usetupEditLabel">User ID:</td>
  if( uid ){
    @   <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" /></td>
  }else{
    @   <td>(new user)<input type="hidden" name="id" value="0" /></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Login:</td>
  if( login_is_special(zLogin) ){
    @    <td><b>%h(zLogin)</b></td>
  }else{
    @   <td><input type="text" name="login" value="%h(zLogin)" /></td>
    @ </tr>
    @ <tr>
    @   <td class="usetupEditLabel">Contact&nbsp;Info:</td>
    @   <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Capabilities:</td>
  @   <td>
#define B(x) inherit[x]
  @ <table border=0><tr><td valign="top">
  if( g.perm.Setup ){
    @  <label><input type="checkbox" name="as"%s(oa['s']) />
    @  Setup%s(B('s'))</label><br />
  }
  @  <label><input type="checkbox" name="aa"%s(oa['a']) />
  @  Admin%s(B('a'))</label><br />
  @  <label><input type="checkbox" name="au"%s(oa['u']) />
  @  Reader%s(B('u'))</label><br>
  @  <label><input type="checkbox" name="av"%s(oa['v']) />
  @  Developer%s(B('v'))</label><br />
  @  <label><input type="checkbox" name="ad"%s(oa['d']) />
  @  Delete%s(B('d'))</label><br />
  @  <label><input type="checkbox" name="ae"%s(oa['e']) />
  @  View-PII%s(B('e'))</label><br />
  @  <label><input type="checkbox" name="ap"%s(oa['p']) />
  @  Password%s(B('p'))</label><br />
  @  <label><input type="checkbox" name="ai"%s(oa['i']) />
  @  Check-In%s(B('i'))</label><br />
  @  <label><input type="checkbox" name="ao"%s(oa['o']) />
  @  Check-Out%s(B('o'))</label><br />
  @  <label><input type="checkbox" name="ah"%s(oa['h']) />
  @  Hyperlinks%s(B('h'))</label><br />
  @  <label><input type="checkbox" name="ab"%s(oa['b']) />
  @  Attachments%s(B('b'))</label><br>
  @  <label><input type="checkbox" name="ag"%s(oa['g']) />
  @  Clone%s(B('g'))</label><br />

  @ </td><td><td width="40"></td><td valign="top">
  @  <label><input type="checkbox" name="aj"%s(oa['j']) />
  @  Read Wiki%s(B('j'))</label><br>
  @  <label><input type="checkbox" name="af"%s(oa['f']) />
  @  New Wiki%s(B('f'))</label><br />
  @  <label><input type="checkbox" name="am"%s(oa['m']) />
  @  Append Wiki%s(B('m'))</label><br />
  @  <label><input type="checkbox" name="ak"%s(oa['k']) />
  @  Write Wiki%s(B('k'))</label><br />
  @  <label><input type="checkbox" name="al"%s(oa['l']) />
  @  Moderate Wiki%s(B('l'))</label><br />
  @  <label><input type="checkbox" name="ar"%s(oa['r']) />
  @  Read Ticket%s(B('r'))</label><br />
  @  <label><input type="checkbox" name="an"%s(oa['n']) />
  @  New Tickets%s(B('n'))</label><br />
  @  <label><input type="checkbox" name="ac"%s(oa['c']) />
  @  Append To Ticket%s(B('c'))</label><br>
  @  <label><input type="checkbox" name="aw"%s(oa['w']) />
  @  Write Tickets%s(B('w'))</label><br />
  @  <label><input type="checkbox" name="aq"%s(oa['q']) />
  @  Moderate Tickets%s(B('q'))</label><br>
  @  <label><input type="checkbox" name="at"%s(oa['t']) />
  @  Ticket Report%s(B('t'))</label><br />
  @  <label><input type="checkbox" name="ax"%s(oa['x']) />
  @  Private%s(B('x'))</label>

  @ </td><td><td width="40"></td><td valign="top">
  @  <label><input type="checkbox" name="ay"%s(oa['y']) />
  @  Write Unversioned%s(B('y'))</label><br />
  @  <label><input type="checkbox" name="az"%s(oa['z']) />
  @  Download Zip%s(B('z'))</label><br />
  @  <label><input type="checkbox" name="a2"%s(oa['2']) />
  @  Read Forum%s(B('2'))</label><br />
  @  <label><input type="checkbox" name="a3"%s(oa['3']) />
  @  Write Forum%s(B('3'))</label><br />
  @  <label><input type="checkbox" name="a4"%s(oa['4']) />
  @  WriteTrusted Forum%s(B('4'))</label><br>
  @  <label><input type="checkbox" name="a5"%s(oa['5']) />
  @  Moderate Forum%s(B('5'))</label><br>
  @  <label><input type="checkbox" name="a6"%s(oa['6']) />
  @  Supervise Forum%s(B('6'))</label><br>
  @  <label><input type="checkbox" name="a7"%s(oa['7']) />
  @  Email Alerts%s(B('7'))</label><br>
  @  <label><input type="checkbox" name="aA"%s(oa['A']) />
  @  Send Announcements%s(B('A'))</label><br>
  @  <label><input type="checkbox" name="aD"%s(oa['D']) />
  @  Enable Debug%s(B('D'))</label>
  @ </td></tr>
  @ </table>
  @   </td>
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Selected Cap.:</td>
  @   <td>
  @     <span id="usetupEditCapability">(missing JS?)</span>
  @     <a href="%R/setup_ucap_list">(key)</a>
  @   </td>
  @ </tr>
  if( !login_is_special(zLogin) ){
    @ <tr>
    @   <td align="right">Password:</td>
    if( zPw[0] ){
      /* Obscure the password for all users */
      @   <td><input type="password" name="pw" value="**********" /></td>
    }else{
      /* Show an empty password as an empty input field */
      @   <td><input type="password" name="pw" value="" /></td>
    }
    @ </tr>
  }
  zGroup = login_group_name();
  if( zGroup ){
    @ <tr>
    @ <td valign="top" align="right">Scope:</td>
    @ <td valign="top">
    @ <input type="radio" name="all" checked value="0">
    @ Apply changes to this repository only.<br />
    @ <input type="radio" name="all" value="1">
    @ Apply changes to all repositories in the "<b>%h(zGroup)</b>"
    @ login group.</td></tr>
  }
  if( !higherUser ){
    @ <tr>
    @   <td>&nbsp;</td>
    @   <td><input type="submit" name="submit" value="Apply Changes" /></td>
    @ </tr>
  }
  @ </table>
  @ </div></form>
  @ </div>
  style_load_one_js_file("useredit.js");
  @ <h2>Privileges And Capabilities:</h2>
  @ <ul>
  if( higherUser ){
    @ <li><p class="missingPriv">
    @ User %h(zLogin) has Setup privileges and you only have Admin privileges
    @ so you are not permitted to make changes to %h(zLogin).
    @ </p></li>
    @
  }
  @ <li><p>
  @ The <span class="capability">Setup</span> user can make arbitrary
  @ configuration changes. An <span class="usertype">Admin</span> user
  @ can add other users and change user privileges
  @ and reset user passwords.  Both automatically get all other privileges
  @ listed below.  Use these two settings with discretion.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritNobody"><sub>N</sub></span>" subscript suffix
  @ indicates the privileges of <span class="usertype">nobody</span> that
  @ are available to all users regardless of whether or not they are logged in.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritAnonymous"><sub>A</sub></span>"
  @ subscript suffix
  @ indicates the privileges of <span class="usertype">anonymous</span> that
  @ are inherited by all logged-in users.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritDeveloper"><sub>D</sub></span>"
  @ subscript suffix indicates the privileges of 
  @ <span class="usertype">developer</span> that
  @ are inherited by all users with the
  @ <span class="capability">Developer</span> privilege.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritReader"><sub>R</sub></span>" subscript suffix
  @ indicates the privileges of <span class="usertype">reader</span> that
  @ are inherited by all users with the <span class="capability">Reader</span>
  @ privilege.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Delete</span> privilege give the user the
  @ ability to erase wiki, tickets, and attachments that have been added
  @ by anonymous users.  This capability is intended for deletion of spam.
  @ The delete capability is only in effect for 24 hours after the item
  @ is first posted.  The <span class="usertype">Setup</span> user can
  @ delete anything at any time.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Hyperlinks</span> privilege allows a user
  @ to see most hyperlinks. This is recommended ON for most logged-in users
  @ but OFF for user "nobody" to avoid problems with spiders trying to walk
  @ every diff and annotation of every historical check-in and file.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Zip</span> privilege allows a user to
  @ see the "download as ZIP"
  @ hyperlink and permits access to the <tt>/zip</tt> page.  This allows
  @ users to download ZIP archives without granting other rights like
  @ <span class="capability">Read</span> or
  @ <span class="capability">Hyperlink</span>.  The "z" privilege is recommended
  @ for user <span class="usertype">nobody</span> so that automatic package
  @ downloaders can obtain the sources without going through the login
  @ procedure.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Check-in</span> privilege allows remote
  @ users to "push". The <span class="capability">Check-out</span> privilege
  @ allows remote users to "pull". The <span class="capability">Clone</span>
  @ privilege allows remote users to "clone".
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Read Wiki</span>,
  @ <span class="capability">New Wiki</span>,
  @ <span class="capability">Append Wiki</span>, and
  @ <b>Write Wiki</b> privileges control access to wiki pages.  The
  @ <span class="capability">Read Ticket</span>,
  @ <span class="capability">New Ticket</span>,
  @ <span class="capability">Append Ticket</span>, and
  @ <span class="capability">Write Ticket</span> privileges control access
  @ to trouble tickets.
  @ The <span class="capability">Ticket Report</span> privilege allows
  @ the user to create or edit ticket report formats.
  @ </p></li>
  @
  @ <li><p>
  @ Users with the <span class="capability">Password</span> privilege
  @ are allowed to change their own password.  Recommended ON for most
  @ users but OFF for special users <span class="usertype">developer</span>,
  @ <span class="usertype">anonymous</span>,
  @ and <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">View-PII</span> privilege allows the display
  @ of personally-identifiable information information such as the
  @ email address of users and contact
  @ information on tickets. Recommended OFF for
  @ <span class="usertype">anonymous</span> and for
  @ <span class="usertype">nobody</span> but ON for
  @ <span class="usertype">developer</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Attachment</span> privilege is needed in
  @ order to add attachments to tickets or wiki.  Write privilege on the
  @ ticket or wiki is also required.
  @ </p></li>
  @
  @ <li><p>
  @ Login is prohibited if the password is an empty string.
  @ </p></li>
  @ </ul>
  @
  @ <h2>Special Logins</h2>
  @
  @ <ul>
  @ <li><p>
  @ No login is required for user <span class="usertype">nobody</span>. The
  @ capabilities of the <span class="usertype">nobody</span> user are
  @ inherited by all users, regardless of whether or not they are logged in.
  @ To disable universal access to the repository, make sure that the
  @ <span class="usertype">nobody</span> user has no capabilities
  @ enabled. The password for <span class="usertype">nobody</span> is ignored.
  @ </p></li>
  @
  @ <li><p>
  @ Login is required for user <span class="usertype">anonymous</span> but the
  @ password is displayed on the login screen beside the password entry box
  @ so anybody who can read should be able to login as anonymous.
  @ On the other hand, spiders and web-crawlers will typically not
  @ be able to login.  Set the capabilities of the
  @ <span class="usertype">anonymous</span>
  @ user to things that you want any human to be able to do, but not any
  @ spider.  Every other logged-in user inherits the privileges of
  @ <span class="usertype">anonymous</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="usertype">developer</span> user is intended as a template
  @ for trusted users with check-in privileges. When adding new trusted users,
  @ simply select the <span class="capability">developer</span> privilege to
  @ cause the new user to inherit all privileges of the
  @ <span class="usertype">developer</span>
  @ user.  Similarly, the <span class="usertype">reader</span> user is a
  @ template for users who are allowed more access than
  @ <span class="usertype">anonymous</span>,
  @ but less than a <span class="usertype">developer</span>.
  @ </p></li>
  @ </ul>
  style_footer();
}


/*
** Generate a checkbox for an attribute.
*/
void onoff_attribute(
  const char *zLabel,   /* The text label on the checkbox */
  const char *zVar,     /* The corresponding row in the VAR table */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







155
156
157
158
159
160
161


























































































































































































































































































































































































































































































































































































































































































































































































































































162
163
164
165
166
167
168
    "Enter raw SQL commands");
  setup_menu_entry("TH1", "admin_th1",
    "Enter raw TH1 commands");
  @ </table>

  style_footer();
}



























































































































































































































































































































































































































































































































































































































































































































































































































































/*
** Generate a checkbox for an attribute.
*/
void onoff_attribute(
  const char *zLabel,   /* The text label on the checkbox */
  const char *zVar,     /* The corresponding row in the VAR table */
1310
1311
1312
1313
1314
1315
1316


1317
1318
1319
1320
1321
1322
1323
  @ <hr />
  entry_attribute("Default privileges", 10, "default-perms",
                  "defaultperms", "u", 0);
  @ <p>Permissions given to users that... <ul><li>register themselves using
  @ the self-registration procedure (if enabled), or <li>access "public"
  @ pages identified by the public-pages glob pattern above, or <li>
  @ are users newly created by the administrator.</ul>


  @ (Property: "default-perms")
  @ </p>

  @ <hr />
  onoff_attribute("Show javascript button to fill in CAPTCHA",
                  "auto-captcha", "autocaptcha", 0, 0);
  @ <p>When enabled, a button appears on the login screen for user







>
>







484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
  @ <hr />
  entry_attribute("Default privileges", 10, "default-perms",
                  "defaultperms", "u", 0);
  @ <p>Permissions given to users that... <ul><li>register themselves using
  @ the self-registration procedure (if enabled), or <li>access "public"
  @ pages identified by the public-pages glob pattern above, or <li>
  @ are users newly created by the administrator.</ul>
  @ <p>Recommended value: "u" for Reader.
  @ <a href="%R/setup_ucap_list">Capability Key</a>.
  @ (Property: "default-perms")
  @ </p>

  @ <hr />
  onoff_attribute("Show javascript button to fill in CAPTCHA",
                  "auto-captcha", "autocaptcha", 0, 0);
  @ <p>When enabled, a button appears on the login screen for user
1708
1709
1710
1711
1712
1713
1714




















1715
1716
1717
1718
1719
1720
1721
  @ to a documentation page (ex: "/doc/tip/index.wiki") or to "/timeline".</p>
  @
  @ <p>Note:  To avoid a redirect loop or other problems, this entry must
  @ begin with "/" and it must specify a valid page.  For example,
  @ "<b>/home</b>" will work but "<b>home</b>" will not, since it omits the
  @ leading "/".</p>
  @ <p>(Property: "index-page")




















  @ <hr />
  onoff_attribute("Use HTML as wiki markup language",
    "wiki-use-html", "wiki-use-html", 0, 0);
  @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
  @ but all other wiki formatting will be ignored. This option is helpful
  @ if you have chosen to use a rich HTML editor for wiki markup such as
  @ TinyMCE.</p>







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
  @ to a documentation page (ex: "/doc/tip/index.wiki") or to "/timeline".</p>
  @
  @ <p>Note:  To avoid a redirect loop or other problems, this entry must
  @ begin with "/" and it must specify a valid page.  For example,
  @ "<b>/home</b>" will work but "<b>home</b>" will not, since it omits the
  @ leading "/".</p>
  @ <p>(Property: "index-page")
  @ <hr>
  @ <p>Extra links to appear on the <a href="%R/sitemap">/sitemap</a> page.
  @ Often these are filled in with links like 
  @ "/doc/trunk/doc/<i>filename</i>.md" so that they refer to 
  @ embedded documentation, or like "/wiki/<i>pagename</i>" to refer
  @ to wiki pages.
  @ Leave blank to omit.
  @ <p>
  entry_attribute("Documentation Index", 40, "sitemap-docidx", "smdocidx",
                  "", 0);
  @ (Property: sitemap-docidx)<br>
  entry_attribute("Download", 40, "sitemap-download", "smdownload",
                  "", 0);
  @ (Property: sitemap-download)<br>
  entry_attribute("License", 40, "sitemap-license", "smlicense",
                  "", 0);
  @ (Property: sitemap-license)<br>
  entry_attribute("Contact", 40, "sitemap-contact", "smcontact",
                  "", 0);
  @ (Property: sitemap-contact)
  @ <hr />
  onoff_attribute("Use HTML as wiki markup language",
    "wiki-use-html", "wiki-use-html", 0, 0);
  @ <p>Use HTML as the wiki markup language. Wiki links will still be parsed
  @ but all other wiki formatting will be ignored. This option is helpful
  @ if you have chosen to use a rich HTML editor for wiki markup such as
  @ TinyMCE.</p>
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
  int go = P("go")!=0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  add_content_sql_commands(g.db);
  db_begin_transaction();
  zQ = cgi_csrf_safe(1) ? P("q") : 0;
  style_header("Raw SQL Commands");
  @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
  @ run by this page.  You can do serious and irrepairable damage to the
  @ repository.  Proceed with extreme caution.</p>
  @
#if 0







<







1223
1224
1225
1226
1227
1228
1229

1230
1231
1232
1233
1234
1235
1236
  int go = P("go")!=0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  add_content_sql_commands(g.db);

  zQ = cgi_csrf_safe(1) ? P("q") : 0;
  style_header("Raw SQL Commands");
  @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
  @ run by this page.  You can do serious and irrepairable damage to the
  @ repository.  Proceed with extreme caution.</p>
  @
#if 0
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
  const char *zQ = P("q");
  int go = P("go")!=0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();
  style_header("Raw TH1 Commands");
  @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
  @ run by this page.  If Tcl integration was enabled at compile-time and
  @ the "tcl" setting is enabled, Tcl commands may be run as well.</p>
  @
  @ <form method="post" action="%s(g.zTop)/admin_th1">
  login_insert_csrf_secret();







<







1361
1362
1363
1364
1365
1366
1367

1368
1369
1370
1371
1372
1373
1374
  const char *zQ = P("q");
  int go = P("go")!=0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }

  style_header("Raw TH1 Commands");
  @ <p><b>Caution:</b> There are no restrictions on the TH1 that can be
  @ run by this page.  If Tcl integration was enabled at compile-time and
  @ the "tcl" setting is enabled, Tcl commands may be run as well.</p>
  @
  @ <form method="post" action="%s(g.zTop)/admin_th1">
  login_insert_csrf_secret();
2254
2255
2256
2257
2258
2259
2260

2261
2262
2263
2264
2265
2266
2267
    @ <tr class="row%d(counter%2)">
    @ <td class="adminTime">%s(zTime)</td>
    @ <td>%s(zUser)</td>
    @ <td>%s(zPage)</td>
    @ <td>%h(zMessage)</td>
    @ </tr>
  }

  @ </tbody></table>
  if( counter>ofst+limit ){
    @ <p><a href="admin_log?n=%d(limit)&x=%d(limit+ofst)">[Older]</a></p>
  }
  style_footer();
}








>







1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
    @ <tr class="row%d(counter%2)">
    @ <td class="adminTime">%s(zTime)</td>
    @ <td>%s(zUser)</td>
    @ <td>%s(zPage)</td>
    @ <td>%h(zMessage)</td>
    @ </tr>
  }
  db_finalize(&stLog);
  @ </tbody></table>
  if( counter>ofst+limit ){
    @ <p><a href="admin_log?n=%d(limit)&x=%d(limit+ofst)">[Older]</a></p>
  }
  style_footer();
}

2310
2311
2312
2313
2314
2315
2316


2317
2318
2319
2320
2321
2322
2323
  onoff_attribute("Search Documents", "search-doc", "sd", 0, 0);
  @ <br />
  onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0);
  @ <br />
  onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0);
  @ <br />
  onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0);


  @ <hr />
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ <hr />
  if( P("fts0") ){
    search_drop_index();
  }else if( P("fts1") ){
    search_drop_index();







>
>







1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
  onoff_attribute("Search Documents", "search-doc", "sd", 0, 0);
  @ <br />
  onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0);
  @ <br />
  onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0);
  @ <br />
  onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0);
  @ <br />
  onoff_attribute("Search Forum", "search-forum", "sf", 0, 0);
  @ <hr />
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ <hr />
  if( P("fts0") ){
    search_drop_index();
  }else if( P("fts1") ){
    search_drop_index();

Added src/setupuser.c.

















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Setup pages associated with user management.  The code in this
** file was formerly part of the "setup.c" module, but has been broken
** out into its own module to improve maintainability.
*/
#include "config.h"
#include <assert.h>
#include "setupuser.h"

/*
** WEBPAGE: setup_ulist
**
** Show a list of users.  Clicking on any user jumps to the edit
** screen for that user.  Requires Admin privileges.
**
** Query parameters:
**
**   with=CAP         Only show users that have one or more capabilities in CAP.
*/
void setup_ulist(void){
  Stmt s;
  double rNow;
  const char *zWith = P("with");

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }

  if( zWith==0 || zWith[0]==0 ){
    style_submenu_element("Add", "setup_uedit");
    style_submenu_element("Log", "access_log");
    style_submenu_element("Help", "setup_ulist_notes");
    style_header("User List");
    @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
    @ <thead><tr>
    @   <th>Category
    @   <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
    @   <th>Info <th>Last Change</tr></thead>
    @ <tbody>
    db_prepare(&s,
       "SELECT uid, login, cap, date(mtime,'unixepoch')"
       "  FROM user"
       " WHERE login IN ('anonymous','nobody','developer','reader')"
       " ORDER BY login"
    );
    while( db_step(&s)==SQLITE_ROW ){
      int uid = db_column_int(&s, 0);
      const char *zLogin = db_column_text(&s, 1);
      const char *zCap = db_column_text(&s, 2);
      const char *zDate = db_column_text(&s, 4);
      @ <tr>
      @ <td><a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
      @ <td>%h(zCap)

      if( fossil_strcmp(zLogin,"anonymous")==0 ){
        @ <td>All logged-in users
      }else if( fossil_strcmp(zLogin,"developer")==0 ){
        @ <td>Users with '<b>v</b>' capability
      }else if( fossil_strcmp(zLogin,"nobody")==0 ){
        @ <td>All users without login
      }else if( fossil_strcmp(zLogin,"reader")==0 ){
        @ <td>Users with '<b>u</b>' capability
      }else{
        @ <td>
      }
      if( zDate && zDate[0] ){
        @ <td>%h(zDate)
      }else{
        @ <td>
      }
      @ </tr>
    }
    db_finalize(&s);
  }else{
    style_header("Users With Capabilities \"%h\"", zWith);
  }
  @ </tbody></table>
  @ <div class='section'>Users</div>
  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
  @  data-column-types='ktxTTK' data-init-sort='2'>
  @ <thead><tr>
  @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
  @ <tbody>
  db_multi_exec(
    "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)"
    "WITHOUT ROWID;"
  );
  if( db_table_exists("repository","accesslog") ){
    db_multi_exec(
      "INSERT INTO lastAccess(uname, atime)"
      " SELECT uname, max(mtime) FROM ("
      "    SELECT uname, mtime FROM accesslog WHERE success"
      "    UNION ALL"
      "    SELECT login AS uname, rcvfrom.mtime AS mtime"
      "      FROM rcvfrom JOIN user USING(uid))"
      " GROUP BY 1;"
    );
  }
  if( zWith && zWith[0] ){
    zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
  }else{
    zWith = "";
  }
  db_prepare(&s,
     "SELECT uid, login, cap, info, date(mtime,'unixepoch'),"
     "       lower(login) AS sortkey, "
     "       CASE WHEN info LIKE '%%expires 20%%'"
             "    THEN substr(info,instr(lower(info),'expires')+8,10)"
             "    END AS exp,"
             "atime"
     "  FROM user LEFT JOIN lastAccess ON login=uname"
     " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
     " ORDER BY sortkey", zWith/*safe-for-%s*/
  );
  rNow = db_double(0.0, "SELECT julianday('now');");
  while( db_step(&s)==SQLITE_ROW ){
    int uid = db_column_int(&s, 0);
    const char *zLogin = db_column_text(&s, 1);
    const char *zCap = db_column_text(&s, 2);
    const char *zInfo = db_column_text(&s, 3);
    const char *zDate = db_column_text(&s, 4);
    const char *zSortKey = db_column_text(&s,5);
    const char *zExp = db_column_text(&s,6);
    double rATime = db_column_double(&s,7);
    char *zAge = 0;
    if( rATime>0.0 ){
      zAge = human_readable_age(rNow - rATime);
    }
    @ <tr>
    @ <td data-sortkey='%h(zSortKey)'>\
    @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
    @ <td>%h(zCap)
    @ <td>%h(zInfo)
    @ <td>%h(zDate?zDate:"")
    @ <td>%h(zExp?zExp:"")
    @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
  style_table_sorter();
  style_footer();
}

/*
** WEBPAGE: setup_ulist_notes
**
** A documentation page showing notes about user configuration.  This
** information used to be a side-bar on the user list page, but has been
** factored out for improved presentation.
*/
void setup_ulist_notes(void){
  style_header("User Configuration Notes");
  @ <h1>User Configuration Notes:</h1>
  @ <ol>
  @ <li><p>
  @ Every user, logged in or not, inherits the privileges of
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Any human can login as <span class="usertype">anonymous</span> since the
  @ password is clearly displayed on the login page for them to type. The
  @ purpose of requiring anonymous to log in is to prevent access by spiders.
  @ Every logged-in user inherits the combined privileges of
  @ <span class="usertype">anonymous</span> and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Users with privilege <span class="capability">u</span> inherit the combined
  @ privileges of <span class="usertype">reader</span>,
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ Users with privilege <span class="capability">v</span> inherit the combined
  @ privileges of <span class="usertype">developer</span>,
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>The permission flags are as follows:</p>
  capabilities_table(CAPCLASS_ALL);
  @ </li>
  @ </ol>
  style_footer();
}

/*
** WEBPAGE: setup_ucap_list
**
** A documentation page showing the meaning of the various user capabilities
** code letters.
*/
void setup_ucap_list(void){
  style_header("User Capability Codes");
  @ <h1>All capabilities</h1>
  capabilities_table(CAPCLASS_ALL);
  @ <h1>Capabilities associated with checked-in content</h1>
  capabilities_table(CAPCLASS_CODE);
  @ <h1>Capabilities associated with data transfer and sync</h1>
  capabilities_table(CAPCLASS_DATA);
  @ <h1>Capabilities associated with the forum</h1>
  capabilities_table(CAPCLASS_FORUM);
  @ <h1>Capabilities associated with tickets</h1>
  capabilities_table(CAPCLASS_TKT);
  @ <h1>Capabilities associated with wiki</h1>
  capabilities_table(CAPCLASS_WIKI);
  @ <h1>Administrative capabilities</h1>
  capabilities_table(CAPCLASS_SUPER);
  @ <h1>Miscellaneous capabilities</h1>
  capabilities_table(CAPCLASS_OTHER);
  style_footer();
}

/*
** Return true if zPw is a valid password string.  A valid
** password string is:
**
**  (1)  A zero-length string, or
**  (2)  a string that contains a character other than '*'.
*/
static int isValidPwString(const char *zPw){
  if( zPw==0 ) return 0;
  if( zPw[0]==0 ) return 1;
  while( zPw[0]=='*' ){ zPw++; }
  return zPw[0]!=0;
}

/*
** WEBPAGE: setup_uedit
**
** Edit information about a user or create a new user.
** Requires Admin privileges.
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  const char *zGroup;
  const char *zOldLogin;
  int uid, i;
  char *zDeleteVerify = 0;   /* Delete user verification text */
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */
  const char *inherit[128];
  int a[128];
  const char *oa[128];

  /* Must have ADMIN privileges to access this page
  */
  login_check_credentials();
  if( !g.perm.Admin ){ login_needed(0); return; }

  /* Check to see if an ADMIN user is trying to edit a SETUP account.
  ** Don't allow that.
  */
  zId = PD("id", "0");
  uid = atoi(zId);
  if( zId && !g.perm.Setup && uid>0 ){
    char *zOldCaps;
    zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid);
    higherUser = zOldCaps && strchr(zOldCaps,'s');
  }

  if( P("can") ){
    /* User pressed the cancel button */
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }

  /* Check for requests to delete the user */
  if( P("delete") && cgi_csrf_safe(1) ){
    int n;
    if( P("verifydelete") ){
      /* Verified delete user request */
      db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
      cgi_redirect(cgi_referer("setup_ulist"));
      return;
    }
    n = db_int(0, "SELECT count(*) FROM event"
                  " WHERE user=%Q AND objid NOT IN private",
                  P("login"));
    if( n==0 ){
      zDeleteVerify = mprintf("Check this box and press \"Delete User\" again");
    }else{
      zDeleteVerify = mprintf(
        "User \"%s\" has %d or more artifacts in the block-chain. "
        "Delete anyhow?",
        P("login")/*safe-for-%s*/, n);
    }
  }

  /* If we have all the necessary information, write the new or
  ** modified user record.  After writing the user record, redirect
  ** to the page that displays a list of users.
  */
  if( !cgi_all("login","info","pw","apply") ){
    /* need all of the above properties to make a change.  Since one or
    ** more are missing, no-op */
  }else if( higherUser ){
    /* An Admin (a) user cannot edit a Superuser (s) */
  }else if( zDeleteVerify!=0 ){
    /* Need to verify a delete request */
  }else if( !cgi_csrf_safe(1) ){
    /* This might be a cross-site request forgery, so ignore it */
  }else{
    /* We have all the information we need to make the change to the user */
    char c;
    char zCap[70], zNm[4];
    zNm[0] = 'a';
    zNm[2] = 0;
    for(i=0, c='a'; c<='z'; c++){
      zNm[1] = c;
      a[c&0x7f] = (c!='s' || g.perm.Setup) && P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }
    for(c='0'; c<='9'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }
    for(c='A'; c<='Z'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
    }

    zCap[i] = 0;
    zPw = P("pw");
    zLogin = P("login");
    if( strlen(zLogin)==0 ){
      const char *zRef = cgi_referer("setup_ulist");
      style_header("User Creation Error");
      @ <span class="loginError">Empty login not allowed.</span>
      @
      @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
      @ [Bummer]</a></p>
      style_footer();
      return;
    }
    if( isValidPwString(zPw) ){
      zPw = sha1_shared_secret(zPw, zLogin, 0);
    }else{
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }
    zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid);
    if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d",zLogin,uid) ){
      const char *zRef = cgi_referer("setup_ulist");
      style_header("User Creation Error");
      @ <span class="loginError">Login "%h(zLogin)" is already used by
      @ a different user.</span>
      @
      @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
      @ [Bummer]</a></p>
      style_footer();
      return;
    }
    login_verify_csrf_secret();
    db_multi_exec(
       "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
       "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
      uid, zLogin, P("info"), zPw, zCap
    );
    setup_incr_cfgcnt();
    admin_log( "Updated user [%q] with capabilities [%q].",
               zLogin, zCap );
    if( atoi(PD("all","0"))>0 ){
      Blob sql;
      char *zErr = 0;
      blob_zero(&sql);
      if( zOldLogin==0 ){
        blob_appendf(&sql,
          "INSERT INTO user(login)"
          "  SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
          zLogin, zLogin
        );
        zOldLogin = zLogin;
      }
      blob_appendf(&sql,
        "UPDATE user SET login=%Q,"
        "  pw=coalesce(shared_secret(%Q,%Q,"
                "(SELECT value FROM config WHERE name='project-code')),pw),"
        "  info=%Q,"
        "  cap=%Q,"
        "  mtime=now()"
        " WHERE login=%Q;",
        zLogin, P("pw"), zLogin, P("info"), zCap,
        zOldLogin
      );
      login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
      blob_reset(&sql);
      admin_log( "Updated user [%q] in all login groups "
                 "with capabilities [%q].",
                 zLogin, zCap );
      if( zErr ){
        const char *zRef = cgi_referer("setup_ulist");
        style_header("User Change Error");
        admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
        @ <span class="loginError">%h(zErr)</span>
        @
        @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
        @ [Bummer]</a></p>
        style_footer();
        return;
      }
    }
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  for(i='a'; i<='z'; i++) oa[i] = "";
  for(i='0'; i<='9'; i++) oa[i] = "";
  for(i='A'; i<='Z'; i++) oa[i] = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    for(i=0; zCap[i]; i++){
      char c = zCap[i];
      if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
        oa[c&0x7f] = " checked=\"checked\"";
      }
    }
  }

  /* figure out inherited permissions */
  memset((char *)inherit, 0, sizeof(inherit));
  if( fossil_strcmp(zLogin, "developer") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
         "<span class=\"ueditInheritDeveloper\"><sub>[D]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "reader") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
          "<span class=\"ueditInheritReader\"><sub>[R]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "anonymous") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
           "<span class=\"ueditInheritAnonymous\"><sub>[A]</sub></span>";
    }
    free(z2);
  }
  if( fossil_strcmp(zLogin, "nobody") ){
    char *z1, *z2;
    z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'");
    while( z1 && *z1 ){
      inherit[0x7f & *(z1++)] =
           "<span class=\"ueditInheritNobody\"><sub>[N]</sub></span>";
    }
    free(z2);
  }

  /* Begin generating the page
  */
  style_submenu_element("Cancel", "%s", cgi_referer("setup_ulist"));
  if( uid ){
    style_header("Edit User %h", zLogin);
    style_submenu_element("Access Log", "%R/access_log?u=%t", zLogin);
  }else{
    style_header("Add A New User");
  }
  @ <div class="ueditCapBox">
  @ <form action="%s(g.zPath)" method="post"><div>
  login_insert_csrf_secret();
  if( login_is_special(zLogin) ){
    @ <input type="hidden" name="login" value="%s(zLogin)">
    @ <input type="hidden" name="info" value="">
    @ <input type="hidden" name="pw" value="*">
  }
  @ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
  @ <table width="100%%">
  @ <tr>
  @   <td class="usetupEditLabel">User ID:</td>
  if( uid ){
    @   <td>%d(uid) <input type="hidden" name="id" value="%d(uid)" /></td>
  }else{
    @   <td>(new user)<input type="hidden" name="id" value="0" /></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Login:</td>
  if( login_is_special(zLogin) ){
    @    <td><b>%h(zLogin)</b></td>
  }else{
    @   <td><input type="text" name="login" value="%h(zLogin)" /></td>
    @ </tr>
    @ <tr>
    @   <td class="usetupEditLabel">Contact&nbsp;Info:</td>
    @   <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Capabilities:</td>
  @   <td width="100%%">
#define B(x) inherit[x]
  @ <div class="columns" style="column-width:13em;">
  @ <ul style="list-style-type: none;">
  if( g.perm.Setup ){
    @  <li><label><input type="checkbox" name="as"%s(oa['s']) />
    @  Setup%s(B('s'))</label>
  }
  @  <li><label><input type="checkbox" name="aa"%s(oa['a']) />
  @  Admin%s(B('a'))</label>
  @  <li><label><input type="checkbox" name="au"%s(oa['u']) />
  @  Reader%s(B('u'))</label>
  @  <li><label><input type="checkbox" name="av"%s(oa['v']) />
  @  Developer%s(B('v'))</label>
  @  <li><label><input type="checkbox" name="ad"%s(oa['d']) />
  @  Delete%s(B('d'))</label>
  @  <li><label><input type="checkbox" name="ae"%s(oa['e']) />
  @  View-PII%s(B('e'))</label>
  @  <li><label><input type="checkbox" name="ap"%s(oa['p']) />
  @  Password%s(B('p'))</label>
  @  <li><label><input type="checkbox" name="ai"%s(oa['i']) />
  @  Check-In%s(B('i'))</label>
  @  <li><label><input type="checkbox" name="ao"%s(oa['o']) />
  @  Check-Out%s(B('o'))</label>
  @  <li><label><input type="checkbox" name="ah"%s(oa['h']) />
  @  Hyperlinks%s(B('h'))</label>
  @  <li><label><input type="checkbox" name="ab"%s(oa['b']) />
  @  Attachments%s(B('b'))</label>
  @  <li><label><input type="checkbox" name="ag"%s(oa['g']) />
  @  Clone%s(B('g'))</label>
  @  <li><label><input type="checkbox" name="aj"%s(oa['j']) />
  @  Read Wiki%s(B('j'))</label>
  @  <li><label><input type="checkbox" name="af"%s(oa['f']) />
  @  New Wiki%s(B('f'))</label>
  @  <li><label><input type="checkbox" name="am"%s(oa['m']) />
  @  Append Wiki%s(B('m'))</label>
  @  <li><label><input type="checkbox" name="ak"%s(oa['k']) />
  @  Write Wiki%s(B('k'))</label>
  @  <li><label><input type="checkbox" name="al"%s(oa['l']) />
  @  Moderate Wiki%s(B('l'))</label>
  @  <li><label><input type="checkbox" name="ar"%s(oa['r']) />
  @  Read Ticket%s(B('r'))</label>
  @  <li><label><input type="checkbox" name="an"%s(oa['n']) />
  @  New Tickets%s(B('n'))</label>
  @  <li><label><input type="checkbox" name="ac"%s(oa['c']) />
  @  Append To Ticket%s(B('c'))</label>
  @  <li><label><input type="checkbox" name="aw"%s(oa['w']) />
  @  Write Tickets%s(B('w'))</label>
  @  <li><label><input type="checkbox" name="aq"%s(oa['q']) />
  @  Moderate Tickets%s(B('q'))</label>
  @  <li><label><input type="checkbox" name="at"%s(oa['t']) />
  @  Ticket Report%s(B('t'))</label>
  @  <li><label><input type="checkbox" name="ax"%s(oa['x']) />
  @  Private%s(B('x'))</label>
  @  <li><label><input type="checkbox" name="ay"%s(oa['y']) />
  @  Write Unversioned%s(B('y'))</label>
  @  <li><label><input type="checkbox" name="az"%s(oa['z']) />
  @  Download Zip%s(B('z'))</label>
  @  <li><label><input type="checkbox" name="a2"%s(oa['2']) />
  @  Read Forum%s(B('2'))</label>
  @  <li><label><input type="checkbox" name="a3"%s(oa['3']) />
  @  Write Forum%s(B('3'))</label>
  @  <li><label><input type="checkbox" name="a4"%s(oa['4']) />
  @  WriteTrusted Forum%s(B('4'))</label>
  @  <li><label><input type="checkbox" name="a5"%s(oa['5']) />
  @  Moderate Forum%s(B('5'))</label>
  @  <li><label><input type="checkbox" name="a6"%s(oa['6']) />
  @  Supervise Forum%s(B('6'))</label>
  @  <li><label><input type="checkbox" name="a7"%s(oa['7']) />
  @  Email Alerts%s(B('7'))</label>
  @  <li><label><input type="checkbox" name="aA"%s(oa['A']) />
  @  Send Announcements%s(B('A'))</label>
  @  <li><label><input type="checkbox" name="aD"%s(oa['D']) />
  @  Enable Debug%s(B('D'))</label>
  @ </ul></div>
  @   </td>
  @ </tr>
  @ <tr>
  @   <td class="usetupEditLabel">Selected Cap:</td>
  @   <td>
  @     <span id="usetupEditCapability">(missing JS?)</span>
  @     <a href="%R/setup_ucap_list">(key)</a>
  @   </td>
  @ </tr>
  if( !login_is_special(zLogin) ){
    @ <tr>
    @   <td align="right">Password:</td>
    if( zPw[0] ){
      /* Obscure the password for all users */
      @   <td><input type="password" name="pw" value="**********" /></td>
    }else{
      /* Show an empty password as an empty input field */
      @   <td><input type="password" name="pw" value="" /></td>
    }
    @ </tr>
  }
  zGroup = login_group_name();
  if( zGroup ){
    @ <tr>
    @ <td valign="top" align="right">Scope:</td>
    @ <td valign="top">
    @ <input type="radio" name="all" checked value="0">
    @ Apply changes to this repository only.<br />
    @ <input type="radio" name="all" value="1">
    @ Apply changes to all repositories in the "<b>%h(zGroup)</b>"
    @ login group.</td></tr>
  }
  if( !higherUser ){
    if( zDeleteVerify ){
      @ <tr>
      @   <td valign="top" align="right">Verify:</td>
      @   <td><label><input type="checkbox" name="verifydelete">\
      @   Confirm Delete \
      @   <span class="loginError">&larr; %h(zDeleteVerify)</span>
      @   </label></td>
      @ <tr>
    }
    @ <tr>
    @   <td>&nbsp;</td>
    @   <td><input type="submit" name="apply" value="Apply Changes">
    if( !login_is_special(zLogin) ){
      @   <input type="submit" name="delete" value="Delete User">
    }
    @   <input type="submit" name="can" value="Cancel"></td>
    @ </tr>
  }
  @ </table>
  @ </div></form>
  @ </div>
  style_load_one_js_file("useredit.js");
  @ <hr>
  @ <h1>Notes On Privileges And Capabilities:</h1>
  @ <ul>
  if( higherUser ){
    @ <li><p class="missingPriv">
    @ User %h(zLogin) has Setup privileges and you only have Admin privileges
    @ so you are not permitted to make changes to %h(zLogin).
    @ </p></li>
    @
  }
  @ <li><p>
  @ The <span class="capability">Setup</span> user can make arbitrary
  @ configuration changes. An <span class="usertype">Admin</span> user
  @ can add other users and change user privileges
  @ and reset user passwords.  Both automatically get all other privileges
  @ listed below.  Use these two settings with discretion.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritNobody"><sub>N</sub></span>" subscript suffix
  @ indicates the privileges of <span class="usertype">nobody</span> that
  @ are available to all users regardless of whether or not they are logged in.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritAnonymous"><sub>A</sub></span>"
  @ subscript suffix
  @ indicates the privileges of <span class="usertype">anonymous</span> that
  @ are inherited by all logged-in users.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritDeveloper"><sub>D</sub></span>"
  @ subscript suffix indicates the privileges of 
  @ <span class="usertype">developer</span> that
  @ are inherited by all users with the
  @ <span class="capability">Developer</span> privilege.
  @ </p></li>
  @
  @ <li><p>
  @ The "<span class="ueditInheritReader"><sub>R</sub></span>" subscript suffix
  @ indicates the privileges of <span class="usertype">reader</span> that
  @ are inherited by all users with the <span class="capability">Reader</span>
  @ privilege.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Delete</span> privilege give the user the
  @ ability to erase wiki, tickets, and attachments that have been added
  @ by anonymous users.  This capability is intended for deletion of spam.
  @ The delete capability is only in effect for 24 hours after the item
  @ is first posted.  The <span class="usertype">Setup</span> user can
  @ delete anything at any time.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Hyperlinks</span> privilege allows a user
  @ to see most hyperlinks. This is recommended ON for most logged-in users
  @ but OFF for user "nobody" to avoid problems with spiders trying to walk
  @ every diff and annotation of every historical check-in and file.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Zip</span> privilege allows a user to
  @ see the "download as ZIP"
  @ hyperlink and permits access to the <tt>/zip</tt> page.  This allows
  @ users to download ZIP archives without granting other rights like
  @ <span class="capability">Read</span> or
  @ <span class="capability">Hyperlink</span>.  The "z" privilege is recommended
  @ for user <span class="usertype">nobody</span> so that automatic package
  @ downloaders can obtain the sources without going through the login
  @ procedure.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Check-in</span> privilege allows remote
  @ users to "push". The <span class="capability">Check-out</span> privilege
  @ allows remote users to "pull". The <span class="capability">Clone</span>
  @ privilege allows remote users to "clone".
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Read Wiki</span>,
  @ <span class="capability">New Wiki</span>,
  @ <span class="capability">Append Wiki</span>, and
  @ <b>Write Wiki</b> privileges control access to wiki pages.  The
  @ <span class="capability">Read Ticket</span>,
  @ <span class="capability">New Ticket</span>,
  @ <span class="capability">Append Ticket</span>, and
  @ <span class="capability">Write Ticket</span> privileges control access
  @ to trouble tickets.
  @ The <span class="capability">Ticket Report</span> privilege allows
  @ the user to create or edit ticket report formats.
  @ </p></li>
  @
  @ <li><p>
  @ Users with the <span class="capability">Password</span> privilege
  @ are allowed to change their own password.  Recommended ON for most
  @ users but OFF for special users <span class="usertype">developer</span>,
  @ <span class="usertype">anonymous</span>,
  @ and <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">View-PII</span> privilege allows the display
  @ of personally-identifiable information information such as the
  @ email address of users and contact
  @ information on tickets. Recommended OFF for
  @ <span class="usertype">anonymous</span> and for
  @ <span class="usertype">nobody</span> but ON for
  @ <span class="usertype">developer</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="capability">Attachment</span> privilege is needed in
  @ order to add attachments to tickets or wiki.  Write privilege on the
  @ ticket or wiki is also required.
  @ </p></li>
  @
  @ <li><p>
  @ Login is prohibited if the password is an empty string.
  @ </p></li>
  @ </ul>
  @
  @ <h2>Special Logins</h2>
  @
  @ <ul>
  @ <li><p>
  @ No login is required for user <span class="usertype">nobody</span>. The
  @ capabilities of the <span class="usertype">nobody</span> user are
  @ inherited by all users, regardless of whether or not they are logged in.
  @ To disable universal access to the repository, make sure that the
  @ <span class="usertype">nobody</span> user has no capabilities
  @ enabled. The password for <span class="usertype">nobody</span> is ignored.
  @ </p></li>
  @
  @ <li><p>
  @ Login is required for user <span class="usertype">anonymous</span> but the
  @ password is displayed on the login screen beside the password entry box
  @ so anybody who can read should be able to login as anonymous.
  @ On the other hand, spiders and web-crawlers will typically not
  @ be able to login.  Set the capabilities of the
  @ <span class="usertype">anonymous</span>
  @ user to things that you want any human to be able to do, but not any
  @ spider.  Every other logged-in user inherits the privileges of
  @ <span class="usertype">anonymous</span>.
  @ </p></li>
  @
  @ <li><p>
  @ The <span class="usertype">developer</span> user is intended as a template
  @ for trusted users with check-in privileges. When adding new trusted users,
  @ simply select the <span class="capability">developer</span> privilege to
  @ cause the new user to inherit all privileges of the
  @ <span class="usertype">developer</span>
  @ user.  Similarly, the <span class="usertype">reader</span> user is a
  @ template for users who are allowed more access than
  @ <span class="usertype">anonymous</span>,
  @ but less than a <span class="usertype">developer</span>.
  @ </p></li>
  @ </ul>
  style_footer();
}

Changes to src/shell.c.

93
94
95
96
97
98
99

100
101
102
103
104
105


106
107
108
109
110
111
112
# if !defined(__RTP__) && !defined(_WRS_KERNEL)
#  include <pwd.h>
# endif
#endif
#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__)
# include <unistd.h>
# include <dirent.h>

# if defined(__MINGW32__)
#  define DIRENT dirent
#  ifndef S_ISLNK
#   define S_ISLNK(mode) (0)
#  endif
# endif


#endif
#include <sys/types.h>
#include <sys/stat.h>

#if HAVE_READLINE
# include <readline/readline.h>
# include <readline/history.h>







>






>
>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# if !defined(__RTP__) && !defined(_WRS_KERNEL)
#  include <pwd.h>
# endif
#endif
#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__)
# include <unistd.h>
# include <dirent.h>
# define GETPID getpid
# if defined(__MINGW32__)
#  define DIRENT dirent
#  ifndef S_ISLNK
#   define S_ISLNK(mode) (0)
#  endif
# endif
#else
# define GETPID (int)GetCurrentProcessId
#endif
#include <sys/types.h>
#include <sys/stat.h>

#if HAVE_READLINE
# include <readline/readline.h>
# include <readline/history.h>
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
  int nLine = zLine==0 ? 0 : 100;
  int n = 0;

  while( 1 ){
    if( n+100>nLine ){
      nLine = nLine*2 + 100;
      zLine = realloc(zLine, nLine);
      if( zLine==0 ) return 0;
    }
    if( fgets(&zLine[n], nLine - n, in)==0 ){
      if( n==0 ){
        free(zLine);
        return 0;
      }
      zLine[n] = 0;







|







582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
  int nLine = zLine==0 ? 0 : 100;
  int n = 0;

  while( 1 ){
    if( n+100>nLine ){
      nLine = nLine*2 + 100;
      zLine = realloc(zLine, nLine);
      if( zLine==0 ) shell_out_of_memory();
    }
    if( fgets(&zLine[n], nLine - n, in)==0 ){
      if( n==0 ){
        free(zLine);
        return 0;
      }
      zLine[n] = 0;
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
  ** multi-byte characterset characters into UTF-8. */
  if( stdin_is_interactive && in==stdin ){
    char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
    if( zTrans ){
      int nTrans = strlen30(zTrans)+1;
      if( nTrans>nLine ){
        zLine = realloc(zLine, nTrans);
        if( zLine==0 ){
          sqlite3_free(zTrans);
          return 0;
        }
      }
      memcpy(zLine, zTrans, nTrans);
      sqlite3_free(zTrans);
    }
  }
#endif /* defined(_WIN32) || defined(WIN32) */
  return zLine;







|
<
<
<







609
610
611
612
613
614
615
616



617
618
619
620
621
622
623
  ** multi-byte characterset characters into UTF-8. */
  if( stdin_is_interactive && in==stdin ){
    char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
    if( zTrans ){
      int nTrans = strlen30(zTrans)+1;
      if( nTrans>nLine ){
        zLine = realloc(zLine, nTrans);
        if( zLine==0 ) shell_out_of_memory();



      }
      memcpy(zLine, zTrans, nTrans);
      sqlite3_free(zTrans);
    }
  }
#endif /* defined(_WIN32) || defined(WIN32) */
  return zLine;
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
      if( zAppend[i]==quote ) len++;
    }
  }

  if( p->n+len>=p->nAlloc ){
    p->nAlloc = p->nAlloc*2 + len + 20;
    p->z = realloc(p->z, p->nAlloc);
    if( p->z==0 ){
      memset(p, 0, sizeof(*p));
      return;
    }
  }

  if( quote ){
    char *zCsr = p->z+p->n;
    *zCsr++ = quote;
    for(i=0; i<nAppend; i++){
      *zCsr++ = zAppend[i];







|
<
<
<







756
757
758
759
760
761
762
763



764
765
766
767
768
769
770
      if( zAppend[i]==quote ) len++;
    }
  }

  if( p->n+len>=p->nAlloc ){
    p->nAlloc = p->nAlloc*2 + len + 20;
    p->z = realloc(p->z, p->nAlloc);
    if( p->z==0 ) shell_out_of_memory();



  }

  if( quote ){
    char *zCsr = p->z+p->n;
    *zCsr++ = quote;
    for(i=0; i<nAppend; i++){
      *zCsr++ = zAppend[i];
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
){
  HANDLE hFindFile;
  WIN32_FIND_DATAW fd;
  LPWSTR zUnicodeName;
  extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
  zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
  if( zUnicodeName ){
    memset(&fd, 0, sizeof(WIN32_FIND_DATA));
    hFindFile = FindFirstFileW(zUnicodeName, &fd);
    if( hFindFile!=NULL ){
      pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
      pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
      pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
      FindClose(hFindFile);
    }







|







2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
){
  HANDLE hFindFile;
  WIN32_FIND_DATAW fd;
  LPWSTR zUnicodeName;
  extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*);
  zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath);
  if( zUnicodeName ){
    memset(&fd, 0, sizeof(WIN32_FIND_DATAW));
    hFindFile = FindFirstFileW(zUnicodeName, &fd);
    if( hFindFile!=NULL ){
      pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime);
      pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime);
      pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime);
      FindClose(hFindFile);
    }
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
  completionCursorReset(pCur);
  if( idxNum & 1 ){
    pCur->nPrefix = sqlite3_value_bytes(argv[iArg]);
    if( pCur->nPrefix>0 ){
      pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg]));
      if( pCur->zPrefix==0 ) return SQLITE_NOMEM;
    }
    iArg++;
  }
  if( idxNum & 2 ){
    pCur->nLine = sqlite3_value_bytes(argv[iArg]);
    if( pCur->nLine>0 ){
      pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg]));
      if( pCur->zLine==0 ) return SQLITE_NOMEM;
    }
    iArg++;
  }
  if( pCur->zLine!=0 && pCur->zPrefix==0 ){
    int i = pCur->nLine;
    while( i>0 && (isalnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){
      i--;
    }
    pCur->nPrefix = pCur->nLine - i;







|







<







3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325

3326
3327
3328
3329
3330
3331
3332
  completionCursorReset(pCur);
  if( idxNum & 1 ){
    pCur->nPrefix = sqlite3_value_bytes(argv[iArg]);
    if( pCur->nPrefix>0 ){
      pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg]));
      if( pCur->zPrefix==0 ) return SQLITE_NOMEM;
    }
    iArg = 1;
  }
  if( idxNum & 2 ){
    pCur->nLine = sqlite3_value_bytes(argv[iArg]);
    if( pCur->nLine>0 ){
      pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg]));
      if( pCur->zLine==0 ) return SQLITE_NOMEM;
    }

  }
  if( pCur->zLine!=0 && pCur->zPrefix==0 ){
    int i = pCur->nLine;
    while( i>0 && (isalnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){
      i--;
    }
    pCur->nPrefix = pCur->nLine - i;
8685
8686
8687
8688
8689
8690
8691

8692
8693
8694
8695
8696
8697
8698
){
  const char *zEditor;
  char *zTempFile = 0;
  sqlite3 *db;
  char *zCmd = 0;
  int bBin;
  int rc;

  FILE *f = 0;
  sqlite3_int64 sz;
  sqlite3_int64 x;
  unsigned char *p = 0;

  if( argc==2 ){
    zEditor = (const char*)sqlite3_value_text(argv[1]);







>







8681
8682
8683
8684
8685
8686
8687
8688
8689
8690
8691
8692
8693
8694
8695
){
  const char *zEditor;
  char *zTempFile = 0;
  sqlite3 *db;
  char *zCmd = 0;
  int bBin;
  int rc;
  int hasCRNL = 0;
  FILE *f = 0;
  sqlite3_int64 sz;
  sqlite3_int64 x;
  unsigned char *p = 0;

  if( argc==2 ){
    zEditor = (const char*)sqlite3_value_text(argv[1]);
8716
8717
8718
8719
8720
8721
8722


8723
8724
8725
8726
8727
8728
8729
8730
8731



8732
8733
8734
8735
8736
8737
8738
8739
8740
8741
8742
8743
8744
8745
8746
8747
8748
8749
8750
8751
8752
8753
8754
8755
8756
8757
8758
8759
8760
8761
8762
8763
8764
8765
8766
8767
8768
8769
8770
8771
8772
8773
8774
8775
8776
8777
8778
8779














8780
8781
8782
8783
8784
8785
8786
    zTempFile = sqlite3_mprintf("temp%llx", r);
    if( zTempFile==0 ){
      sqlite3_result_error_nomem(context);
      return;
    }
  }
  bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB;


  f = fopen(zTempFile, bBin ? "wb" : "w");
  if( f==0 ){
    sqlite3_result_error(context, "edit() cannot open temp file", -1);
    goto edit_func_end;
  }
  sz = sqlite3_value_bytes(argv[0]);
  if( bBin ){
    x = fwrite(sqlite3_value_blob(argv[0]), 1, sz, f);
  }else{



    x = fwrite(sqlite3_value_text(argv[0]), 1, sz, f);
  }
  fclose(f);
  f = 0;
  if( x!=sz ){
    sqlite3_result_error(context, "edit() could not write the whole file", -1);
    goto edit_func_end;
  }
  zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile);
  if( zCmd==0 ){
    sqlite3_result_error_nomem(context);
    goto edit_func_end;
  }
  rc = system(zCmd);
  sqlite3_free(zCmd);
  if( rc ){
    sqlite3_result_error(context, "EDITOR returned non-zero", -1);
    goto edit_func_end;
  }
  f = fopen(zTempFile, bBin ? "rb" : "r");
  if( f==0 ){
    sqlite3_result_error(context,
      "edit() cannot reopen temp file after edit", -1);
    goto edit_func_end;
  }
  fseek(f, 0, SEEK_END);
  sz = ftell(f);
  rewind(f);
  p = sqlite3_malloc64( sz+(bBin==0) );
  if( p==0 ){
    sqlite3_result_error_nomem(context);
    goto edit_func_end;
  }
  if( bBin ){
    x = fread(p, 1, sz, f);
  }else{
    x = fread(p, 1, sz, f);
    p[sz] = 0;
  }
  fclose(f);
  f = 0;
  if( x!=sz ){
    sqlite3_result_error(context, "could not read back the whole file", -1);
    goto edit_func_end;
  }
  if( bBin ){
    sqlite3_result_blob64(context, p, sz, sqlite3_free);
  }else{














    sqlite3_result_text64(context, (const char*)p, sz,
                          sqlite3_free, SQLITE_UTF8);
  }
  p = 0;

edit_func_end:
  if( f ) fclose(f);







>
>









>
>
>



















|













<
|
<
<
<
<









>
>
>
>
>
>
>
>
>
>
>
>
>
>







8713
8714
8715
8716
8717
8718
8719
8720
8721
8722
8723
8724
8725
8726
8727
8728
8729
8730
8731
8732
8733
8734
8735
8736
8737
8738
8739
8740
8741
8742
8743
8744
8745
8746
8747
8748
8749
8750
8751
8752
8753
8754
8755
8756
8757
8758
8759
8760
8761
8762
8763
8764
8765
8766

8767




8768
8769
8770
8771
8772
8773
8774
8775
8776
8777
8778
8779
8780
8781
8782
8783
8784
8785
8786
8787
8788
8789
8790
8791
8792
8793
8794
8795
8796
8797
    zTempFile = sqlite3_mprintf("temp%llx", r);
    if( zTempFile==0 ){
      sqlite3_result_error_nomem(context);
      return;
    }
  }
  bBin = sqlite3_value_type(argv[0])==SQLITE_BLOB;
  /* When writing the file to be edited, do \n to \r\n conversions on systems
  ** that want \r\n line endings */
  f = fopen(zTempFile, bBin ? "wb" : "w");
  if( f==0 ){
    sqlite3_result_error(context, "edit() cannot open temp file", -1);
    goto edit_func_end;
  }
  sz = sqlite3_value_bytes(argv[0]);
  if( bBin ){
    x = fwrite(sqlite3_value_blob(argv[0]), 1, sz, f);
  }else{
    const char *z = (const char*)sqlite3_value_text(argv[0]);
    /* Remember whether or not the value originally contained \r\n */
    if( z && strstr(z,"\r\n")!=0 ) hasCRNL = 1;
    x = fwrite(sqlite3_value_text(argv[0]), 1, sz, f);
  }
  fclose(f);
  f = 0;
  if( x!=sz ){
    sqlite3_result_error(context, "edit() could not write the whole file", -1);
    goto edit_func_end;
  }
  zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile);
  if( zCmd==0 ){
    sqlite3_result_error_nomem(context);
    goto edit_func_end;
  }
  rc = system(zCmd);
  sqlite3_free(zCmd);
  if( rc ){
    sqlite3_result_error(context, "EDITOR returned non-zero", -1);
    goto edit_func_end;
  }
  f = fopen(zTempFile, "rb");
  if( f==0 ){
    sqlite3_result_error(context,
      "edit() cannot reopen temp file after edit", -1);
    goto edit_func_end;
  }
  fseek(f, 0, SEEK_END);
  sz = ftell(f);
  rewind(f);
  p = sqlite3_malloc64( sz+(bBin==0) );
  if( p==0 ){
    sqlite3_result_error_nomem(context);
    goto edit_func_end;
  }

  x = fread(p, 1, sz, f);




  fclose(f);
  f = 0;
  if( x!=sz ){
    sqlite3_result_error(context, "could not read back the whole file", -1);
    goto edit_func_end;
  }
  if( bBin ){
    sqlite3_result_blob64(context, p, sz, sqlite3_free);
  }else{
    int i, j;
    if( hasCRNL ){
      /* If the original contains \r\n then do no conversions back to \n */
      j = sz;
    }else{
      /* If the file did not originally contain \r\n then convert any new
      ** \r\n back into \n */
      for(i=j=0; i<sz; i++){
        if( p[i]=='\r' && p[i+1]=='\n' ) i++;
        p[j++] = p[i];
      }
      sz = j;
      p[sz] = 0;
    } 
    sqlite3_result_text64(context, (const char*)p, sz,
                          sqlite3_free, SQLITE_UTF8);
  }
  p = 0;

edit_func_end:
  if( f ) fclose(f);
10123
10124
10125
10126
10127
10128
10129

10130

10131
10132
10133
10134
10135
10136
10137
            sqlite3_reset(pSql);
            return;
          }
        }
      }
      nAlloc += 100;
      p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int));

      abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));

    }
    abYield[iOp] = str_in_array(zOp, azYield);
    p->aiIndent[iOp] = 0;
    p->nIndent = iOp+1;

    if( str_in_array(zOp, azNext) ){
      for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2;







>

>







10134
10135
10136
10137
10138
10139
10140
10141
10142
10143
10144
10145
10146
10147
10148
10149
10150
            sqlite3_reset(pSql);
            return;
          }
        }
      }
      nAlloc += 100;
      p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int));
      if( p->aiIndent==0 ) shell_out_of_memory();
      abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
      if( abYield==0 ) shell_out_of_memory();
    }
    abYield[iOp] = str_in_array(zOp, azYield);
    p->aiIndent[iOp] = 0;
    p->nIndent = iOp+1;

    if( str_in_array(zOp, azNext) ){
      for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2;
15845
15846
15847
15848
15849
15850
15851

















15852
15853
15854
15855
15856
15857
15858
  int argcToFree = 0;
#endif

  setBinaryMode(stdin, 0);
  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  stdin_is_interactive = isatty(0);
  stdout_is_console = isatty(1);


















#if USE_SYSTEM_SQLITE+0!=1
  if( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
    utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
            sqlite3_sourceid(), SQLITE_SOURCE_ID);
    exit(1);
  }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







15858
15859
15860
15861
15862
15863
15864
15865
15866
15867
15868
15869
15870
15871
15872
15873
15874
15875
15876
15877
15878
15879
15880
15881
15882
15883
15884
15885
15886
15887
15888
  int argcToFree = 0;
#endif

  setBinaryMode(stdin, 0);
  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  stdin_is_interactive = isatty(0);
  stdout_is_console = isatty(1);

#if !defined(_WIN32_WCE)
  if( getenv("SQLITE_DEBUG_BREAK") ){
    if( isatty(0) && isatty(2) ){
      fprintf(stderr,
          "attach debugger to process %d and press any key to continue.\n",
          GETPID());
      fgetc(stdin);
    }else{
#if defined(_WIN32) || defined(WIN32)
      DebugBreak();
#elif defined(SIGTRAP)
      raise(SIGTRAP);
#endif
    }
  }
#endif

#if USE_SYSTEM_SQLITE+0!=1
  if( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
    utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
            sqlite3_sourceid(), SQLITE_SOURCE_ID);
    exit(1);
  }

Changes to src/sitemap.c.

27
28
29
30
31
32
33













34







35

36
37
38


39
40
41
42
43



44
45



46




47

48


49


50

51
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
67
68
69
70

71
72
73
74

75
76
77
78
79
80
81
82
83
84
85

86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107



108







109

110




111

112























113
114
115
116
117
118
119
120
121
122
123
124
125
126
127


128
129
130
131
132
133
134
** List some of the web pages offered by the Fossil web engine.  This
** page is intended as a supplement to the menu bar on the main screen.
** That is, this page is designed to hold links that are omitted from
** the main menu due to lack of space.
*/
void sitemap_page(void){
  int srchFlags;













  login_check_credentials();







  srchFlags = search_restrict(SRCH_ALL);

  style_header("Site Map");
  style_adunit_config(ADUNIT_RIGHT_OK);
#if 0


  @ <p>
  @ The following links are just a few of the many web-pages available for
  @ this Fossil repository:
  @ </p>
  @



#endif
  @ <ul>



  @ <li>%z(href("%R/home"))Home Page</a>




  if( srchFlags & SRCH_DOC ){

    @   <ul>


    @   <li>%z(href("%R/docsrch"))Search Project Documentation</a></li>


    @   </ul>

  }
  @ </li>
  if( g.perm.Read ){
    @ <li>%z(href("%R/tree"))File Browser</a></li>
    @   <ul>
    @   <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
    @        Trunk Check-in</a></li>
    @   <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
    @   <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>

    @ </ul>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/timeline?n=200"))Project Timeline</a></li>
    @ <ul>
    @   <li>%z(href("%R/reports"))Activity Reports</a></li>
    @   <li>%z(href("%R/timeline?n=all&namechng"))File name changes</a></li>
    @   <li>%z(href("%R/timeline?n=all&forks"))Forks</a></li>
    @   <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10
    @       check-ins</a></li>
    @ </ul>

  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/brlist"))Branches</a></li>
    @ <ul>

    @   <li>%z(href("%R/leaves"))Leaf Check-ins</a></li>
    @   <li>%z(href("%R/taglist"))List of Tags</a></li>
    @ </ul>
    @ </li>
  }
  if( g.perm.RdWiki ){
    @ <li>%z(href("%R/wikihelp"))Wiki</a>
    @   <ul>
    if( srchFlags & SRCH_WIKI ){
      @     <li>%z(href("%R/wikisrch"))Wiki Search</a></li>
    }

    @     <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li>

    @     <li>%z(href("%R/timeline?y=w"))Recent activity</a></li>
    @     <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li>
    @     <li>%z(href("%R/md_rules"))Markdown Formatting Rules</a></li>
    @     <li>%z(href("%R/wiki?name=Sandbox"))Sandbox</a></li>
    @     <li>%z(href("%R/attachlist"))List of Attachments</a></li>
    @   </ul>
    @ </li>
  }
  if( g.perm.RdTkt ){
    @ <li>%z(href("%R/reportlist"))Tickets</a>
    @   <ul>
    if( srchFlags & SRCH_TKT ){
      @   <li>%z(href("%R/tktsrch"))Ticket Search</a></li>
    }
    @   <li>%z(href("%R/timeline?y=t"))Recent activity</a></li>
    @   <li>%z(href("%R/attachlist"))List of Attachments</a></li>
    @   </ul>
    @ </li>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/uvlist"))Unversioned Files</a>



  }







  if( srchFlags ){

    @ <li>%z(href("%R/search"))Full-Text Search</a></li>




  }

  @ <li>%z(href("%R/login"))Login/Logout/Change Password</a></li>























  if( g.perm.Read ){
    @ <li>%z(href("%R/stat"))Repository Status</a>
    @   <ul>
    @   <li>%z(href("%R/hash-collisions"))Collisions on hash prefixes</a></li>
    if( g.perm.Admin ){
      @   <li>%z(href("%R/urllist"))List of URLs used to access
      @       this repository</a></li>
    }
    @   <li>%z(href("%R/bloblist"))List of Artifacts</a></li>
    @   <li>%z(href("%R/timewarps"))List of "Timewarp" Check-ins</a></li>
    @   </ul>
    @ </li>
  }
  @ <li>On-line Documentation
  @   <ul>


  @   <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li>
  @   <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li>
  @   <li>%z(href("%R/mimetype_list"))Filename suffix to mimetype map</a></li>
  @   </ul></li>
  if( g.perm.Admin ){
    @ <li>%z(href("%R/setup"))Administration Pages</a>
    @   <ul>







>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>

>
|
|
<
>
>
|
<
<
<
<
>
>
>
|
|
>
>
>
|
>
>
>
>

>
|
>
>
|
>
>
|
>



|





>



|







>


|

>

<



<
<
<
|
|
|
>
|
>
|
<
<
<
<
|













|
|
>
>
>
|
>
>
>
>
>
>
>
|
>
|
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













|

>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

59
60
61




62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

113
114
115



116
117
118
119
120
121
122




123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
** List some of the web pages offered by the Fossil web engine.  This
** page is intended as a supplement to the menu bar on the main screen.
** That is, this page is designed to hold links that are omitted from
** the main menu due to lack of space.
*/
void sitemap_page(void){
  int srchFlags;
  int inSublist = 0;
  int i;
  int isPopup = 0;         /* This is an XMLHttpRequest() for /sitemap */
  const struct {
    const char *zTitle;
    const char *zProperty;
  } aExtra[] = {
    { "Documentation",  "sitemap-docidx" },
    { "Download",       "sitemap-download" },
    { "License",        "sitemap-license" },
    { "Contact",        "sitemap-contact" },
  };

  login_check_credentials();
  if( P("popup")!=0 && cgi_csrf_safe(1) ){
    /* If this is a POST from the same origin with the popup=1 parameter,
    ** then disable anti-robot defenses */
    isPopup = 1;
    g.perm.Hyperlink = 1;
    g.javascriptHyperlink = 0;
  }
  srchFlags = search_restrict(SRCH_ALL);
  if( !isPopup ){
    style_header("Site Map");
    style_adunit_config(ADUNIT_RIGHT_OK);

  }
  @ <ul id="sitemap" class="columns" style="column-width:20em">
  @ <li>%z(href("%R/home"))Home Page</a>




  for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){
    char *z = db_get(aExtra[i].zProperty,0);
    if( z==0 || z[0]==0 ) continue;
    if( !inSublist ){
      @ <ul>
      inSublist = 1;
    }
    if( z[0]=='/' ){
      @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li>
    }else{
      @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li>
    }
  }
  if( srchFlags & SRCH_DOC ){
    if( !inSublist ){
      @ <ul>
      inSublist = 1;
    }
    @ <li>%z(href("%R/docsrch"))Documentation Search</a></li>
  }
  if( inSublist ){
    @ </ul>
    inSublist = 0;    
  }
  @ </li>
  if( g.perm.Read ){
    @ <li>%z(href("%R/tree"))File Browser</a>
    @   <ul>
    @   <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
    @        Trunk Check-in</a></li>
    @   <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
    @   <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
    @   <li>%z(href("%R/uvlist"))Unversioned Files</a>
    @ </ul>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/timeline"))Project Timeline</a>
    @ <ul>
    @   <li>%z(href("%R/reports"))Activity Reports</a></li>
    @   <li>%z(href("%R/timeline?n=all&namechng"))File name changes</a></li>
    @   <li>%z(href("%R/timeline?n=all&forks"))Forks</a></li>
    @   <li>%z(href("%R/timeline?a=1970-01-01&y=ci&n=10"))First 10
    @       check-ins</a></li>
    @ </ul>
    @ </li>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/brlist"))Branches</a>
    @ <ul>
    @   <li>%z(href("%R/taglist"))Tags</a></li>
    @   <li>%z(href("%R/leaves"))Leaf Check-ins</a></li>

    @ </ul>
    @ </li>
  }



  if( srchFlags ){
    @ <li>%z(href("%R/search"))Search</a></li>
  }
  if( g.perm.RdForum ){
    @ <li>%z(href("%R/forum"))Forum</a>
    @ <ul>
    @   <li>%z(href("%R/timeline?y=f"))Recent activity</a></li>




    @ </ul>
    @ </li>
  }
  if( g.perm.RdTkt ){
    @ <li>%z(href("%R/reportlist"))Tickets</a>
    @   <ul>
    if( srchFlags & SRCH_TKT ){
      @   <li>%z(href("%R/tktsrch"))Ticket Search</a></li>
    }
    @   <li>%z(href("%R/timeline?y=t"))Recent activity</a></li>
    @   <li>%z(href("%R/attachlist"))List of Attachments</a></li>
    @   </ul>
    @ </li>
  }
  if( g.perm.RdWiki ){
    @ <li>%z(href("%R/wikihelp"))Wiki</a>
    @   <ul>
    if( srchFlags & SRCH_WIKI ){
      @     <li>%z(href("%R/wikisrch"))Wiki Search</a></li>
    }
    @     <li>%z(href("%R/wcontent"))List of Wiki Pages</a></li>
    @     <li>%z(href("%R/timeline?y=w"))Recent activity</a></li>
    @     <li>%z(href("%R/wiki?name=Sandbox"))Sandbox</a></li>
    @     <li>%z(href("%R/attachlist"))List of Attachments</a></li>
    @   </ul>
    @ </li>
  }

  if( !g.zLogin ){
    @ <li>%z(href("%R/login"))Login</a>
    if( login_self_register_available(0) ){
       @ <ul>
       @ <li>%z(href("%R/register"))Create a new account</a></li>
       inSublist = 1;
    }
  }else {
    @ <li>%z(href("%R/logout"))Logout</a>
    if( g.perm.Password ){
      @ <ul>
      @ <li>%z(href("%R/logout"))Change Password</a></li>
      inSublist = 1;
    }
  }
  if( alert_enabled() && g.perm.EmailAlert ){
    if( !inSublist ){
      inSublist = 1;
      @ <ul>
    }
    if( login_is_individual() ){
      @ <li>%z(href("%R/alerts"))Email Alerts</a></li>
    }else{
      @ <li>%z(href("%R/subscribe"))Subscribe to Email Alerts</a></li>
    }
  }
  if( inSublist ){
    @ </ul>
    inSublist = 0;
  }
  @ </li>

  if( g.perm.Read ){
    @ <li>%z(href("%R/stat"))Repository Status</a>
    @   <ul>
    @   <li>%z(href("%R/hash-collisions"))Collisions on hash prefixes</a></li>
    if( g.perm.Admin ){
      @   <li>%z(href("%R/urllist"))List of URLs used to access
      @       this repository</a></li>
    }
    @   <li>%z(href("%R/bloblist"))List of Artifacts</a></li>
    @   <li>%z(href("%R/timewarps"))List of "Timewarp" Check-ins</a></li>
    @   </ul>
    @ </li>
  }
  @ <li>Help
  @   <ul>
  @   <li>%z(href("%R/wiki_rules"))Wiki Formatting Rules</a></li>
  @   <li>%z(href("%R/md_rules"))Markdown Formatting Rules</a></li>
  @   <li>%z(href("%R/help"))List of All Commands and Web Pages</a></li>
  @   <li>%z(href("%R/test-all-help"))All "help" text on a single page</a></li>
  @   <li>%z(href("%R/mimetype_list"))Filename suffix to mimetype map</a></li>
  @   </ul></li>
  if( g.perm.Admin ){
    @ <li>%z(href("%R/setup"))Administration Pages</a>
    @   <ul>
145
146
147
148
149
150
151
152

153
154

  if( g.perm.Read ){
    @   <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
  }
  @   <li>%z(href("%R/hash-color-test"))Page to experiment with the automatic
  @       colors assigned to branch names</a>
  @   <li>%z(href("%R/test-captcha"))Random ASCII-art Captcha image</a></li>
  @   </ul></li>
  @ </ul></li>

  style_footer();
}








|
>
|
|
>
217
218
219
220
221
222
223
224
225
226
227
228
  if( g.perm.Read ){
    @   <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
  }
  @   <li>%z(href("%R/hash-color-test"))Page to experiment with the automatic
  @       colors assigned to branch names</a>
  @   <li>%z(href("%R/test-captcha"))Random ASCII-art Captcha image</a></li>
  @   </ul></li>
  @ </ul>
  if( !isPopup ){
    style_footer();
  }
}

Changes to src/skins.c.

53
54
55
56
57
58
59
60
61
62


63
64
65
66
67
68
69
  { "Black & White, Menu on Left",       "black_and_white",   0 },
  { "Plain Gray, No Logo",               "plain_gray",        0 },
  { "Khaki, No Logo",                    "khaki",             0 },
  { "Ardoise",                           "ardoise",           0 },
};

/*
** A skin consists of four "files" named here:
*/
static const char *azSkinFile[] = { "css", "header", "footer", "details" };



/*
** Alternative skins can be specified in the CGI script or by options
** on the "http", "ui", and "server" commands.  The alternative skin
** name must be one of the aBuiltinSkin[].zLabel names.  If there is
** a match, that alternative is used.
**







|

|
>
>







53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  { "Black & White, Menu on Left",       "black_and_white",   0 },
  { "Plain Gray, No Logo",               "plain_gray",        0 },
  { "Khaki, No Logo",                    "khaki",             0 },
  { "Ardoise",                           "ardoise",           0 },
};

/*
** A skin consists of five "files" named here:
*/
static const char *azSkinFile[] = { 
  "css", "header", "footer", "details", "js"
};

/*
** Alternative skins can be specified in the CGI script or by options
** on the "http", "ui", and "server" commands.  The alternative skin
** name must be one of the aBuiltinSkin[].zLabel names.  If there is
** a match, that alternative is used.
**
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
  iDraftSkin = i;
}

/*
** The following routines return the various components of the skin
** that should be used for the current run.
**
** zWhat is one of:  "css", "header", "footer", "details".
*/
const char *skin_get(const char *zWhat){
  const char *zOut;
  char *z;
  if( iDraftSkin ){
    z = mprintf("draft%d-%s", iDraftSkin, zWhat);
    zOut = db_get(z, 0);







|







149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
  iDraftSkin = i;
}

/*
** The following routines return the various components of the skin
** that should be used for the current run.
**
** zWhat is one of:  "css", "header", "footer", "details", "js"
*/
const char *skin_get(const char *zWhat){
  const char *zOut;
  char *z;
  if( iDraftSkin ){
    z = mprintf("draft%d-%s", iDraftSkin, zWhat);
    zOut = db_get(z, 0);
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/*
** Return a skin detail setting
*/
const char *skin_detail(const char *zName){
  struct SkinDetail *pDetail;
  skin_detail_initialize();
  pDetail = skin_detail_find(zName);
  if( pDetail==0 ) fossil_panic("no such skin detail: %s", zName);
  return pDetail->zValue;
}
int skin_detail_boolean(const char *zName){
  return !is_false(skin_detail(zName));
}

/*







|







254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*
** Return a skin detail setting
*/
const char *skin_detail(const char *zName){
  struct SkinDetail *pDetail;
  skin_detail_initialize();
  pDetail = skin_detail_find(zName);
  if( pDetail==0 ) fossil_fatal("no such skin detail: %s", zName);
  return pDetail->zValue;
}
int skin_detail_boolean(const char *zName){
  return !is_false(skin_detail(zName));
}

/*
478
479
480
481
482
483
484

485
486
487
488
489
490
491
492
493
494
495
496

497

498
499
500
501
502
503
504
      @ be undone.  Please confirm that this is what you want to do:</p>
      @ <input type="hidden" name="sn" value="%h(P("sn"))" />
      @ <input type="submit" name="del2" value="Confirm - Delete The Skin" />
      @ <input type="submit" name="cancel" value="Cancel - Do Not Delete" />
      login_insert_csrf_secret();
      @ </div></form>
      style_footer();

      return;
    }
    if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
      db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
    }
    if( P("draftdel")!=0 ){
      const char *zDraft = P("name");
      if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
        db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
      }
    }
    if( skinRename() ) return;

    if( skinSave(zCurrent) ) return;

  
    /* The user pressed one of the "Install" buttons. */
    if( P("load") && (z = P("sn"))!=0 && z[0] ){
      int seen = 0;
  
      /* Check to see if the current skin is already saved.  If it is, there
      ** is no need to create a backup */







>











|
>
|
>







480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
      @ be undone.  Please confirm that this is what you want to do:</p>
      @ <input type="hidden" name="sn" value="%h(P("sn"))" />
      @ <input type="submit" name="del2" value="Confirm - Delete The Skin" />
      @ <input type="submit" name="cancel" value="Cancel - Do Not Delete" />
      login_insert_csrf_secret();
      @ </div></form>
      style_footer();
      db_end_transaction(1);
      return;
    }
    if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
      db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
    }
    if( P("draftdel")!=0 ){
      const char *zDraft = P("name");
      if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
        db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
      }
    }
    if( skinRename() || skinSave(zCurrent) ){
      db_end_transaction(0);
      return;
    }
  
    /* The user pressed one of the "Install" buttons. */
    if( P("load") && (z = P("sn"))!=0 && z[0] ){
      int seen = 0;
  
      /* Check to see if the current skin is already saved.  If it is, there
      ** is no need to create a backup */
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705

706
707

708

709
710
711
712
713
714
715

716
717
718
719
720
721
722

/*
** WEBPAGE: setup_skinedit
**
** Edit aspects of a skin determined by the w= query parameter.
** Requires Setup privileges.
**
**    w=NUM     -- 0=CSS, 1=footer, 2=header, 3=details
**    sk=NUM    -- the draft skin number
*/
void setup_skinedit(void){
  static const struct sSkinAddr {
    const char *zFile;
    const char *zTitle;
    const char *zSubmenu;
  } aSkinAttr[] = {
    /* 0 */ { "css",     "CSS",             "CSS",     },
    /* 1 */ { "footer",  "Page Footer",     "Footer",  },
    /* 2 */ { "header",  "Page Header",     "Header",  },
    /* 3 */ { "details", "Display Details", "Details", },

  };
  const char *zBasis;         /* The baseline file */

  const char *zContent;       /* Content after editing */

  char *zDraft;               /* Which draft:  "draft%d" */
  char *zKey;                 /* CONFIG table key name: "draft%d-%s" */
  char *zTitle;               /* Title of this page */
  const char *zFile;          /* One of "css", "footer", "header", "details" */
  int iSkin;                  /* draft number.  1..9 */
  int ii;                     /* Index in aSkinAttr[] of this file */
  int j;                      /* Loop counter */


  login_check_credentials();

  /* Figure out which skin we are editing */
  iSkin = atoi(PD("sk","1"));
  if( iSkin<1 || iSkin>9 ) iSkin = 1;








|












>


>

>







>







691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731

/*
** WEBPAGE: setup_skinedit
**
** Edit aspects of a skin determined by the w= query parameter.
** Requires Setup privileges.
**
**    w=NUM     -- 0=CSS, 1=footer, 2=header, 3=details, 4=js
**    sk=NUM    -- the draft skin number
*/
void setup_skinedit(void){
  static const struct sSkinAddr {
    const char *zFile;
    const char *zTitle;
    const char *zSubmenu;
  } aSkinAttr[] = {
    /* 0 */ { "css",     "CSS",             "CSS",     },
    /* 1 */ { "footer",  "Page Footer",     "Footer",  },
    /* 2 */ { "header",  "Page Header",     "Header",  },
    /* 3 */ { "details", "Display Details", "Details", },
    /* 4 */ { "js",      "JavaScript",      "Script",  },
  };
  const char *zBasis;         /* The baseline file */
  const char *zOrig;          /* Original content prior to editing */
  const char *zContent;       /* Content after editing */
  const char *zDflt;          /* Default content */
  char *zDraft;               /* Which draft:  "draft%d" */
  char *zKey;                 /* CONFIG table key name: "draft%d-%s" */
  char *zTitle;               /* Title of this page */
  const char *zFile;          /* One of "css", "footer", "header", "details" */
  int iSkin;                  /* draft number.  1..9 */
  int ii;                     /* Index in aSkinAttr[] of this file */
  int j;                      /* Loop counter */
  int isRevert = 0;           /* True if Revert-to-Baseline was pressed */

  login_check_credentials();

  /* Figure out which skin we are editing */
  iSkin = atoi(PD("sk","1"));
  if( iSkin<1 || iSkin>9 ) iSkin = 1;

743
744
745
746
747
748
749







750
751
752
753
754
755
756
757
758
759
760
761
762

763
764
765

766
767
768
769
770
771
772





773
774
775
776
777
778
779
  ii = atoi(PD("w","0"));
  if( ii<0 || ii>count(aSkinAttr) ) ii = 0;
  zFile = aSkinAttr[ii].zFile;
  zDraft = mprintf("draft%d", iSkin);
  zKey = mprintf("draft%d-%s", iSkin, zFile);
  zTitle = mprintf("%s for Draft%d", aSkinAttr[ii].zTitle, iSkin);
  zBasis = PD("basis","current");








  db_begin_transaction();
  style_header("%s", zTitle);
  for(j=0; j<count(aSkinAttr); j++){
    if( j==ii ) continue;
    style_submenu_element(aSkinAttr[j].zSubmenu,
          "%R/setup_skinedit?w=%d&basis=%h&sk=%d",j,zBasis,iSkin);
  }
  @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div>
  login_insert_csrf_secret();
  @ <input type='hidden' name='w' value='%d(ii)'>
  @ <input type='hidden' name='sk' value='%d(iSkin)'>
  @ <h2>Edit %s(zTitle):</h2>

  zContent = textarea_attribute(
        "",                      /* Text label */
        10, 80,                  /* Height and width of the edit area */

        zKey,                    /* Name of CONFIG table entry */
        zFile,                              /* CGI query parameter name */
        skin_file_content(zBasis, zFile),   /* Default value of the text */
        0                                   /* Disabled flag */
  );
  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes" />





  @ <hr />
  @ Baseline: \
  skin_emit_skin_selector("basis", zBasis, zDraft);
  @ <input type="submit" name="diff" value="Unified Diff" />
  @ <input type="submit" name="sbsdiff" value="Side-by-Side Diff" />
  if( P("diff")!=0 || P("sbsdiff")!=0 ){
    u64 diffFlags = construct_diff_flags(1) | DIFF_STRIP_EOLCR;







>
>
>
>
>
>
>




<








>
|
<
<
>
|
<
|
<
<


>
>
>
>
>







752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769

770
771
772
773
774
775
776
777
778
779


780
781

782


783
784
785
786
787
788
789
790
791
792
793
794
795
796
  ii = atoi(PD("w","0"));
  if( ii<0 || ii>count(aSkinAttr) ) ii = 0;
  zFile = aSkinAttr[ii].zFile;
  zDraft = mprintf("draft%d", iSkin);
  zKey = mprintf("draft%d-%s", iSkin, zFile);
  zTitle = mprintf("%s for Draft%d", aSkinAttr[ii].zTitle, iSkin);
  zBasis = PD("basis","current");
  zDflt = skin_file_content(zBasis, zFile);
  zOrig = db_get(zKey, zDflt);
  zContent = PD(zFile,zOrig);
  if( P("revert")!=0 && cgi_csrf_safe(0) ){
    zContent = zDflt;
    isRevert = 1;
  }

  db_begin_transaction();
  style_header("%s", zTitle);
  for(j=0; j<count(aSkinAttr); j++){

    style_submenu_element(aSkinAttr[j].zSubmenu,
          "%R/setup_skinedit?w=%d&basis=%h&sk=%d",j,zBasis,iSkin);
  }
  @ <form action="%s(g.zTop)/setup_skinedit" method="post"><div>
  login_insert_csrf_secret();
  @ <input type='hidden' name='w' value='%d(ii)'>
  @ <input type='hidden' name='sk' value='%d(iSkin)'>
  @ <h2>Edit %s(zTitle):</h2>
  if( P("submit") && cgi_csrf_safe(0) && strcmp(zOrig,zContent)!=0 ){
    db_set(zKey, zContent, 0);


  }
  @ <textarea name="%s(zFile)" rows="10" cols="80">\

  @ %h(zContent)</textarea>


  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes" />
  if( isRevert ){
    @ &larr; Press to complete reversion to "%s(zBasis)"
  }else if( fossil_strcmp(zContent,zDflt)!=0 ){
    @ <input type="submit" name="revert" value='Revert To "%s(zBasis)"' />
  }
  @ <hr />
  @ Baseline: \
  skin_emit_skin_selector("basis", zBasis, zDraft);
  @ <input type="submit" name="diff" value="Unified Diff" />
  @ <input type="submit" name="sbsdiff" value="Side-by-Side Diff" />
  if( P("diff")!=0 || P("sbsdiff")!=0 ){
    u64 diffFlags = construct_diff_flags(1) | DIFF_STRIP_EOLCR;
1000
1001
1002
1003
1004
1005
1006


1007
1008
1009
1010
1011
1012
1013
    @ <li><a href='%R/setup_skinedit?w=0&sk=%d(iSkin)' target='_blank'>CSS</a>
    @ <li><a href='%R/setup_skinedit?w=2&sk=%d(iSkin)' target='_blank'>\
    @ Header</a>
    @ <li><a href='%R/setup_skinedit?w=1&sk=%d(iSkin)' target='_blank'>\
    @ Footer</a>
    @ <li><a href='%R/setup_skinedit?w=3&sk=%d(iSkin)' target='_blank'>\
    @ Details</a>


    @ </ul>
  }
  @
  @ <a name='step5'></a>
  @ <h1>Step 5: Verify The Draft Skin</h1>
  @
  @ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the







>
>







1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
    @ <li><a href='%R/setup_skinedit?w=0&sk=%d(iSkin)' target='_blank'>CSS</a>
    @ <li><a href='%R/setup_skinedit?w=2&sk=%d(iSkin)' target='_blank'>\
    @ Header</a>
    @ <li><a href='%R/setup_skinedit?w=1&sk=%d(iSkin)' target='_blank'>\
    @ Footer</a>
    @ <li><a href='%R/setup_skinedit?w=3&sk=%d(iSkin)' target='_blank'>\
    @ Details</a>
    @ <li><a href='%R/setup_skinedit?w=4&sk=%d(iSkin)' target='_blank'>\
    @ Javascript</a> (optional)
    @ </ul>
  }
  @
  @ <a name='step5'></a>
  @ <h1>Step 5: Verify The Draft Skin</h1>
  @
  @ <p>To test this draft skin, insert text "/draft%d(iSkin)/" just before the

Changes to src/smtp.c.

446
447
448
449
450
451
452
453

454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470

471
472
473
474
475

476
477
478
479
480
481
482
  smtp_session_free(p);
}

/*
** Send the content of an email message followed by a single
** "." line.  All lines must be \r\n terminated.  Any isolated
** \n line terminators in the input must be converted.  Also,
** an line contain using "." should be converted to "..".

*/
static void smtp_send_email_body(
  const char *zMsg,                          /* Message to send */
  size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
  void *pArg                                 /* First arg to sender */
){
  Blob in;
  Blob out = BLOB_INITIALIZER;
  Blob line;
  blob_init(&in, zMsg, -1);
  while( blob_line(&in, &line) ){
    char *z = blob_buffer(&line);
    int n = blob_size(&line);
    if( n==0 ) break;
    n--;
    if( n && z[n-1]=='\r' ) n--;
    if( n==1 && z[0]=='.' ){

      blob_append(&out, "..\r\n", 4);
    }else{
      blob_append(&out, z, n);
      blob_append(&out, "\r\n", 2);
    }

  }
  blob_append(&out, ".\r\n", 3);
  xSend(pArg, blob_buffer(&out), blob_size(&out));
  blob_reset(&out);
  blob_reset(&line);
}








|
>
















|
>
|


<

>







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475

476
477
478
479
480
481
482
483
484
  smtp_session_free(p);
}

/*
** Send the content of an email message followed by a single
** "." line.  All lines must be \r\n terminated.  Any isolated
** \n line terminators in the input must be converted.  Also,
** a line beginning with "." must have the dot doubled per
** https://tools.ietf.org/html/rfc5321#section-4.5.2
*/
static void smtp_send_email_body(
  const char *zMsg,                          /* Message to send */
  size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
  void *pArg                                 /* First arg to sender */
){
  Blob in;
  Blob out = BLOB_INITIALIZER;
  Blob line;
  blob_init(&in, zMsg, -1);
  while( blob_line(&in, &line) ){
    char *z = blob_buffer(&line);
    int n = blob_size(&line);
    if( n==0 ) break;
    n--;
    if( n && z[n-1]=='\r' ) n--;
    if( z[0]=='.' ){
      blob_append(&out, "..", 2);   /* RFC 5321 § 4.5.2 */
      blob_append(&out, z+1, n-1);
    }else{
      blob_append(&out, z, n);

    }
    blob_append(&out, "\r\n", 2);
  }
  blob_append(&out, ".\r\n", 3);
  xSend(pArg, blob_buffer(&out), blob_size(&out));
  blob_reset(&out);
  blob_reset(&line);
}

632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
** Server implementation
*****************************************************************************/

/*
** Schema used by the email processing system.
*/
static const char zEmailSchema[] = 
@ -- bulk storage is in a separate table.  This table can store either
@ -- the body of email messages or transcripts of smtp sessions.
@ CREATE TABLE IF NOT EXISTS repository.emailblob(
@   emailid INTEGER PRIMARY KEY AUTOINCREMENT,  -- numeric idea for the entry
@   enref INT,                    -- Number of references to this blob
@   ets INT,                      -- Corresponding transcript, or NULL
@   etime INT,                    -- insertion time, secs since 1970
@   esz INT,                      -- uncompressed content size
@   etxt TEXT                     -- content of this entry







|
|







634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
** Server implementation
*****************************************************************************/

/*
** Schema used by the email processing system.
*/
static const char zEmailSchema[] = 
@ -- bulk storage is in this table.  This table can store either
@ -- the body of email messages or transcripts of an smtp session.
@ CREATE TABLE IF NOT EXISTS repository.emailblob(
@   emailid INTEGER PRIMARY KEY AUTOINCREMENT,  -- numeric idea for the entry
@   enref INT,                    -- Number of references to this blob
@   ets INT,                      -- Corresponding transcript, or NULL
@   etime INT,                    -- insertion time, secs since 1970
@   esz INT,                      -- uncompressed content size
@   etxt TEXT                     -- content of this entry
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
@   eaddr TEXT PRIMARY KEY,  -- Email address
@   epolicy TEXT             -- How to handle email sent to this address
@ ) WITHOUT ROWID;
@
@ -- Outgoing email queue
@ CREATE TABLE IF NOT EXISTS repository.emailoutq(
@   edomain TEXT,            -- Destination domain.  (ex: "fossil-scm.org")
@   efrom TEXT,              -- Sender email address
@   eto TEXT,                -- Recipient email address
@   emsgid INT,              -- Message body in the emailblob table
@   ectime INT,              -- Time enqueued.  Seconds since 1970
@   emtime INT,              -- Time of last send attempt.  Sec since 1970
@   ensend INT,              -- Number of send attempts
@   ets INT                  -- Transcript of last failed attempt
@ );
@







|
|







667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
@   eaddr TEXT PRIMARY KEY,  -- Email address
@   epolicy TEXT             -- How to handle email sent to this address
@ ) WITHOUT ROWID;
@
@ -- Outgoing email queue
@ CREATE TABLE IF NOT EXISTS repository.emailoutq(
@   edomain TEXT,            -- Destination domain.  (ex: "fossil-scm.org")
@   efrom TEXT,              -- Sender email address (envelope "from")
@   eto TEXT,                -- Recipient email address (envelope "to")
@   emsgid INT,              -- Message body in the emailblob table
@   ectime INT,              -- Time enqueued.  Seconds since 1970
@   emtime INT,              -- Time of last send attempt.  Sec since 1970
@   ensend INT,              -- Number of send attempts
@   ets INT                  -- Transcript of last failed attempt
@ );
@
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800


801
802
803
804

805
806

807
808
809

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827

828

829
830
831
832
833
834
835
836
837






















































































































838
839
840
841
842
843
844
  if( eForce==2 ){
    db_multi_exec(zEmailDrop/*works-like:""*/);
  }
  if( eForce==1 || !db_table_exists("repository","emailblob") ){
    db_multi_exec(zEmailSchema/*works-like:""*/);
  }
}

/*
** Process POST change requests from the setup_smtp page
*/
static void handle_smtp_setup_edit(char **pzErr, int *piErr){
  *piErr = 0;
  *pzErr = 0;
  if( !cgi_csrf_safe(1) ) return;
  if( P("new")!=0 ){
    const char *zEAddr = PDT("eaddr","");
    const char *zEPolicy = PDT("epolicy","");
    smtp_server_schema(0);
    if( db_exists("SELECT 1 FROM emailroute WHERE eaddr=%Q", zEAddr) ){
      *pzErr = mprintf("\"%s\" already exists", zEAddr);
      *piErr = 100;
      return;
    }
    db_multi_exec("INSERT INTO emailroute(eaddr,epolicy)"
                  "VALUES(%Q,%Q)", zEAddr, zEPolicy);
    return;
  }
  if( P("delete")!=0 ){
    const char *zEAddr = PDT("eaddr","");
    const char *zEPolicy = PDT("epolicy","");
    smtp_server_schema(0);
    if( zEPolicy[0] ){
      *pzErr = mprintf("Erase routing information to delete");
      *piErr = 200;
      return;
    }
    db_multi_exec("DELETE FROM emailroute WHERE eaddr=%Q", zEAddr);
    return;
  }
  if( P("change")!=0 ){
    const char *zEAddr = PDT("eaddr","");
    const char *zEPolicy = PDT("epolicy","");
    smtp_server_schema(0);
    db_multi_exec("UPDATE emailroute SET epolicy=%Q WHERE eaddr=%Q",
                   zEPolicy, zEAddr);
    return;
  }
}


/*
** WEBPAGE: setup_smtp
**
** Administrative page for configuring and controlling inbound email and
** output email queuing.  This page is available to administrators
** only via the /Admin/EmailServer menu.
*/
void setup_smtp(void){
  Stmt q;
  char *zErr = 0;
  int iErr = 0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();
  style_header("Email Server Setup");
  handle_smtp_setup_edit(&zErr, &iErr);
  if( db_table_exists("repository","emailroute") ){


    db_prepare(&q, "SELECT eaddr, epolicy FROM emailroute ORDER BY 1");
  }else{
    db_prepare(&q, "SELECT null, null WHERE false");
  }

  @ <table class="emailroutetab">
  @ <thead>

  @ <tr><th>Email Address
  @ <th>Routing
  @ <th></th>

  @ </thead><tbody>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zEAddr = db_column_text(&q, 0);
    const char *zEPolicy = db_column_text(&q, 1);
    @ <tr><form action="%R/setup_smtp" method="POST">
    @ <td valign="top">%h(zEAddr)\
    @ <input type="hidden" name="eaddr" value="%h(zEAddr)"></td>
    @ <td valign="top">\
    @ <textarea name="epolicy" rows="3" cols="40">%h(zEPolicy)</textarea>\
    @ </td>
    @ <td valign="top">\
    @ <input type="submit" name="change" value="Apply Changes"><br>\
    @ <input type="submit" name="delete" value="Delete"></td>
    @ </form></tr>
  }
  db_finalize(&q);
  @ <tr><form action="%R/setup_smtp" method="POST">
  @ <td valign="top">

  @ <input type="text" name="eaddr" width="30"></td>

  @ <td valign="top">\
  @ <textarea name="epolicy" rows="3" cols="40"></textarea>\
  @ </td>
  @ <td valign="top"><input type="submit" name="new" value="New"></td>
  @ </form></tr>
  style_footer();
  fossil_free(zErr);
  db_end_transaction(0);
}























































































































#if LOCAL_INTERFACE
/*
** State information for the server
*/
struct SmtpServer {
  sqlite3_int64 idTranscript; /* Transcript ID number */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










<
<







<

>
>




>
|

>
|
|
|
>




|
|
<
|
<
<
|
|
|
|


|
|
>
|
>
|
<
|
<
<

<


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







732
733
734
735
736
737
738











































739
740
741
742
743
744
745
746
747
748


749
750
751
752
753
754
755

756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776

777


778
779
780
781
782
783
784
785
786
787
788
789

790


791

792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
  if( eForce==2 ){
    db_multi_exec(zEmailDrop/*works-like:""*/);
  }
  if( eForce==1 || !db_table_exists("repository","emailblob") ){
    db_multi_exec(zEmailSchema/*works-like:""*/);
  }
}












































/*
** WEBPAGE: setup_smtp
**
** Administrative page for configuring and controlling inbound email and
** output email queuing.  This page is available to administrators
** only via the /Admin/EmailServer menu.
*/
void setup_smtp(void){
  Stmt q;


  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();
  style_header("Email Server Setup");

  if( db_table_exists("repository","emailroute") ){
    style_submenu_element("emailblob table", "%R/emailblob");
    style_submenu_element("emailoutq table", "%R/emailoutq");
    db_prepare(&q, "SELECT eaddr, epolicy FROM emailroute ORDER BY 1");
  }else{
    db_prepare(&q, "SELECT null, null WHERE false");
  }
  @ <h1>Email Routing Table</h1>
  @ <table class="emailroutetab" cellpadding="5" border="1" cellspacing="0">
  @ <thead>
  @ <tr>
  @   <th>Email Address
  @   <th>Routing
  @   <th>
  @ </tr>
  @ </thead><tbody>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zEAddr = db_column_text(&q, 0);
    const char *zEPolicy = db_column_text(&q, 1);
    @ <tr>
    @  <td valign="top">%h(zEAddr)</td>

    @  <td valign="top"><span style="white-space:pre;">%h(zEPolicy)</span></td>


    @  <td valign="top"><form method="POST" action="%R/setup_smtp_route">
    @    <input type="hidden" name="oaddr" value="%h(zEAddr)">
    @    <input type="submit" value="Edit">
    @    </form>
  }
  db_finalize(&q);
  @ <tr>
  @   <td colspan="3">
  @   <form method="POST" action="%R/setup_smtp_route">
  @   <input type="submit" value="New">
  @    &larr; Add a new email address
  @   </form>

  @ </table>


  style_footer();

  db_end_transaction(0);
}

/*
** WEBPAGE: setup_smtp_route
**
** Edit a single entry in the emailroute table.
** Query parameters:
**
**    eaddr=ADDR          ADDR is the email address as edited.
**
**    oaddr=ADDR          The original email address prior to editing.
**                        Omit to add a new address.
**
**    epolicy=TXT         The routing policy.
*/
void setup_smtp_route(void){
  char *zEAddr = PT("eaddr");         /* new email address */
  char *zEPolicy = PT("epolicy");     /* new routing policy */
  char *zOAddr = PT("oaddr");         /* original email address */
  char *zErr = 0;
  int iErr = 0;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  style_header("Email Route Editor");

  if( P("edit") && cgi_csrf_safe(1) && zEAddr!=0 && zEPolicy!=0 ){
    smtp_server_schema(0);
    if( (zOAddr==0 || fossil_strcmp(zEAddr,zOAddr)!=0) ){
      /* New or changed email address */
      if( db_exists("SELECT 1 FROM emailroute WHERE eaddr=%Q",zEAddr) ){
        iErr = 1;
        zErr = mprintf("email address \"%h(zEAddr)\" already exists",zEAddr);
        goto smtp_route_edit;
      }
      if( zEPolicy[0]==0 ){
        iErr = 2;
        zErr = mprintf("empty route");
        goto smtp_route_edit;
      }
    }
    /* If the email address has changed, or if the new policy is blank,
    ** delete the old address and route information
    */
    db_begin_transaction();
    if( (zOAddr && fossil_strcmp(zEAddr,zOAddr)!=0) || zEPolicy[0]==0 ){
      db_multi_exec("DELETE FROM emailroute WHERE eaddr=%Q", zOAddr);
    }
    if( zEPolicy[0] ){
      /* Insert the new address and route */
      db_multi_exec(
        "REPLACE INTO emailroute(eaddr,epolicy) VALUES(%Q,%Q)",
        zEAddr, zEPolicy
      );
    }
    db_end_transaction(0);
    cgi_redirectf("%R/setup_smtp");
  }
  if( P("cancel")!=0 ){
    cgi_redirectf("%R/setup_smtp");
  }

smtp_route_edit:
  if( zEAddr==0 ) zEAddr = zOAddr;
  if( zEPolicy==0 && db_table_exists("repository","emailroute") ){
    zEPolicy = db_text(0, "SELECT epolicy FROM emailroute WHERE eaddr=%Q",
                      zEAddr);
  }
  if( zEPolicy==0 ) zEPolicy = "";
  @ <form method="POST" action="%R/setup_smtp_route">
  if( zOAddr ){
    @ <input type="hidden" name="oaddr" value="%h(zOAddr)">
  }
  @ <table class="label-value">
  @ <tr>
  @   <th>Email Address:</th>
  @   <td><input type="text" size=30 name="eaddr" value="%h(zEAddr)">
  if( iErr==1 ){
    @ <td><span class="generalError">&larr; %z(zErr)</span>
  }
  @ </tr>
  if( zOAddr && fossil_strcmp(zOAddr,zEAddr)!=0 ){
    @ <tr>
    @   <th>Original Address:</th>
    @   <td>%h(zOAddr)
    @ </tr>
  }
  @ <tr>
  @   <th>Routing:</th>
  @   <td><textarea name="epolicy" rows="3" cols="40">%h(zEPolicy)</textarea>
  if( iErr==2 ){
    @ <td valign="top"><span class="generalError">&larr; %z(zErr)</span>
  }
  @ </tr>
  @ <tr>
  @   <td>&nbsp;
  @   <td><input type="submit" name="edit" value="Apply">
  @       <input type="submit" name="cancel" value="Cancel">
  @ </tr>
  @ </table>
  @ <hr>
  @ <h1>Instructions</h1>
  @ 
  @ <p>The "Routing" field consists of zero or more lines where each
  @ line is an "action" followed by an "argument".  Available actions:
  @ <ul>
  @ <li><p><b>forward</b> <i>email-address</i>
  @ <p>Forward the message to <i>email-address</i>.
  @ <li><p><b>mbox</b> <i>login-name</i>
  @ <p>Store the message in the local mailbox for the user
  @ with USER.LOGIN=<i>login-name</i>.
  @ </ul>
  @ 
  @ <p>To delete a route &rarr; erase all text from the "Routing" field then
  @ press the "Apply" button.
  style_footer();
}

#if LOCAL_INTERFACE
/*
** State information for the server
*/
struct SmtpServer {
  sqlite3_int64 idTranscript; /* Transcript ID number */
1137
1138
1139
1140
1141
1142
1143














1144
1145
1146
1147
1148
1149
1150
    }

    /* Finish the transaction after all changes are implemented */
    db_commit_transaction();
  }
  smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
}















/*
** COMMAND: test-emailblob-refcheck
**
** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
**
** Verify that the emailblob.enref field is correct.  Report any errors.







>
>
>
>
>
>
>
>
>
>
>
>
>
>







1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
    }

    /* Finish the transaction after all changes are implemented */
    db_commit_transaction();
  }
  smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
}

/*
** Remove stale content from the emailblob table.
*/
void smtp_cleanup(void){
  if( db_table_exists("repository","emailblob") ){
    db_begin_transaction();
    db_multi_exec(
      "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
      "DELETE FROM emailblob WHERE enref<=0;"
    );
    db_end_transaction(0);
  }
}

/*
** COMMAND: test-emailblob-refcheck
**
** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
**
** Verify that the emailblob.enref field is correct.  Report any errors.
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
    "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;"
  );
  if( doRepair ){
    db_multi_exec(
      "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
    );
    if( doClean ){
      db_multi_exec(
        "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
        "DELETE FROM emailblob WHERE enref<=0;"
      );
    }
  }
  blob_init(&sql, 0, 0);
  blob_append_sql(&sql, 
    "SELECT a.emailid, a.enref, b.n"
    "  FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
  );







<
<
<
|







1269
1270
1271
1272
1273
1274
1275



1276
1277
1278
1279
1280
1281
1282
1283
    "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;"
  );
  if( doRepair ){
    db_multi_exec(
      "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
    );
    if( doClean ){



      smtp_cleanup();
    }
  }
  blob_init(&sql, 0, 0);
  blob_append_sql(&sql, 
    "SELECT a.emailid, a.enref, b.n"
    "  FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
  );
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
    }else
    if( strncmp(z, "HELO", 4)==0  && fossil_isspace(z[4]) ){
      smtp_server_send(&x, "250 ok\r\n");
    }else
    if( strncmp(z, "MAIL FROM:<", 11)==0 ){
      smtp_server_route_incoming(&x, 0);
      smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
      x.zFrom = email_copy_addr(z+11);
      if( x.zFrom==0 ){
        smtp_server_send(&x, "500 unacceptable email address\r\n");
      }else{
        smtp_server_send(&x, "250 ok\r\n");
      }
    }else
    if( strncmp(z, "RCPT TO:<", 9)==0 ){
      char *zAddr;
      if( x.zFrom==0 ){
        smtp_server_send(&x, "500 missing MAIL FROM\r\n");
        continue;
      }
      zAddr = email_copy_addr(z+9);
      if( zAddr==0 ){
        smtp_server_send(&x, "505 no such user\r\n");
        continue;
      }
      smtp_append_to(&x, zAddr, 0);
      if( x.nTo>=100 ){
        smtp_server_send(&x, "452 too many recipients\r\n");







|












|







1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
    }else
    if( strncmp(z, "HELO", 4)==0  && fossil_isspace(z[4]) ){
      smtp_server_send(&x, "250 ok\r\n");
    }else
    if( strncmp(z, "MAIL FROM:<", 11)==0 ){
      smtp_server_route_incoming(&x, 0);
      smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
      x.zFrom = email_copy_addr(z+11,'>');
      if( x.zFrom==0 ){
        smtp_server_send(&x, "500 unacceptable email address\r\n");
      }else{
        smtp_server_send(&x, "250 ok\r\n");
      }
    }else
    if( strncmp(z, "RCPT TO:<", 9)==0 ){
      char *zAddr;
      if( x.zFrom==0 ){
        smtp_server_send(&x, "500 missing MAIL FROM\r\n");
        continue;
      }
      zAddr = email_copy_addr(z+9, '>');
      if( zAddr==0 ){
        smtp_server_send(&x, "505 no such user\r\n");
        continue;
      }
      smtp_append_to(&x, zAddr, 0);
      if( x.nTo>=100 ){
        smtp_server_send(&x, "452 too many recipients\r\n");
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
  fflush(stdout);
  if( pLog ) fprintf(pLog, "S: %s\n", zLine);
}

/*
** Try to log in for zUser and zPass.
**
** If zUser/zPass does not work as written, then modify zUser by
** omitting everything after the "@" (if there is one) and trying
** again.
*/
static int pop3_login(char *zUser, char *zPass){
  int uid;
  int i;
  uid = login_search_uid(zUser, zPass);
  if( uid ) return 1;
  for(i=0; zUser[i] && zUser[i]!='@'; i++){}
  if( zUser[i]=='@' ){
    zUser[i] = 0;
    uid = login_search_uid(zUser, zPass);
    if( uid ) return 1;
    zUser[i] = '@';
  }
  return 0; 
}

/*
** COMMAND: pop3d
**
** Usage: %fossil pop3d [OPTIONS] REPOSITORY
**







|
|
<

|
<
<
|
<
<
<
<
<
<
<
<
<







1436
1437
1438
1439
1440
1441
1442
1443
1444

1445
1446


1447









1448
1449
1450
1451
1452
1453
1454
  fflush(stdout);
  if( pLog ) fprintf(pLog, "S: %s\n", zLine);
}

/*
** Try to log in for zUser and zPass.
**
** zUser can either point to a Fossil user name or to an email address
** found in the user table's info field, in angle brackets.

*/
static int pop3_login(const char *zUser, char *zPass){


  return login_search_uid(&zUser, zPass) != 0;









}

/*
** COMMAND: pop3d
**
** Usage: %fossil pop3d [OPTIONS] REPOSITORY
**

Changes to src/sqlite3.c.

more than 10,000 changes

Changes to src/sqlite3.h.

121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
**
** See also: [sqlite3_libversion()],
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION        "3.25.0"
#define SQLITE_VERSION_NUMBER 3025000
#define SQLITE_SOURCE_ID      "2018-07-18 19:09:07 a5087c5c87ad65f92e3bc96bbc84afb43faf10ab6b9ed3ba16304b5c60ad069f"

/*
** CAPI3REF: Run-Time Library Version Numbers
** KEYWORDS: sqlite3_version sqlite3_sourceid
**
** These interfaces provide the same information as the [SQLITE_VERSION],
** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros







|







121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
**
** See also: [sqlite3_libversion()],
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION        "3.25.0"
#define SQLITE_VERSION_NUMBER 3025000
#define SQLITE_SOURCE_ID      "2018-09-10 19:34:06 74c381b573817d0212153278b5ee5d2238a27a727dcf7ee769365c47bb9fc40d"

/*
** CAPI3REF: Run-Time Library Version Numbers
** KEYWORDS: sqlite3_version sqlite3_sourceid
**
** These interfaces provide the same information as the [SQLITE_VERSION],
** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
468
469
470
471
472
473
474

475
476
477
478
479
480
481
** on a per database connection basis using the
** [sqlite3_extended_result_codes()] API.  Or, the extended code for
** the most recent error can be obtained using
** [sqlite3_extended_errcode()].
*/
#define SQLITE_ERROR_MISSING_COLLSEQ   (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY             (SQLITE_ERROR | (2<<8))

#define SQLITE_IOERR_READ              (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ        (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE             (SQLITE_IOERR | (3<<8))
#define SQLITE_IOERR_FSYNC             (SQLITE_IOERR | (4<<8))
#define SQLITE_IOERR_DIR_FSYNC         (SQLITE_IOERR | (5<<8))
#define SQLITE_IOERR_TRUNCATE          (SQLITE_IOERR | (6<<8))
#define SQLITE_IOERR_FSTAT             (SQLITE_IOERR | (7<<8))







>







468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
** on a per database connection basis using the
** [sqlite3_extended_result_codes()] API.  Or, the extended code for
** the most recent error can be obtained using
** [sqlite3_extended_errcode()].
*/
#define SQLITE_ERROR_MISSING_COLLSEQ   (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY             (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT          (SQLITE_ERROR | (3<<8))
#define SQLITE_IOERR_READ              (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ        (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE             (SQLITE_IOERR | (3<<8))
#define SQLITE_IOERR_FSYNC             (SQLITE_IOERR | (4<<8))
#define SQLITE_IOERR_DIR_FSYNC         (SQLITE_IOERR | (5<<8))
#define SQLITE_IOERR_TRUNCATE          (SQLITE_IOERR | (6<<8))
#define SQLITE_IOERR_FSTAT             (SQLITE_IOERR | (7<<8))
883
884
885
886
887
888
889

890
891
892
893
894
895
896
897
** is not changed but instead the prior value of that setting is written
** into the array entry, allowing the current retry settings to be
** interrogated.  The zDbName parameter is ignored.
**
** <li>[[SQLITE_FCNTL_PERSIST_WAL]]
** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the
** persistent [WAL | Write Ahead Log] setting.  By default, the auxiliary

** write ahead log and shared memory files used for transaction control
** are automatically deleted when the latest connection to the database
** closes.  Setting persistent WAL mode causes those files to persist after
** close.  Persisting the files is useful when other processes that do not
** have write permission on the directory containing the database file want
** to read the database file, as the WAL and shared memory files must exist
** in order for the database to be readable.  The fourth parameter to
** [sqlite3_file_control()] for this opcode should be a pointer to an integer.







>
|







884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
** is not changed but instead the prior value of that setting is written
** into the array entry, allowing the current retry settings to be
** interrogated.  The zDbName parameter is ignored.
**
** <li>[[SQLITE_FCNTL_PERSIST_WAL]]
** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the
** persistent [WAL | Write Ahead Log] setting.  By default, the auxiliary
** write ahead log ([WAL file]) and shared memory
** files used for transaction control
** are automatically deleted when the latest connection to the database
** closes.  Setting persistent WAL mode causes those files to persist after
** close.  Persisting the files is useful when other processes that do not
** have write permission on the directory containing the database file want
** to read the database file, as the WAL and shared memory files must exist
** in order for the database to be readable.  The fourth parameter to
** [sqlite3_file_control()] for this opcode should be a pointer to an integer.
6435
6436
6437
6438
6439
6440
6441

6442
6443
6444
6445
6446
6447
6448
#define SQLITE_INDEX_CONSTRAINT_GLOB      66
#define SQLITE_INDEX_CONSTRAINT_REGEXP    67
#define SQLITE_INDEX_CONSTRAINT_NE        68
#define SQLITE_INDEX_CONSTRAINT_ISNOT     69
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
#define SQLITE_INDEX_CONSTRAINT_ISNULL    71
#define SQLITE_INDEX_CONSTRAINT_IS        72


/*
** CAPI3REF: Register A Virtual Table Implementation
** METHOD: sqlite3
**
** ^These routines are used to register a new [virtual table module] name.
** ^Module names must be registered before







>







6437
6438
6439
6440
6441
6442
6443
6444
6445
6446
6447
6448
6449
6450
6451
#define SQLITE_INDEX_CONSTRAINT_GLOB      66
#define SQLITE_INDEX_CONSTRAINT_REGEXP    67
#define SQLITE_INDEX_CONSTRAINT_NE        68
#define SQLITE_INDEX_CONSTRAINT_ISNOT     69
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
#define SQLITE_INDEX_CONSTRAINT_ISNULL    71
#define SQLITE_INDEX_CONSTRAINT_IS        72
#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150

/*
** CAPI3REF: Register A Virtual Table Implementation
** METHOD: sqlite3
**
** ^These routines are used to register a new [virtual table module] name.
** ^Module names must be registered before
8956
8957
8958
8959
8960
8961
8962
8963
8964
8965
8966
8967
8968
8969
8970
8971
8972
8973
8974
8975
8976
8977
8978
8979
8980
8981
8982
8983
8984
8985
8986
8987
8988
8989
8990
8991
8992
8993
8994
8995
8996
8997
8998
8999
9000
9001
9002
9003
9004
9005
9006
9007
9008
9009
9010
9011
9012
9013
9014
9015
** as ENOSPC, EAUTH, EISDIR, and so forth.  
*/
SQLITE_API int sqlite3_system_errno(sqlite3*);

/*
** CAPI3REF: Database Snapshot
** KEYWORDS: {snapshot} {sqlite3_snapshot}
** EXPERIMENTAL
**
** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history.
**
** In [WAL mode], multiple [database connections] that are open on the
** same database file can each be reading a different historical version
** of the database file.  When a [database connection] begins a read
** transaction, that connection sees an unchanging copy of the database
** as it existed for the point in time when the transaction first started.
** Subsequent changes to the database from other connections are not seen
** by the reader until a new read transaction is started.
**
** The sqlite3_snapshot object records state information about an historical
** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than
** the most recent version.
**
** The constructor for this object is [sqlite3_snapshot_get()].  The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible).  The destructor for 
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
typedef struct sqlite3_snapshot {
  unsigned char hidden[48];
} sqlite3_snapshot;

/*
** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D.  ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
** If there is not already a read-transaction open on schema S when
** this function is called, one is opened automatically. 
**
** The following must be true for this function to succeed. If any of
** the following statements are false when sqlite3_snapshot_get() is
** called, SQLITE_ERROR is returned. The final value of *P is undefined
** in this case. 
**
** <ul>
**   <li> The database handle must be in [autocommit mode].
**
**   <li> Schema S of [database connection] D must be a [WAL mode] database.
**
**   <li> There must not be a write transaction open on schema S of database
**        connection D.
**
**   <li> One or more transactions must have been written to the current wal







<
















<
<
<
<
<







|















|







8959
8960
8961
8962
8963
8964
8965

8966
8967
8968
8969
8970
8971
8972
8973
8974
8975
8976
8977
8978
8979
8980
8981





8982
8983
8984
8985
8986
8987
8988
8989
8990
8991
8992
8993
8994
8995
8996
8997
8998
8999
9000
9001
9002
9003
9004
9005
9006
9007
9008
9009
9010
9011
9012
** as ENOSPC, EAUTH, EISDIR, and so forth.  
*/
SQLITE_API int sqlite3_system_errno(sqlite3*);

/*
** CAPI3REF: Database Snapshot
** KEYWORDS: {snapshot} {sqlite3_snapshot}

**
** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history.
**
** In [WAL mode], multiple [database connections] that are open on the
** same database file can each be reading a different historical version
** of the database file.  When a [database connection] begins a read
** transaction, that connection sees an unchanging copy of the database
** as it existed for the point in time when the transaction first started.
** Subsequent changes to the database from other connections are not seen
** by the reader until a new read transaction is started.
**
** The sqlite3_snapshot object records state information about an historical
** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than
** the most recent version.





*/
typedef struct sqlite3_snapshot {
  unsigned char hidden[48];
} sqlite3_snapshot;

/*
** CAPI3REF: Record A Database Snapshot
** CONSTRUCTOR: sqlite3_snapshot
**
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D.  ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
** If there is not already a read-transaction open on schema S when
** this function is called, one is opened automatically. 
**
** The following must be true for this function to succeed. If any of
** the following statements are false when sqlite3_snapshot_get() is
** called, SQLITE_ERROR is returned. The final value of *P is undefined
** in this case. 
**
** <ul>
**   <li> The database handle must not be in [autocommit mode].
**
**   <li> Schema S of [database connection] D must be a [WAL mode] database.
**
**   <li> There must not be a write transaction open on schema S of database
**        connection D.
**
**   <li> One or more transactions must have been written to the current wal
9024
9025
9026
9027
9028
9029
9030
9031
9032
9033
9034
9035
9036
9037
9038
9039
9040
9041
9042
9043
9044
9045
9046
9047
9048
9049
9050
9051
9052
9053




9054

9055
9056
9057

9058








9059
9060
9061
9062
9063
9064
9065
9066
9067
9068
9069
9070
9071
9072
9073
9074
9075
9076
9077
9078
9079
9080
9081
9082
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
9106
9107
9108
9109
9110



9111
9112
9113
9114
9115
9116
9117
9118
9119
9120
9121
9122
9123

9124
9125

9126
9127
9128
9129
9130
9131
9132
9133
9134
9135



9136
9137
9138
9139
9140
9141
9142
** whether or not a read transaction is opened on schema S is undefined.
**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot **ppSnapshot
);

/*
** CAPI3REF: Start a read transaction on an historical snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
** read transaction for schema S of
** [database connection] D such that the read transaction
** refers to historical [snapshot] P, rather than the most
** recent change to the database.
** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
** or an appropriate [error code] if it fails.
**
** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
** the first operation following the [BEGIN] that takes the schema S
** out of [autocommit mode].




** ^In other words, schema S must not currently be in

** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
** database connection D must be out of [autocommit mode].
** ^A [snapshot] will fail to open if it has been overwritten by a

** [checkpoint].








** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode].  A database connection might not know
** that the database file is in [WAL mode] if there has been no prior
** I/O on that database connection, or if the database entered [WAL mode] 
** after the most recent I/O on the database connection.)^
** (Hint: Run "[PRAGMA application_id]" against a newly opened
** database connection in order to make it ready to use snapshots.)
**
** The [sqlite3_snapshot_open()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot *pSnapshot
);

/*
** CAPI3REF: Destroy a snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
** The application must eventually free every [sqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
** The [sqlite3_snapshot_free()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);

/*
** CAPI3REF: Compare the ages of two snapshot handles.
** EXPERIMENTAL
**
** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles. 
**
** If the two snapshot handles are not associated with the same database 
** file, the result of the comparison is undefined. 
**
** Additionally, the result of the comparison is only valid if both of the
** snapshot handles were obtained by calling sqlite3_snapshot_get() since the
** last time the wal file was deleted. The wal file is deleted when the
** database is changed back to rollback mode or when the number of database
** clients drops to zero. If either snapshot handle was obtained before the 
** wal file was last deleted, the value returned by this function 
** is undefined.
**
** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2.



*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
  sqlite3_snapshot *p1,
  sqlite3_snapshot *p2
);

/*
** CAPI3REF: Recover snapshots from a wal file
** EXPERIMENTAL
**
** If all connections disconnect from a database file but do not perform
** a checkpoint, the existing wal file is opened along with the database
** file the next time the database is opened. At this point it is only

** possible to successfully call sqlite3_snapshot_open() to open the most
** recent snapshot of the database (the one at the head of the wal file),

** even though the wal file may contain other valid snapshots for which
** clients have sqlite3_snapshot handles.
**
** This function attempts to scan the wal file associated with database zDb
** of database handle db and make all valid snapshots available to
** sqlite3_snapshot_open(). It is an error if there is already a read
** transaction open on the database, or if the database is not a wal mode
** database.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.



*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);

/*
** CAPI3REF: Serialize a database
**
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory







|









|

|
|
|
|
<
|
|

|
<
|
>
>
>
>
|
>
|
<
|
>
|
>
>
>
>
>
>
>
>










|









|






|





|


















>
>
>








|

|
|
|
>
|
<
>
|
<

|


|



>
>
>







9021
9022
9023
9024
9025
9026
9027
9028
9029
9030
9031
9032
9033
9034
9035
9036
9037
9038
9039
9040
9041
9042
9043

9044
9045
9046
9047

9048
9049
9050
9051
9052
9053
9054
9055

9056
9057
9058
9059
9060
9061
9062
9063
9064
9065
9066
9067
9068
9069
9070
9071
9072
9073
9074
9075
9076
9077
9078
9079
9080
9081
9082
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098
9099
9100
9101
9102
9103
9104
9105
9106
9107
9108
9109
9110
9111
9112
9113
9114
9115
9116
9117
9118
9119
9120
9121
9122
9123
9124
9125
9126
9127
9128
9129
9130
9131
9132
9133
9134
9135
9136

9137
9138

9139
9140
9141
9142
9143
9144
9145
9146
9147
9148
9149
9150
9151
9152
9153
9154
9155
9156
** whether or not a read transaction is opened on schema S is undefined.
**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot **ppSnapshot
);

/*
** CAPI3REF: Start a read transaction on an historical snapshot
** METHOD: sqlite3_snapshot
**
** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read 
** transaction or upgrades an existing one for schema S of 
** [database connection] D such that the read transaction refers to 
** historical [snapshot] P, rather than the most recent change to the 

** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK 
** on success or an appropriate [error code] if it fails.
**
** ^In order to succeed, the database connection must not be in 

** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
** is already a read transaction open on schema S, then the database handle
** must have no active statements (SELECT statements that have been passed
** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). 
** SQLITE_ERROR is returned if either of these conditions is violated, or
** if schema S does not exist, or if the snapshot object is invalid.
**
** ^A call to sqlite3_snapshot_open() will fail to open if the specified

** snapshot has been overwritten by a [checkpoint]. In this case 
** SQLITE_ERROR_SNAPSHOT is returned.
**
** If there is already a read transaction open when this function is 
** invoked, then the same read transaction remains open (on the same
** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT
** is returned. If another error code - for example SQLITE_PROTOCOL or an
** SQLITE_IOERR error code - is returned, then the final state of the
** read transaction is undefined. If SQLITE_OK is returned, then the 
** read transaction is now open on database snapshot P.
**
** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode].  A database connection might not know
** that the database file is in [WAL mode] if there has been no prior
** I/O on that database connection, or if the database entered [WAL mode] 
** after the most recent I/O on the database connection.)^
** (Hint: Run "[PRAGMA application_id]" against a newly opened
** database connection in order to make it ready to use snapshots.)
**
** The [sqlite3_snapshot_open()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot *pSnapshot
);

/*
** CAPI3REF: Destroy a snapshot
** DESTRUCTOR: sqlite3_snapshot
**
** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
** The application must eventually free every [sqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
** The [sqlite3_snapshot_free()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);

/*
** CAPI3REF: Compare the ages of two snapshot handles.
** METHOD: sqlite3_snapshot
**
** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles. 
**
** If the two snapshot handles are not associated with the same database 
** file, the result of the comparison is undefined. 
**
** Additionally, the result of the comparison is only valid if both of the
** snapshot handles were obtained by calling sqlite3_snapshot_get() since the
** last time the wal file was deleted. The wal file is deleted when the
** database is changed back to rollback mode or when the number of database
** clients drops to zero. If either snapshot handle was obtained before the 
** wal file was last deleted, the value returned by this function 
** is undefined.
**
** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
  sqlite3_snapshot *p1,
  sqlite3_snapshot *p2
);

/*
** CAPI3REF: Recover snapshots from a wal file
** METHOD: sqlite3_snapshot
**
** If a [WAL file] remains on disk after all database connections close
** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control]
** or because the last process to have the database opened exited without
** calling [sqlite3_close()]) and a new connection is subsequently opened
** on that database and [WAL file], the [sqlite3_snapshot_open()] interface

** will only be able to open the last transaction added to the WAL file
** even though the WAL file contains other valid transactions.

**
** This function attempts to scan the WAL file associated with database zDb
** of database handle db and make all valid snapshots available to
** sqlite3_snapshot_open(). It is an error if there is already a read
** transaction open on the database, or if the database is not a WAL mode
** database.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);

/*
** CAPI3REF: Serialize a database
**
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory

Changes to src/stat.c.

264
265
266
267
268
269
270
271




272
273
274
275
276
277
278
    i64 szFile = file_size(g.zErrlog, ExtFILE);
    if( szFile>=0 ){
      @ <tr><th>Error Log:</th>
      @ <td><a href='%R/errorlog'>%h(g.zErrlog)</a> (%,lld(szFile) bytes)
    }
    @ </td></tr>
  }
  if( g.perm.Admin && email_enabled() ){




    stats_for_email();
  }

  @ </table>
  style_footer();
}








|
>
>
>
>







264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
    i64 szFile = file_size(g.zErrlog, ExtFILE);
    if( szFile>=0 ){
      @ <tr><th>Error Log:</th>
      @ <td><a href='%R/errorlog'>%h(g.zErrlog)</a> (%,lld(szFile) bytes)
    }
    @ </td></tr>
  }
  if( g.perm.Admin ){
    @ <tr><th>Backoffice:</th>
    @ <td>Last run: %z(backoffice_last_run())</td></tr>
  }
  if( g.perm.Admin && alert_enabled() ){
    stats_for_email();
  }

  @ </table>
  style_footer();
}

Changes to src/style.c.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
** This file contains code to implement the basic web page look and feel.
**
*/
#include "VERSION.h"
#include "config.h"
#include "style.h"


/*
** Elements of the submenu are collected into the following
** structure and displayed below the main menu.
**
** Populate these structure with calls to
**
**      style_submenu_element()







<







18
19
20
21
22
23
24

25
26
27
28
29
30
31
** This file contains code to implement the basic web page look and feel.
**
*/
#include "VERSION.h"
#include "config.h"
#include "style.h"


/*
** Elements of the submenu are collected into the following
** structure and displayed below the main menu.
**
** Populate these structure with calls to
**
**      style_submenu_element()
85
86
87
88
89
90
91





92
93
94
95
96
97
98
/*
** Flags for various javascript files needed prior to </body>
*/
static int needHrefJs = 0;   /* href.js */
static int needSortJs = 0;   /* sorttable.js */
static int needGraphJs = 0;  /* graph.js */






/*
** Generate and return a anchor tag like this:
**
**        <a href="URL">
**  or    <a id="ID">
**
** The form of the anchor tag is determined by the g.javascriptHyperlink







>
>
>
>
>







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
** Flags for various javascript files needed prior to </body>
*/
static int needHrefJs = 0;   /* href.js */
static int needSortJs = 0;   /* sorttable.js */
static int needGraphJs = 0;  /* graph.js */

/*
** Extra JS added to the end of the file.
*/
static Blob blobOnLoad = BLOB_INITIALIZER;

/*
** Generate and return a anchor tag like this:
**
**        <a href="URL">
**  or    <a id="ID">
**
** The form of the anchor tag is determined by the g.javascriptHyperlink
359
360
361
362
363
364
365














366
367
368
369
370
371
372
373
374
375
376
377



378
379
380
381
382
383
384
385



























386
387
388
389
390
391
392
static void image_url_var(const char *zImageName){
  char *zVarPrefix = mprintf("%s_image", zImageName);
  char *zConfigName = mprintf("%s-image", zImageName);
  url_var(zVarPrefix, zConfigName, zImageName);
  free(zVarPrefix);
  free(zConfigName);
}















/*
** Default HTML page header text through <body>.  If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] = 
@ <html>
@ <head>
@ <base href="$baseurl/$current_page" />
@ <meta http-equiv="Content-Security-Policy" \
@  content="default-src 'self' data: 'unsafe-inline'" />



@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \
@  href="$home/timeline.rss" />
@ <link rel="stylesheet" href="$stylesheet_url" type="text/css" \
@  media="screen" />
@ </head>
@ <body>
;




























/*
** Draw the header.
*/
void style_header(const char *zTitleFormat, ...){
  va_list ap;
  char *zTitle;







>
>
>
>
>
>
>
>
>
>
>
>
>
>











|
>
>
>








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
static void image_url_var(const char *zImageName){
  char *zVarPrefix = mprintf("%s_image", zImageName);
  char *zConfigName = mprintf("%s-image", zImageName);
  url_var(zVarPrefix, zConfigName, zImageName);
  free(zVarPrefix);
  free(zConfigName);
}

/*
** Return a random nonce that is stored in static space.  For a particular
** run, the same nonce is always returned.
*/
char *style_nonce(void){
  static char zNonce[52];
  if( zNonce[0]==0 ){
    unsigned char zSeed[24];
    sqlite3_randomness(24, zSeed);
    encode16(zSeed,(unsigned char*)zNonce,24);
  }
  return zNonce;
}

/*
** Default HTML page header text through <body>.  If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] = 
@ <html>
@ <head>
@ <base href="$baseurl/$current_page" />
@ <meta http-equiv="Content-Security-Policy" \
@  content="default-src 'self' data: ; \
@  script-src 'self' 'nonce-$<nonce>' ;\
@  style-src 'self' 'unsafe-inline'" />
@ <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \
@  href="$home/timeline.rss" />
@ <link rel="stylesheet" href="$stylesheet_url" type="text/css" \
@  media="screen" />
@ </head>
@ <body>
;

/*
** Initialize all the default TH1 variables
*/
static void style_init_th1_vars(const char *zTitle){
  Th_Store("nonce", style_nonce());
  Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
  Th_Store("project_description", db_get("project-description",""));
  if( zTitle ) Th_Store("title", zTitle);
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  Th_Store("index_page", db_get("index-page","/home"));
  if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
  Th_Store("current_page", local_zCurrentPage);
  Th_Store("csrf_token", g.zCsrfToken);
  Th_Store("release_version", RELEASE_VERSION);
  Th_Store("manifest_version", MANIFEST_VERSION);
  Th_Store("manifest_date", MANIFEST_DATE);
  Th_Store("compiler_name", COMPILER_NAME);
  url_var("stylesheet", "css", "style.css");
  image_url_var("logo");
  image_url_var("background");
  if( !login_is_nobody() ){
    Th_Store("login", g.zLogin);
  }
}

/*
** Draw the header.
*/
void style_header(const char *zTitleFormat, ...){
  va_list ap;
  char *zTitle;
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437





438
439
440
441
442
443
444
  cgi_destination(CGI_HEADER);

  @ <!DOCTYPE html>

  if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1);

  /* Generate the header up through the main menu */
  Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
  Th_Store("project_description", db_get("project-description",""));
  Th_Store("title", zTitle);
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  Th_Store("index_page", db_get("index-page","/home"));
  if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
  Th_Store("current_page", local_zCurrentPage);
  Th_Store("csrf_token", g.zCsrfToken);
  Th_Store("release_version", RELEASE_VERSION);
  Th_Store("manifest_version", MANIFEST_VERSION);
  Th_Store("manifest_date", MANIFEST_DATE);
  Th_Store("compiler_name", COMPILER_NAME);
  url_var("stylesheet", "css", "style.css");
  image_url_var("logo");
  image_url_var("background");
  if( !login_is_nobody() ){
    Th_Store("login", g.zLogin);
  }
  if( sqlite3_strlike("%<body%", zHeader, 0)!=0 ){
    Th_Render(zDfltHeader);
  }
  if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
  Th_Render(zHeader);
  if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1);
  Th_Unstore("title");   /* Avoid collisions with ticket field names */
  cgi_destination(CGI_BODY);
  g.cgiOutput = 1;
  headerHasBeenGenerated = 1;
  sideboxUsed = 0;





}

#if INTERFACE
/* Allowed parameters for style_adunit() */
#define ADUNIT_OFF        0x0001       /* Do not allow ads on this page */
#define ADUNIT_RIGHT_OK   0x0002       /* Right-side vertical ads ok here */
#endif







<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











>
>
>
>
>







448
449
450
451
452
453
454


455

















456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
  cgi_destination(CGI_HEADER);

  @ <!DOCTYPE html>

  if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1);

  /* Generate the header up through the main menu */


  style_init_th1_vars(zTitle);

















  if( sqlite3_strlike("%<body%", zHeader, 0)!=0 ){
    Th_Render(zDfltHeader);
  }
  if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
  Th_Render(zHeader);
  if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1);
  Th_Unstore("title");   /* Avoid collisions with ticket field names */
  cgi_destination(CGI_BODY);
  g.cgiOutput = 1;
  headerHasBeenGenerated = 1;
  sideboxUsed = 0;
  if( g.perm.Debug && P("showqp") ){
    @ <div class="debug">
    cgi_print_all(0, 0);
    @ </div>
  }
}

#if INTERFACE
/* Allowed parameters for style_adunit() */
#define ADUNIT_OFF        0x0001       /* Do not allow ads on this page */
#define ADUNIT_RIGHT_OK   0x0002       /* Right-side vertical ads ok here */
#endif
500
501
502
503
504
505
506




















507
508
509
510
511

512
513
514
515
516
517
518
519



520
521
522
523
524
525
526
527



















528
529
530
531
532
533
534

/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}





















/*
** Generate code to load all required javascript files.
*/
static void style_load_all_js_files(void){

  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover;
    /* Load up the page data */
    bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0))
                 && db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>



    style_load_one_js_file("href.js");
  }
  if( needSortJs ){
    style_load_one_js_file("sorttable.js");
  }
  if( needGraphJs ){
    style_load_one_js_file("graph.js");
  }



















}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>








>
>
>
|


|


|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611

/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
  @ <script src='%R/builtin/%s(zFile)?id=%S(MANIFEST_UUID)'></script>
}

/*
** All extra JS files to load.
*/
static const char *azJsToLoad[4];
static int nJsToLoad = 0;

/*
** Register a new JS file to load at the end of the document.
*/
void style_load_js(const char *zName){
  int i;
  for(i=0; i<nJsToLoad; i++){
    if( fossil_strcmp(zName, azJsToLoad[i])==0 ) return;
  }
  if( nJsToLoad>=sizeof(azJsToLoad)/sizeof(azJsToLoad[0]) ){
    fossil_panic("too many JS files");
  }
  azJsToLoad[nJsToLoad++] = zName;
}

/*
** Generate code to load all required javascript files.
*/
static void style_load_all_js_files(void){
  int i;
  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover;
    /* Load up the page data */
    bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0))
                 && db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">
  if( needHrefJs ){
    cgi_append_content(builtin_text("href.js"),-1);
  }
  if( needSortJs ){
    cgi_append_content(builtin_text("sorttable.js"),-1);
  }
  if( needGraphJs ){
    cgi_append_content(builtin_text("graph.js"),-1);
  }
  for(i=0; i<nJsToLoad; i++){
    cgi_append_content(builtin_text(azJsToLoad[i]),-1);
  }
  if( blob_size(&blobOnLoad)>0 ){
    @ window.onload = function(){
    cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
    cgi_append_content("\n}\n", -1);
  }
  @ </script>
}

/*
** Extra JS to run after all content is loaded.
*/
void style_js_onload(const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  blob_vappendf(&blobOnLoad, zFormat, ap);
  va_end(ap);
}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;
777
778
779
780
781
782
783



















784
785
786
787
788
789
790
  blob_read_from_file(&css, g.argv[2], ExtFILE);
  zSelector = g.argv[3];
  found = containsSelector(blob_str(&css), zSelector);
  fossil_print("%s %s\n", zSelector, found ? "found" : "not found");
  blob_reset(&css);
}





















/*
** WEBPAGE: style.css
**
** Return the style sheet.
*/
void page_style_css(void){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
  blob_read_from_file(&css, g.argv[2], ExtFILE);
  zSelector = g.argv[3];
  found = containsSelector(blob_str(&css), zSelector);
  fossil_print("%s %s\n", zSelector, found ? "found" : "not found");
  blob_reset(&css);
}

/*
** WEBPAGE: script.js
**
** Return the "Javascript" content for the current skin (if there is any)
*/
void page_script_js(void){
  const char *zScript = skin_get("js");
  if( P("test") ){
    /* Render the script as plain-text for testing purposes, if the "test"
    ** query parameter is present */
    cgi_set_content_type("text/plain");
  }else{
    /* Default behavior is to return javascript */
    cgi_set_content_type("application/javascript");
  }
  style_init_th1_vars(0);
  Th_Render(zScript?zScript:"");
}


/*
** WEBPAGE: style.css
**
** Return the style sheet.
*/
void page_style_css(void){
862
863
864
865
866
867
868

































869
870
871
872
873
874
875
876


877





















878
879


880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904













905
906
907
908
909


910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955





956





957
958
959
960
961
962
963

964
965
966




  }else{
    etag_check(0,0);
  }
  blob_init(&out, zTxt, -1);
  cgi_set_content(&out);
}



































/*
** WEBPAGE: test_env
**
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){


  char c;





















  int i;
  int showAll;


  char zCap[30];
  static const char *const azCgiVars[] = {
    "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
    "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
    "HTTP_CONNECTION", "HTTP_HOST",
    "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
    "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
    "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
    "REMOTE_USER", "REQUEST_METHOD",
    "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
    "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
    "SQLITE_TMPDIR", "TMPDIR",
    "TEMP", "TMP", "FOSSIL_VFS",
    "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
    "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
    "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST"
  };

  login_check_credentials();
  if( !g.perm.Admin && !g.perm.Setup && !db_get_boolean("test_env_enable",0) ){
    login_needed(0);
    return;
  }
  for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);













  style_header("Environment Test");
  showAll = PB("showall");
  style_submenu_checkbox("showall", "Cookies", 0, 0);
  style_submenu_element("Stats", "%R/stat");



#if !defined(_WIN32)
  @ uid=%d(getuid()), gid=%d(getgid())<br />
#endif
  @ g.zBaseURL = %h(g.zBaseURL)<br />
  @ g.zHttpsURL = %h(g.zHttpsURL)<br />
  @ g.zTop = %h(g.zTop)<br />
  @ g.zPath = %h(g.zPath)<br />
  for(i=0, c='a'; c<='z'; c++){
    if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  @ g.userUid = %d(g.userUid)<br />
  @ g.zLogin = %h(g.zLogin)<br />
  @ g.isHuman = %d(g.isHuman)<br />
  if( g.nRequest ){
    @ g.nRequest = %d(g.nRequest)<br />
  }
  if( g.nPendingRequest>1 ){
    @ g.nPendingRequest = %d(g.nPendingRequest)<br />
  }
  @ capabilities = %s(zCap)<br />
  for(i=0, c='a'; c<='z'; c++){
    if( login_has_capability(&c, 1, LOGIN_ANON)
         && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  if( i>0 ){
    @ anonymous-adds = %s(zCap)<br />
  }
  @ g.zRepositoryName = %h(g.zRepositoryName)<br />
  @ load_average() = %f(load_average())<br />
  @ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))<br />
  @ <hr />
  P("HTTP_USER_AGENT");
  cgi_print_all(showAll, 0);
  if( showAll && blob_size(&g.httpHeader)>0 ){
    @ <hr />
    @ <pre>
    @ %h(blob_str(&g.httpHeader))
    @ </pre>
  }
  if( g.perm.Setup ){
    const char *zRedir = P("redirect");
    if( zRedir ) cgi_redirect(zRedir);
  }
  style_footer();





  if( g.perm.Admin && P("err") ) fossil_fatal("%s", P("err"));





}

/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){

  cgi_set_status(403, "Forbidden");
  @ <p>Please enable javascript or log in to see this content</p>
}











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
>
|



















|
<
|


>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
>
>
|
|
|
|
|
|
|
<
<
<
<
|
|
|
|
|
|
|
|
|
|
<
<
<
<
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<


>
>
>
>
>
|
>
>
>
>
>



<
|

|
>
|
|

>
>
>
>
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054

1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084




1085
1086
1087
1088
1089
1090
1091
1092
1093
1094




1095

1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109



1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125

1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
  }else{
    etag_check(0,0);
  }
  blob_init(&out, zTxt, -1);
  cgi_set_content(&out);
}

/*
** All possible capabilities
*/
static const char allCap[] = 
  "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKL";

/*
** Compute the current login capabilities
*/
static char *find_capabilities(char *zCap){
  int i, j;
  char c;
  for(i=j=0; (c = allCap[j])!=0; j++){
    if( login_has_capability(&c, 1, 0) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  return zCap;
}

/*
** Compute the current login capabilities that were
** contributed by Anonymous
*/
static char *find_anon_capabilities(char *zCap){
  int i, j;
  char c;
  for(i=j=0; (c = allCap[j])!=0; j++){
    if( login_has_capability(&c, 1, LOGIN_ANON)
      && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  return zCap;
}

/*
** WEBPAGE: test_env
**
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){
  webpage_error("");
}

/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
  cgi_set_status(403, "Forbidden");
  @ <p>Please enable javascript or log in to see this content</p>
}

/*
** Webpages that encounter an error due to missing or incorrect
** query parameters can jump to this routine to render an error
** message screen.
**
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed.  Otherwise, just
** the error message is shown.
**
** If zFormat is an empty string, then this is the /test_env page.
*/
void webpage_error(const char *zFormat, ...){
  int i;
  int showAll;
  char *zErr = 0;
  int isAuth = 0;
  char zCap[100];
  static const char *const azCgiVars[] = {
    "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
    "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
    "HTTP_CONNECTION", "HTTP_HOST",
    "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
    "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
    "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
    "REMOTE_USER", "REQUEST_METHOD",
    "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
    "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
    "SQLITE_TMPDIR", "TMPDIR",
    "TEMP", "TMP", "FOSSIL_VFS",
    "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
    "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
    "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST"
  };

  login_check_credentials();
  if( g.perm.Admin || g.perm.Setup  || db_get_boolean("test_env_enable",0) ){

    isAuth = 1;
  }
  for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
  if( zFormat[0] ){
    va_list ap;
    va_start(ap, zFormat);
    zErr = vmprintf(zFormat, ap);
    va_end(ap);
    style_header("Bad Request");
    @ <h1>/%h(g.zPath): %h(zErr)</h1>
    showAll = 0;
    cgi_set_status(500, "Bad Request");
  }else if( !isAuth ){
    login_needed(0);
    return;
  }else{
    style_header("Environment Test");
    showAll = PB("showall");
    style_submenu_checkbox("showall", "Cookies", 0, 0);
    style_submenu_element("Stats", "%R/stat");
  }

  if( isAuth ){
  #if !defined(_WIN32)
    @ uid=%d(getuid()), gid=%d(getgid())<br />
  #endif
    @ g.zBaseURL = %h(g.zBaseURL)<br />
    @ g.zHttpsURL = %h(g.zHttpsURL)<br />
    @ g.zTop = %h(g.zTop)<br />
    @ g.zPath = %h(g.zPath)<br />




    @ g.userUid = %d(g.userUid)<br />
    @ g.zLogin = %h(g.zLogin)<br />
    @ g.isHuman = %d(g.isHuman)<br />
    if( g.nRequest ){
      @ g.nRequest = %d(g.nRequest)<br />
    }
    if( g.nPendingRequest>1 ){
      @ g.nPendingRequest = %d(g.nPendingRequest)<br />
    }
    @ capabilities = %s(find_capabilities(zCap))<br />




    if( zCap[0] ){

      @ anonymous-adds = %s(find_anon_capabilities(zCap))<br />
    }
    @ g.zRepositoryName = %h(g.zRepositoryName)<br />
    @ load_average() = %f(load_average())<br />
    @ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))<br />
    @ <hr />
    P("HTTP_USER_AGENT");
    cgi_print_all(showAll, 0);
    if( showAll && blob_size(&g.httpHeader)>0 ){
      @ <hr />
      @ <pre>
      @ %h(blob_str(&g.httpHeader))
      @ </pre>
    }



  }
  style_footer();
  if( zErr ){
    cgi_reply();
    fossil_exit(1);
  }
}

/*
** Generate a Not Yet Implemented error page.
*/
void webpage_not_yet_implemented(void){
  webpage_error("Not yet implemented");
}

/*

** Generate a webpage for a webpage_assert().
*/
void webpage_assert_page(const char *zFile, int iLine, const char *zExpr){
  fossil_warning("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
  cgi_reset_content();
  webpage_error("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
}

#if INTERFACE
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
#endif

Changes to src/th_main.c.

1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
  Th_SetResult(interp, 0, 0);
  return rc;
}

/*
** TH1 command: styleHeader TITLE
**
** Render the configured style header.
*/
static int styleHeaderCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl







|







1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
  Th_SetResult(interp, 0, 0);
  return rc;
}

/*
** TH1 command: styleHeader TITLE
**
** Render the configured style header for the selected skin.
*/
static int styleHeaderCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304




























1305
1306
1307
1308
1309
1310
1311
    return TH_ERROR;
  }
}

/*
** TH1 command: styleFooter
**
** Render the configured style footer.
*/
static int styleFooterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=1 ){
    return Th_WrongNumArgs(interp, "styleFooter");
  }
  if( Th_IsRepositoryOpen() ){
    style_footer();
    Th_SetResult(interp, 0, 0);
    return TH_OK;
  }else{
    Th_SetResult(interp, "repository unavailable", -1);
    return TH_ERROR;
  }
}





























/*
** TH1 command: artifact ID ?FILENAME?
**
** Attempts to locate the specified artifact and return its contents.  An
** error is generated if the repository is not open or the artifact cannot
** be found.







|




















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
    return TH_ERROR;
  }
}

/*
** TH1 command: styleFooter
**
** Render the configured style footer for the selected skin.
*/
static int styleFooterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=1 ){
    return Th_WrongNumArgs(interp, "styleFooter");
  }
  if( Th_IsRepositoryOpen() ){
    style_footer();
    Th_SetResult(interp, 0, 0);
    return TH_OK;
  }else{
    Th_SetResult(interp, "repository unavailable", -1);
    return TH_ERROR;
  }
}

/*
** TH1 command: styleScript
**
** Render the configured JavaScript for the selected skin.
*/
static int styleScriptCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=1 ){
    return Th_WrongNumArgs(interp, "styleScript");
  }
  if( Th_IsRepositoryOpen() ){
    const char *zScript = skin_get("js");
    if( zScript==0 ) zScript = "";
    Th_Render(zScript);
    Th_SetResult(interp, 0, 0);
    return TH_OK;
  }else{
    Th_SetResult(interp, "repository unavailable", -1);
    return TH_ERROR;
  }
}


/*
** TH1 command: artifact ID ?FILENAME?
**
** Attempts to locate the specified artifact and return its contents.  An
** error is generated if the repository is not open or the artifact cannot
** be found.
1982
1983
1984
1985
1986
1987
1988

1989
1990
1991
1992
1993
1994
1995
1996
1997
    {"regexp",        regexpCmd,            0},
    {"reinitialize",  reinitializeCmd,      0},
    {"render",        renderCmd,            0},
    {"repository",    repositoryCmd,        0},
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},

    {"styleHeader",   styleHeaderCmd,       0},
    {"styleFooter",   styleFooterCmd,       0},
    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},
    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},







>

|







2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
    {"regexp",        regexpCmd,            0},
    {"reinitialize",  reinitializeCmd,      0},
    {"render",        renderCmd,            0},
    {"repository",    repositoryCmd,        0},
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},
    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},

Changes to src/timeline.c.

354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    **   (2)  YYYY-MM-DD HH:MM
    **   (3)  YYMMDD HH:MM
    **   (4)  (off)
    */
    if( dateFormat<2 ){
      if( fossil_strnicmp(zDate, zPrevDate, 10) ){
        sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
        @ <tr><td>
        @   <div class="divider timelineDate">%s(zPrevDate)</div>
        @ </td><td></td><td></td></tr>
      }
      memcpy(zTime, &zDate[11], 5+dateFormat*3);
      zTime[5+dateFormat*3] = 0;
    }else if( 2==dateFormat ){
      /* YYYY-MM-DD HH:MM */







|







354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    **   (2)  YYYY-MM-DD HH:MM
    **   (3)  YYMMDD HH:MM
    **   (4)  (off)
    */
    if( dateFormat<2 ){
      if( fossil_strnicmp(zDate, zPrevDate, 10) ){
        sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
        @ <tr class="timelineDateRow"><td>
        @   <div class="divider timelineDate">%s(zPrevDate)</div>
        @ </td><td></td><td></td></tr>
      }
      memcpy(zTime, &zDate[11], 5+dateFormat*3);
      zTime[5+dateFormat*3] = 0;
    }else if( 2==dateFormat ){
      /* YYYY-MM-DD HH:MM */
458
459
460
461
462
463
464

465
466
467
468
469
470
471
472
      @ &bull;
    }
    if( modPending ){
      @ <span class="modpending">(Awaiting Moderator Approval)</span>
    }
    if( (tmFlags & TIMELINE_BISECT)!=0 && zType[0]=='c' ){
      static Stmt bisectQuery;

      db_prepare(&bisectQuery, "SELECT seq, stat FROM bilog WHERE rid=:rid");
      db_bind_int(&bisectQuery, ":rid", rid);
      if( db_step(&bisectQuery)==SQLITE_ROW ){
        @ <b>%s(db_column_text(&bisectQuery,1))</b>
        @ (%d(db_column_int(&bisectQuery,0)))
      }
      db_reset(&bisectQuery);
    }







>
|







458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
      @ &bull;
    }
    if( modPending ){
      @ <span class="modpending">(Awaiting Moderator Approval)</span>
    }
    if( (tmFlags & TIMELINE_BISECT)!=0 && zType[0]=='c' ){
      static Stmt bisectQuery;
      db_static_prepare(&bisectQuery,
          "SELECT seq, stat FROM bilog WHERE rid=:rid");
      db_bind_int(&bisectQuery, ":rid", rid);
      if( db_step(&bisectQuery)==SQLITE_ROW ){
        @ <b>%s(db_column_text(&bisectQuery,1))</b>
        @ (%d(db_column_int(&bisectQuery,0)))
      }
      db_reset(&bisectQuery);
    }
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
        cgi_printf("check-in:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }else if( zType[0]=='e' && tagid ){
        cgi_printf("technote:&nbsp;");
        hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
      }else{
        cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }
    }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' ){
      cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
    }

    if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
      char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd&n=200", zDispUser, zDate);
      cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
    }else{







|







573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
        cgi_printf("check-in:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }else if( zType[0]=='e' && tagid ){
        cgi_printf("technote:&nbsp;");
        hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
      }else{
        cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }
    }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' || zType[0]=='f'){
      cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
    }

    if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
      char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd&n=200", zDispUser, zDate);
      cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
    }else{
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
/*
** Add the select/option box to the timeline submenu that is used to
** set the y= parameter that determines which elements to display
** on the timeline.
*/
static void timeline_y_submenu(int isDisabled){
  static int i = 0;
  static const char *az[12];
  if( i==0 ){
    az[0] = "all";
    az[1] = "Any Type";
    i = 2;
    if( g.perm.Read ){
      az[i++] = "ci";
      az[i++] = "Check-ins";







|







1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
/*
** Add the select/option box to the timeline submenu that is used to
** set the y= parameter that determines which elements to display
** on the timeline.
*/
static void timeline_y_submenu(int isDisabled){
  static int i = 0;
  static const char *az[14];
  if( i==0 ){
    az[0] = "all";
    az[1] = "Any Type";
    i = 2;
    if( g.perm.Read ){
      az[i++] = "ci";
      az[i++] = "Check-ins";
1051
1052
1053
1054
1055
1056
1057




1058
1059
1060
1061
1062
1063
1064
    if( g.perm.RdTkt ){
      az[i++] = "t";
      az[i++] = "Tickets";
    }
    if( g.perm.RdWiki ){
      az[i++] = "w";
      az[i++] = "Wiki";




    }
    assert( i<=count(az) );
  }
  if( i>2 ){
    style_submenu_multichoice("y", i/2, az, isDisabled);
  }
}







>
>
>
>







1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
    if( g.perm.RdTkt ){
      az[i++] = "t";
      az[i++] = "Tickets";
    }
    if( g.perm.RdWiki ){
      az[i++] = "w";
      az[i++] = "Wiki";
    }
    if( g.perm.RdForum ){
      az[i++] = "f";
      az[i++] = "Forum";
    }
    assert( i<=count(az) );
  }
  if( i>2 ){
    style_submenu_multichoice("y", i/2, az, isDisabled);
  }
}
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
**    dp=CHECKIN      The same as d=CHECKIN&p=CHECKIN
**    t=TAG           Show only check-ins with the given TAG
**    r=TAG           Show check-ins related to TAG, equivalent to t=TAG&rel
**    rel             Show related check-ins as well as those matching t=TAG
**    mionly          Limit rel to show ancestors but not descendants
**    ms=MATCHSTYLE   Set tag match style to EXACT, GLOB, LIKE, REGEXP
**    u=USER          Only show items associated with USER
**    y=TYPE          'ci', 'w', 't', 'e', or 'all'.
**    ss=VIEWSTYLE    c: "Compact"  v: "Verbose"   m: "Modern"  j: "Columnar"
**    advm            Use the "Advanced" or "Busy" menu design.
**    ng              No Graph.
**    nd              Do not highlight the focus check-in
**    v               Show details of files changed
**    f=CHECKIN       Show family (immediate parents and children) of CHECKIN
**    from=CHECKIN    Path from...







|







1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
**    dp=CHECKIN      The same as d=CHECKIN&p=CHECKIN
**    t=TAG           Show only check-ins with the given TAG
**    r=TAG           Show check-ins related to TAG, equivalent to t=TAG&rel
**    rel             Show related check-ins as well as those matching t=TAG
**    mionly          Limit rel to show ancestors but not descendants
**    ms=MATCHSTYLE   Set tag match style to EXACT, GLOB, LIKE, REGEXP
**    u=USER          Only show items associated with USER
**    y=TYPE          'ci', 'w', 't', 'e', 'f', or 'all'.
**    ss=VIEWSTYLE    c: "Compact"  v: "Verbose"   m: "Modern"  j: "Columnar"
**    advm            Use the "Advanced" or "Busy" menu design.
**    ng              No Graph.
**    nd              Do not highlight the focus check-in
**    v               Show details of files changed
**    f=CHECKIN       Show family (immediate parents and children) of CHECKIN
**    from=CHECKIN    Path from...
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
  /* To view the timeline, must have permission to read project data.
  */
  pd_rid = name_to_typed_rid(P("dp"),"ci");
  if( pd_rid ){
    p_rid = d_rid = pd_rid;
  }
  login_check_credentials();
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki)
   || (bisectOnly && !g.perm.Setup)
  ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  cookie_read_parameter("y","y");
  zType = P("y");







|







1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
  /* To view the timeline, must have permission to read project data.
  */
  pd_rid = name_to_typed_rid(P("dp"),"ci");
  if( pd_rid ){
    p_rid = d_rid = pd_rid;
  }
  login_check_credentials();
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
   || (bisectOnly && !g.perm.Setup)
  ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  cookie_read_parameter("y","y");
  zType = P("y");
1852
1853
1854
1855
1856
1857
1858

1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876




1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891


1892
1893
1894
1895
1896
1897
1898
      blob_append_sql(&cond, ")");
    }
    if( (zType[0]=='w' && !g.perm.RdWiki)
     || (zType[0]=='t' && !g.perm.RdTkt)
     || (zType[0]=='e' && !g.perm.RdWiki)
     || (zType[0]=='c' && !g.perm.Read)
     || (zType[0]=='g' && !g.perm.Read)

    ){
      zType = "all";
    }
    if( zType[0]=='a' ){
      if( !g.perm.Read || !g.perm.RdWiki || !g.perm.RdTkt ){
        char cSep = '(';
        blob_append_sql(&cond, " AND event.type IN ");
        if( g.perm.Read ){
          blob_append_sql(&cond, "%c'ci','g'", cSep);
          cSep = ',';
        }
        if( g.perm.RdWiki ){
          blob_append_sql(&cond, "%c'w','e'", cSep);
          cSep = ',';
        }
        if( g.perm.RdTkt ){
          blob_append_sql(&cond, "%c't'", cSep);
          cSep = ',';




        }
        blob_append_sql(&cond, ")");
      }
    }else{ /* zType!="all" */
      blob_append_sql(&cond, " AND event.type=%Q", zType);
      if( zType[0]=='c' ){
        zEType = "check-in";
      }else if( zType[0]=='w' ){
        zEType = "wiki";
      }else if( zType[0]=='t' ){
        zEType = "ticket change";
      }else if( zType[0]=='e' ){
        zEType = "technical note";
      }else if( zType[0]=='g' ){
        zEType = "tag";


      }
    }
    if( zUser ){
      int n = db_int(0,"SELECT count(*) FROM event"
                       " WHERE user=%Q OR euser=%Q", zUser, zUser);
      if( n<=nEntry ){
        zCirca = zBefore = zAfter = 0;







>


















>
>
>
>















>
>







1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
      blob_append_sql(&cond, ")");
    }
    if( (zType[0]=='w' && !g.perm.RdWiki)
     || (zType[0]=='t' && !g.perm.RdTkt)
     || (zType[0]=='e' && !g.perm.RdWiki)
     || (zType[0]=='c' && !g.perm.Read)
     || (zType[0]=='g' && !g.perm.Read)
     || (zType[0]=='f' && !g.perm.RdForum)
    ){
      zType = "all";
    }
    if( zType[0]=='a' ){
      if( !g.perm.Read || !g.perm.RdWiki || !g.perm.RdTkt ){
        char cSep = '(';
        blob_append_sql(&cond, " AND event.type IN ");
        if( g.perm.Read ){
          blob_append_sql(&cond, "%c'ci','g'", cSep);
          cSep = ',';
        }
        if( g.perm.RdWiki ){
          blob_append_sql(&cond, "%c'w','e'", cSep);
          cSep = ',';
        }
        if( g.perm.RdTkt ){
          blob_append_sql(&cond, "%c't'", cSep);
          cSep = ',';
        }
        if( g.perm.RdForum ){
          blob_append_sql(&cond, "%c'f'", cSep);
          cSep = ',';
        }
        blob_append_sql(&cond, ")");
      }
    }else{ /* zType!="all" */
      blob_append_sql(&cond, " AND event.type=%Q", zType);
      if( zType[0]=='c' ){
        zEType = "check-in";
      }else if( zType[0]=='w' ){
        zEType = "wiki";
      }else if( zType[0]=='t' ){
        zEType = "ticket change";
      }else if( zType[0]=='e' ){
        zEType = "technical note";
      }else if( zType[0]=='g' ){
        zEType = "tag";
      }else if( zType[0]=='f' ){
        zEType = "forum post";
      }
    }
    if( zUser ){
      int n = db_int(0,"SELECT count(*) FROM event"
                       " WHERE user=%Q OR euser=%Q", zUser, zUser);
      if( n<=nEntry ){
        zCirca = zBefore = zAfter = 0;

Changes to src/unicode.c.

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122
123
124
125
126

127
128
129
130
131
132
133

134
135
136
137
138
139

140
141
142
143
144
145
146
147
    0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01,
    0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401,
    0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01,
    0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163403,
    0x00164437, 0x0017CC02, 0x0018001D, 0x00187802, 0x00192C15,
    0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07,
    0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B,
    0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804, 0x00206C09,
    0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, 0x00217801,
    0x00235030, 0x0024E803, 0x0024F812, 0x00254407, 0x00258804,
    0x0025C001, 0x00260403, 0x0026F001, 0x0026F807, 0x00271C02,
    0x00272C03, 0x00275C01, 0x00278802, 0x0027C802, 0x0027E802,
    0x0027F401, 0x00280403, 0x0028F001, 0x0028F805, 0x00291C02,
    0x00292C03, 0x00294401, 0x0029C002, 0x0029D401, 0x002A0403,
    0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03, 0x002B8802,
    0x002BC002, 0x002BE806, 0x002C0403, 0x002CF001, 0x002CF807,
    0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001,
    0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01,
    0x002FCC08, 0x00300004, 0x0030F807, 0x00311803, 0x00312804,
    0x00315402, 0x00318802, 0x0031FC01, 0x00320403, 0x0032F001,
    0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802,
    0x00340004, 0x0034EC02, 0x0034F807, 0x00351803, 0x00352804,
    0x00353C01, 0x00355C01, 0x00358802, 0x0035E401, 0x00360802,
    0x00372801, 0x00373C06, 0x00375801, 0x00376008, 0x0037C803,
    0x0038C401, 0x0038D007, 0x0038FC01, 0x00391C09, 0x00396802,
    0x003AC401, 0x003AD006, 0x003AEC02, 0x003B2006, 0x003C041F,
    0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424, 0x003EF80F,
    0x003F380D, 0x0040AC14, 0x00412806, 0x00415804, 0x00417803,
    0x00418803, 0x00419C07, 0x0041C404, 0x0042080C, 0x00423C01,
    0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A, 0x00500001,
    0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03, 0x005C4803,
    0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023, 0x005F6004,
    0x005F7401, 0x0060000F, 0x00621402, 0x0062A401, 0x0064800C,
    0x0064C00C, 0x00650001, 0x00651002, 0x00677822, 0x00685C05,
    0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007,
    0x006AA006, 0x006AC00F, 0x006C0005, 0x006CD011, 0x006D6823,
    0x006E0003, 0x006E840D, 0x006F980E, 0x006FF004, 0x00709014,
    0x0070EC05, 0x0071F802, 0x00730008, 0x00734019, 0x0073B401,
    0x0073C803, 0x0073DC03, 0x0077003A, 0x0077EC05, 0x007EF401,
    0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403, 0x007FF402,
    0x00800065, 0x0081980A, 0x0081E805, 0x00822805, 0x00828020,
    0x00834021, 0x00840002, 0x00840C04, 0x00842002, 0x00845001,
    0x00845803, 0x00847806, 0x00849401, 0x00849C01, 0x0084A401,
    0x0084B801, 0x0084E802, 0x00850005, 0x00852804, 0x00853C01,
    0x00862802, 0x00864297, 0x0091000B, 0x0092704E, 0x00940276,
    0x009E53E0, 0x00ADD820, 0x00AE6022, 0x00AEF40C, 0x00AF2809,
    0x00AFB004, 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802,
    0x00B5C001, 0x00B5FC01, 0x00B7804F, 0x00B8C01A, 0x00BA001A,
    0x00BA6C59, 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019,
    0x00C0A807, 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001,
    0x00C3EC01, 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F,
    0x00C8A81E, 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F,
    0x00CC0100, 0x01370040, 0x02924037, 0x0293F802, 0x02983403,
    0x0299BC10, 0x029A7802, 0x029BC008, 0x029C0017, 0x029C8002,
    0x029E2402, 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09,
    0x02A0D804, 0x02A1D004, 0x02A20002, 0x02A2D012, 0x02A33802,
    0x02A38012, 0x02A3E003, 0x02A3F001, 0x02A4980A, 0x02A51C0D,
    0x02A57C01, 0x02A60004, 0x02A6CC1B, 0x02A77802, 0x02A79401,
    0x02A8A40E, 0x02A90C01, 0x02A93002, 0x02A97004, 0x02A9DC03,
    0x02A9EC03, 0x02AAC001, 0x02AAC803, 0x02AADC02, 0x02AAF802,
    0x02AB0401, 0x02AB7802, 0x02ABAC07, 0x02ABD402, 0x02AD6C01,
    0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01,
    0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002,
    0x03F8001A, 0x03F88033, 0x03F95013, 0x03F9A004, 0x03FBFC01,
    0x03FC040F, 0x03FC6807, 0x03FCEC06, 0x03FD6C0B, 0x03FF8007,
    0x03FFA007, 0x03FFE405, 0x04040003, 0x0404DC09, 0x0405E411,
    0x04063003, 0x0406400C, 0x04068001, 0x0407402E, 0x040B8001,
    0x040DD805, 0x040E7C01, 0x040F4001, 0x0415BC01, 0x04215C01,
    0x0421DC02, 0x04247C01, 0x0424FC01, 0x04280403, 0x04281402,
    0x04283004, 0x0428E003, 0x0428FC01, 0x04294009, 0x0429FC01,
    0x042B2001, 0x042B9402, 0x042BC007, 0x042CE407, 0x042E6404,

    0x04400003, 0x0440E016, 0x0441FC04, 0x0442C012, 0x04440003,
    0x04449C0E, 0x04450004, 0x0445CC03, 0x04460003, 0x0446CC0E,
    0x04471409, 0x04476C01, 0x04477403, 0x0448B013, 0x044AA401,
    0x044B7C0C, 0x044C0004, 0x044CF001, 0x044CF807, 0x044D1C02,
    0x044D2C03, 0x044D5C01, 0x044D8802, 0x044D9807, 0x044DC005,
    0x0450D412, 0x04512C05, 0x04516C01, 0x04517401, 0x0452C014,
    0x04531801, 0x0456BC07, 0x0456E020, 0x04577002, 0x0458C014,
    0x0459800D, 0x045AAC0D, 0x045C740F, 0x045CF004, 0x0468040A,
    0x0468CC07, 0x0468EC0D, 0x0469440B, 0x046A2813, 0x046A7805,
    0x0470BC08, 0x0470E008, 0x04710405, 0x0471C002, 0x04724816,
    0x0472A40E, 0x0474C406, 0x0474E801, 0x0474F002, 0x0474FC07,

    0x04751C01, 0x0491C005, 0x05A9B802, 0x05ABC006, 0x05ACC010,
    0x05AD1002, 0x05BD442E, 0x05BE3C04, 0x06F27008, 0x074000F6,
    0x07440027, 0x0744A4C0, 0x07480046, 0x074C0057, 0x075B0401,
    0x075B6C01, 0x075BEC01, 0x075C5401, 0x075CD401, 0x075D3C01,
    0x075DBC01, 0x075E2401, 0x075EA401, 0x075F0C01, 0x0760028C,
    0x076A6C05, 0x076A840F, 0x07800007, 0x07802011, 0x07806C07,
    0x07808C02, 0x07809805, 0x07A34007, 0x07A51007, 0x07A57802,

    0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40F,
    0x07C3040F, 0x07C34425, 0x07C4401F, 0x07C4C03C, 0x07C5C03D,
    0x07C7981D, 0x07C8402C, 0x07C90009, 0x07C94002, 0x07C98006,
    0x07CC03D5, 0x07DB800D, 0x07DBC009, 0x07DC0074, 0x07DE0055,
    0x07E0000C, 0x07E04038, 0x07E1400A, 0x07E18028, 0x07E2401E,
    0x07E4000C, 0x07E4402F, 0x07E5000D, 0x07E5401C, 0x07E60018,

    0x07E70001, 0x07E74017, 0x38000401, 0x38008060, 0x380400F0,
  };
  static const unsigned int aAscii[4] = {
    0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
  };

  if( (unsigned int)c<128 ){
    return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 );







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|














>
|
|

|

|

|
|
|
|
>
|
|
|
|
|
|
|
>
|
<
|
|
|
|
>
|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
146
147
148
149
150
    0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01,
    0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401,
    0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01,
    0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163403,
    0x00164437, 0x0017CC02, 0x0018001D, 0x00187802, 0x00192C15,
    0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07,
    0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B,
    0x001E980B, 0x001FAC09, 0x001FD804, 0x001FF403, 0x00205804,
    0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403,
    0x00217801, 0x00234C31, 0x0024E803, 0x0024F812, 0x00254407,
    0x00258804, 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807,
    0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802,
    0x0027E802, 0x0027F402, 0x00280403, 0x0028F001, 0x0028F805,
    0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D402,
    0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
    0x002B8802, 0x002BC002, 0x002BE806, 0x002C0403, 0x002CF001,
    0x002CF807, 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802,
    0x002DC001, 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804,
    0x002F5C01, 0x002FCC08, 0x00300005, 0x0030F807, 0x00311803,
    0x00312804, 0x00315402, 0x00318802, 0x0031FC01, 0x00320404,
    0x0032F001, 0x0032F807, 0x00331803, 0x00332804, 0x00335402,
    0x00338802, 0x00340004, 0x0034EC02, 0x0034F807, 0x00351803,
    0x00352804, 0x00353C01, 0x00355C01, 0x00358802, 0x0035E401,
    0x00360802, 0x00372801, 0x00373C06, 0x00375801, 0x00376008,
    0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01, 0x00391C09,
    0x00396802, 0x003AC401, 0x003AD006, 0x003AEC02, 0x003B2006,
    0x003C041F, 0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424,
    0x003EF80F, 0x003F380D, 0x0040AC14, 0x00412806, 0x00415804,
    0x00417803, 0x00418803, 0x00419C07, 0x0041C404, 0x0042080C,
    0x00423C01, 0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A,
    0x00500001, 0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03,
    0x005C4803, 0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023,
    0x005F6004, 0x005F7401, 0x0060000F, 0x00621402, 0x0062A401,
    0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, 0x00677822,
    0x00685C05, 0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01,
    0x006A8007, 0x006AA006, 0x006AC00F, 0x006C0005, 0x006CD011,
    0x006D6823, 0x006E0003, 0x006E840D, 0x006F980E, 0x006FF004,
    0x00709014, 0x0070EC05, 0x0071F802, 0x00730008, 0x00734019,
    0x0073B401, 0x0073C803, 0x0073DC03, 0x0077003A, 0x0077EC05,
    0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403,
    0x007FF402, 0x00800065, 0x0081980A, 0x0081E805, 0x00822805,
    0x00828020, 0x00834021, 0x00840002, 0x00840C04, 0x00842002,
    0x00845001, 0x00845803, 0x00847806, 0x00849401, 0x00849C01,
    0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, 0x00852804,
    0x00853C01, 0x00862802, 0x00864297, 0x0091000B, 0x0092704E,
    0x00940276, 0x009E53E0, 0x00ADD820, 0x00AE6031, 0x00AF2835,
    0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001,
    0x00B5FC01, 0x00B7804F, 0x00B8C01F, 0x00BA001A, 0x00BA6C59,
    0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807,
    0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01,
    0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E,
    0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100,
    0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10,
    0x029A7802, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402,
    0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804,
    0x02A1D004, 0x02A20002, 0x02A2D012, 0x02A33802, 0x02A38012,
    0x02A3E003, 0x02A3F001, 0x02A3FC01, 0x02A4980A, 0x02A51C0D,
    0x02A57C01, 0x02A60004, 0x02A6CC1B, 0x02A77802, 0x02A79401,
    0x02A8A40E, 0x02A90C01, 0x02A93002, 0x02A97004, 0x02A9DC03,
    0x02A9EC03, 0x02AAC001, 0x02AAC803, 0x02AADC02, 0x02AAF802,
    0x02AB0401, 0x02AB7802, 0x02ABAC07, 0x02ABD402, 0x02AD6C01,
    0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01,
    0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002,
    0x03F8001A, 0x03F88033, 0x03F95013, 0x03F9A004, 0x03FBFC01,
    0x03FC040F, 0x03FC6807, 0x03FCEC06, 0x03FD6C0B, 0x03FF8007,
    0x03FFA007, 0x03FFE405, 0x04040003, 0x0404DC09, 0x0405E411,
    0x04063003, 0x0406400C, 0x04068001, 0x0407402E, 0x040B8001,
    0x040DD805, 0x040E7C01, 0x040F4001, 0x0415BC01, 0x04215C01,
    0x0421DC02, 0x04247C01, 0x0424FC01, 0x04280403, 0x04281402,
    0x04283004, 0x0428E003, 0x0428FC01, 0x04294009, 0x0429FC01,
    0x042B2001, 0x042B9402, 0x042BC007, 0x042CE407, 0x042E6404,
    0x04349004, 0x043D180B, 0x043D5405, 0x04400003, 0x0440E016,
    0x0441FC04, 0x0442C012, 0x04433401, 0x04440003, 0x04449C0E,
    0x04450004, 0x04451402, 0x0445CC03, 0x04460003, 0x0446CC0E,
    0x04471409, 0x04476C01, 0x04477403, 0x0448B013, 0x044AA401,
    0x044B7C0C, 0x044C0004, 0x044CEC02, 0x044CF807, 0x044D1C02,
    0x044D2C03, 0x044D5C01, 0x044D8802, 0x044D9807, 0x044DC005,
    0x0450D412, 0x04512C05, 0x04516C01, 0x04517402, 0x0452C014,
    0x04531801, 0x0456BC07, 0x0456E020, 0x04577002, 0x0458C014,
    0x0459800D, 0x045AAC0D, 0x045C740F, 0x045CF004, 0x0460B010,
    0x0468040A, 0x0468CC07, 0x0468EC0D, 0x0469440B, 0x046A2813,
    0x046A7805, 0x0470BC08, 0x0470E008, 0x04710405, 0x0471C002,
    0x04724816, 0x0472A40E, 0x0474C406, 0x0474E801, 0x0474F002,
    0x0474FC07, 0x04751C01, 0x04762805, 0x04764002, 0x04764C05,
    0x047BCC06, 0x0491C005, 0x05A9B802, 0x05ABC006, 0x05ACC010,
    0x05AD1002, 0x05BA5C04, 0x05BD442E, 0x05BE3C04, 0x06F27008,
    0x074000F6, 0x07440027, 0x0744A4C0, 0x07480046, 0x074C0057,
    0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401, 0x075CD401,
    0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401, 0x075F0C01,
    0x0760028C, 0x076A6C05, 0x076A840F, 0x07800007, 0x07802011,
    0x07806C07, 0x07808C02, 0x07809805, 0x07A34007, 0x07A51007,
    0x07A57802, 0x07B2B001, 0x07B2C001, 0x07BBC002, 0x07C0002C,
    0x07C0C064, 0x07C2800F, 0x07C2C40F, 0x07C3040F, 0x07C34425,

    0x07C4405C, 0x07C5C03D, 0x07C7981D, 0x07C8402C, 0x07C90009,
    0x07C94002, 0x07C98006, 0x07CC03D5, 0x07DB800D, 0x07DBC00A,
    0x07DC0074, 0x07DE0059, 0x07E0000C, 0x07E04038, 0x07E1400A,
    0x07E18028, 0x07E2401E, 0x07E4000C, 0x07E4402F, 0x07E50031,
    0x07E5CC04, 0x07E5E801, 0x07E5F027, 0x07E6C00A, 0x07E70003,
    0x07E74030, 0x07E9800E, 0x38000401, 0x38008060, 0x380400F0,
  };
  static const unsigned int aAscii[4] = {
    0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
  };

  if( (unsigned int)c<128 ){
    return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 );
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
  static const struct TableEntry {
    unsigned short iCode;
    unsigned char flags;
    unsigned char nRange;
  } aEntry[] = {
    {65, 14, 26},          {181, 66, 1},          {192, 14, 23},
    {216, 14, 7},          {256, 1, 48},          {306, 1, 6},
    {313, 1, 16},          {330, 1, 46},          {376, 150, 1},
    {377, 1, 6},           {383, 138, 1},         {385, 52, 1},
    {386, 1, 4},           {390, 46, 1},          {391, 0, 1},
    {393, 44, 2},          {395, 0, 1},           {398, 34, 1},
    {399, 40, 1},          {400, 42, 1},          {401, 0, 1},
    {403, 44, 1},          {404, 48, 1},          {406, 54, 1},
    {407, 50, 1},          {408, 0, 1},           {412, 54, 1},
    {413, 56, 1},          {415, 58, 1},          {416, 1, 6},
    {422, 62, 1},          {423, 0, 1},           {425, 62, 1},
    {428, 0, 1},           {430, 62, 1},          {431, 0, 1},
    {433, 60, 2},          {435, 1, 4},           {439, 64, 1},
    {440, 0, 1},           {444, 0, 1},           {452, 2, 1},
    {453, 0, 1},           {455, 2, 1},           {456, 0, 1},
    {458, 2, 1},           {459, 1, 18},          {478, 1, 18},
    {497, 2, 1},           {498, 1, 4},           {502, 156, 1},
    {503, 168, 1},         {504, 1, 40},          {544, 144, 1},
    {546, 1, 18},          {570, 74, 1},          {571, 0, 1},
    {573, 142, 1},         {574, 72, 1},          {577, 0, 1},
    {579, 140, 1},         {580, 30, 1},          {581, 32, 1},
    {582, 1, 10},          {837, 38, 1},          {880, 1, 4},
    {886, 0, 1},           {895, 38, 1},          {902, 20, 1},
    {904, 18, 3},          {908, 28, 1},          {910, 26, 2},
    {913, 14, 17},         {931, 14, 9},          {962, 0, 1},
    {975, 4, 1},           {976, 174, 1},         {977, 176, 1},
    {981, 180, 1},         {982, 178, 1},         {984, 1, 24},
    {1008, 170, 1},        {1009, 172, 1},        {1012, 164, 1},
    {1013, 162, 1},        {1015, 0, 1},          {1017, 186, 1},
    {1018, 0, 1},          {1021, 144, 3},        {1024, 36, 16},
    {1040, 14, 32},        {1120, 1, 34},         {1162, 1, 54},
    {1216, 6, 1},          {1217, 1, 14},         {1232, 1, 96},
    {1329, 24, 38},        {4256, 70, 38},        {4295, 70, 1},
    {4301, 70, 1},         {5112, 184, 6},        {7296, 122, 1},
    {7297, 124, 1},        {7298, 126, 1},        {7299, 130, 2},
    {7301, 128, 1},        {7302, 132, 1},        {7303, 134, 1},

    {7304, 96, 1},         {7680, 1, 150},        {7835, 166, 1},
    {7838, 116, 1},        {7840, 1, 96},         {7944, 184, 8},
    {7960, 184, 6},        {7976, 184, 8},        {7992, 184, 8},
    {8008, 184, 6},        {8025, 185, 8},        {8040, 184, 8},
    {8072, 184, 8},        {8088, 184, 8},        {8104, 184, 8},
    {8120, 184, 2},        {8122, 160, 2},        {8124, 182, 1},
    {8126, 120, 1},        {8136, 158, 4},        {8140, 182, 1},
    {8152, 184, 2},        {8154, 154, 2},        {8168, 184, 2},
    {8170, 152, 2},        {8172, 186, 1},        {8184, 146, 2},
    {8186, 148, 2},        {8188, 182, 1},        {8486, 118, 1},
    {8490, 112, 1},        {8491, 114, 1},        {8498, 12, 1},
    {8544, 8, 16},         {8579, 0, 1},          {9398, 10, 26},
    {11264, 24, 47},       {11360, 0, 1},         {11362, 108, 1},
    {11363, 136, 1},       {11364, 110, 1},       {11367, 1, 6},
    {11373, 104, 1},       {11374, 106, 1},       {11375, 100, 1},
    {11376, 102, 1},       {11378, 0, 1},         {11381, 0, 1},
    {11390, 98, 2},        {11392, 1, 100},       {11499, 1, 4},
    {11506, 0, 1},         {42560, 1, 46},        {42624, 1, 28},
    {42786, 1, 14},        {42802, 1, 62},        {42873, 1, 4},
    {42877, 94, 1},        {42878, 1, 10},        {42891, 0, 1},
    {42893, 86, 1},        {42896, 1, 4},         {42902, 1, 20},
    {42922, 80, 1},        {42923, 76, 1},        {42924, 78, 1},
    {42925, 82, 1},        {42926, 80, 1},        {42928, 90, 1},
    {42929, 84, 1},        {42930, 88, 1},        {42931, 68, 1},
    {42932, 1, 4},         {43888, 92, 80},       {65313, 14, 26},
  };
  static const unsigned short aiOff[] = {
   1,     2,     8,     15,    16,    26,    28,    32,
   34,    37,    38,    40,    48,    63,    64,    69,
   71,    79,    80,    116,   202,   203,   205,   206,
   207,   209,   210,   211,   213,   214,   217,   218,
   219,   775,   928,   7264,  10792, 10795, 23217, 23221,
   23228, 23231, 23254, 23256, 23275, 23278, 26672, 30204,
   35267, 54721, 54753, 54754, 54756, 54787, 54793, 54809,
   57153, 57274, 57921, 58019, 58363, 59314, 59315, 59324,
   59325, 59326, 59332, 59356, 61722, 65268, 65341, 65373,
   65406, 65408, 65410, 65415, 65424, 65436, 65439, 65450,
   65462, 65472, 65476, 65478, 65480, 65482, 65488, 65506,
   65511, 65514, 65521, 65527, 65528, 65529,
  };

  int ret = c;

  assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );

  if( c<128 ){







|
|












|
|

|
|




|
|
|
|
|



|


>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|










|
|
|
|







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
  static const struct TableEntry {
    unsigned short iCode;
    unsigned char flags;
    unsigned char nRange;
  } aEntry[] = {
    {65, 14, 26},          {181, 66, 1},          {192, 14, 23},
    {216, 14, 7},          {256, 1, 48},          {306, 1, 6},
    {313, 1, 16},          {330, 1, 46},          {376, 152, 1},
    {377, 1, 6},           {383, 140, 1},         {385, 52, 1},
    {386, 1, 4},           {390, 46, 1},          {391, 0, 1},
    {393, 44, 2},          {395, 0, 1},           {398, 34, 1},
    {399, 40, 1},          {400, 42, 1},          {401, 0, 1},
    {403, 44, 1},          {404, 48, 1},          {406, 54, 1},
    {407, 50, 1},          {408, 0, 1},           {412, 54, 1},
    {413, 56, 1},          {415, 58, 1},          {416, 1, 6},
    {422, 62, 1},          {423, 0, 1},           {425, 62, 1},
    {428, 0, 1},           {430, 62, 1},          {431, 0, 1},
    {433, 60, 2},          {435, 1, 4},           {439, 64, 1},
    {440, 0, 1},           {444, 0, 1},           {452, 2, 1},
    {453, 0, 1},           {455, 2, 1},           {456, 0, 1},
    {458, 2, 1},           {459, 1, 18},          {478, 1, 18},
    {497, 2, 1},           {498, 1, 4},           {502, 158, 1},
    {503, 170, 1},         {504, 1, 40},          {544, 146, 1},
    {546, 1, 18},          {570, 74, 1},          {571, 0, 1},
    {573, 144, 1},         {574, 72, 1},          {577, 0, 1},
    {579, 142, 1},         {580, 30, 1},          {581, 32, 1},
    {582, 1, 10},          {837, 38, 1},          {880, 1, 4},
    {886, 0, 1},           {895, 38, 1},          {902, 20, 1},
    {904, 18, 3},          {908, 28, 1},          {910, 26, 2},
    {913, 14, 17},         {931, 14, 9},          {962, 0, 1},
    {975, 4, 1},           {976, 176, 1},         {977, 178, 1},
    {981, 182, 1},         {982, 180, 1},         {984, 1, 24},
    {1008, 172, 1},        {1009, 174, 1},        {1012, 166, 1},
    {1013, 164, 1},        {1015, 0, 1},          {1017, 188, 1},
    {1018, 0, 1},          {1021, 146, 3},        {1024, 36, 16},
    {1040, 14, 32},        {1120, 1, 34},         {1162, 1, 54},
    {1216, 6, 1},          {1217, 1, 14},         {1232, 1, 96},
    {1329, 24, 38},        {4256, 70, 38},        {4295, 70, 1},
    {4301, 70, 1},         {5112, 186, 6},        {7296, 122, 1},
    {7297, 124, 1},        {7298, 126, 1},        {7299, 130, 2},
    {7301, 128, 1},        {7302, 132, 1},        {7303, 134, 1},
    {7304, 96, 1},         {7312, 138, 43},       {7357, 138, 3},
    {7680, 1, 150},        {7835, 168, 1},        {7838, 116, 1},
    {7840, 1, 96},         {7944, 186, 8},        {7960, 186, 6},
    {7976, 186, 8},        {7992, 186, 8},        {8008, 186, 6},
    {8025, 187, 8},        {8040, 186, 8},        {8072, 186, 8},
    {8088, 186, 8},        {8104, 186, 8},        {8120, 186, 2},
    {8122, 162, 2},        {8124, 184, 1},        {8126, 120, 1},
    {8136, 160, 4},        {8140, 184, 1},        {8152, 186, 2},
    {8154, 156, 2},        {8168, 186, 2},        {8170, 154, 2},
    {8172, 188, 1},        {8184, 148, 2},        {8186, 150, 2},
    {8188, 184, 1},        {8486, 118, 1},        {8490, 112, 1},
    {8491, 114, 1},        {8498, 12, 1},         {8544, 8, 16},
    {8579, 0, 1},          {9398, 10, 26},        {11264, 24, 47},
    {11360, 0, 1},         {11362, 108, 1},       {11363, 136, 1},
    {11364, 110, 1},       {11367, 1, 6},         {11373, 104, 1},
    {11374, 106, 1},       {11375, 100, 1},       {11376, 102, 1},
    {11378, 0, 1},         {11381, 0, 1},         {11390, 98, 2},
    {11392, 1, 100},       {11499, 1, 4},         {11506, 0, 1},
    {42560, 1, 46},        {42624, 1, 28},        {42786, 1, 14},
    {42802, 1, 62},        {42873, 1, 4},         {42877, 94, 1},
    {42878, 1, 10},        {42891, 0, 1},         {42893, 86, 1},
    {42896, 1, 4},         {42902, 1, 20},        {42922, 80, 1},
    {42923, 76, 1},        {42924, 78, 1},        {42925, 82, 1},
    {42926, 80, 1},        {42928, 90, 1},        {42929, 84, 1},
    {42930, 88, 1},        {42931, 68, 1},        {42932, 1, 6},
    {43888, 92, 80},       {65313, 14, 26},
  };
  static const unsigned short aiOff[] = {
   1,     2,     8,     15,    16,    26,    28,    32,
   34,    37,    38,    40,    48,    63,    64,    69,
   71,    79,    80,    116,   202,   203,   205,   206,
   207,   209,   210,   211,   213,   214,   217,   218,
   219,   775,   928,   7264,  10792, 10795, 23217, 23221,
   23228, 23231, 23254, 23256, 23275, 23278, 26672, 30204,
   35267, 54721, 54753, 54754, 54756, 54787, 54793, 54809,
   57153, 57274, 57921, 58019, 58363, 59314, 59315, 59324,
   59325, 59326, 59332, 59356, 61722, 62528, 65268, 65341,
   65373, 65406, 65408, 65410, 65415, 65424, 65436, 65439,
   65450, 65462, 65472, 65476, 65478, 65480, 65482, 65488,
   65506, 65511, 65514, 65521, 65527, 65528, 65529,
  };

  int ret = c;

  assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );

  if( c<128 ){
385
386
387
388
389
390
391



392
393
394
395
396
397
398
    ret = c + 40;
  }
  else if( c>=68736 && c<68787 ){
    ret = c + 64;
  }
  else if( c>=71840 && c<71872 ){
    ret = c + 32;



  }
  else if( c>=125184 && c<125218 ){
    ret = c + 34;
  }

  return ret;
}







>
>
>







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    ret = c + 40;
  }
  else if( c>=68736 && c<68787 ){
    ret = c + 64;
  }
  else if( c>=71840 && c<71872 ){
    ret = c + 32;
  }
  else if( c>=93760 && c<93792 ){
    ret = c + 32;
  }
  else if( c>=125184 && c<125218 ){
    ret = c + 34;
  }

  return ret;
}

Changes to src/webmail.c.

618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
    style_footer();
    return;
  }
  add_content_sql_commands(g.db);
  emailid = atoi(PD("id","0"));
  url_initialize(&url, "webmail");
  if( g.perm.Admin ){
    zUser = P("user");
    if( zUser ){
      url_add_parameter(&url, "user", zUser);
      if( fossil_strcmp(zUser,"*")==0 ){
        showAll = 1;
        zUser = 0;
      }
    }







|







618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
    style_footer();
    return;
  }
  add_content_sql_commands(g.db);
  emailid = atoi(PD("id","0"));
  url_initialize(&url, "webmail");
  if( g.perm.Admin ){
    zUser = PD("user",g.zLogin);
    if( zUser ){
      url_add_parameter(&url, "user", zUser);
      if( fossil_strcmp(zUser,"*")==0 ){
        showAll = 1;
        zUser = 0;
      }
    }
700
701
702
703
704
705
706


707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759

760
761
762
763
764
765
766
  pg = atoi(PD("pg","0"));
  blob_append_sql(&sql, " ORDER BY edate DESC limit %d offset %d", N+1, pg*N);
  db_multi_exec("%s", blob_sql_text(&sql));
  got = db_int(0, "SELECT count(*) FROM tmbox");
  db_prepare(&q, "SELECT * FROM tmbox LIMIT %d", N);
  blob_reset(&sql);
  @ <form action="%R/webmail" method="POST">


  @ <table border="0" width="100%%">
  @ <tr><td align="left">
  if( d==2 ){
    @ <input type="submit" name="read" value="Undelete">
    @ <input type="submit" name="purge" value="Delete Permanently">
  }else{
    @ <input type="submit" name="trash", value="Delete">
    if( d!=1 ){
      @ <input type="submit" name="unread" value="Mark as unread">
    }
    @ <input type="submit" name="read" value="Mark as read">
  }
  @ <button onclick="webmailSelectAll(); return false;">Select All</button>
  @ <a href="%s(url_render(&url,0,0,0,0))">refresh</a>
  @ </td><td align="right">
  if( pg>0 ){
    sqlite3_snprintf(sizeof(zPPg), zPPg, "%d", pg-1);
    @ <a href="%s(url_render(&url,"pg",zPPg,0,0))">&lt; Newer</a>&nbsp;&nbsp;
  }
  if( got>50 ){
    sqlite3_snprintf(sizeof(zNPg),zNPg,"%d",pg+1);
    @ <a href="%s(url_render(&url,"pg",zNPg,0,0))">Older &gt;</a></td>
  }
  @ </table>
  @ <table>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zId = db_column_text(&q,0);
    const char *zFrom = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zSubject = db_column_text(&q, 4);

    @ <tr>
    @ <td><input type="checkbox" class="webmailckbox" name="e%s(zId)"></td>
    @ <td>%h(zFrom)</td>
    @ <td><a href="%s(url_render(&url,"id",zId,0,0))">%h(zSubject)</a> \
    @ %s(zDate)</td>
    if( showAll ){
      const char *zTo = db_column_text(&q,5);
      @ <td><a href="%s(url_render(&url,"user",zTo,0,0))">%h(zTo)</a></td>
    }
    @ </tr>
  }
  db_finalize(&q);
  @ </table>
  @ </form>
  style_footer();
  @ <script>
  @ function webmailSelectAll(){
  @   var x = document.getElementsByClassName("webmailckbox");
  @   for(i=0; i<x.length; i++){
  @     x[i].checked = true;
  @   }
  @ }
  @ </script>

  db_end_transaction(0);
}

/*
** WEBPAGE:  emailblob
**
** This page, accessible only to administrators, allows easy viewing of







>
>






|






|
















>



|



|






<








>







700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753

754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
  pg = atoi(PD("pg","0"));
  blob_append_sql(&sql, " ORDER BY edate DESC limit %d offset %d", N+1, pg*N);
  db_multi_exec("%s", blob_sql_text(&sql));
  got = db_int(0, "SELECT count(*) FROM tmbox");
  db_prepare(&q, "SELECT * FROM tmbox LIMIT %d", N);
  blob_reset(&sql);
  @ <form action="%R/webmail" method="POST">
  @ <input type="hidden" name="d" value="%d(d)">
  @ <input type="hidden" name="user" value="%h(zUser?zUser:"*")">
  @ <table border="0" width="100%%">
  @ <tr><td align="left">
  if( d==2 ){
    @ <input type="submit" name="read" value="Undelete">
    @ <input type="submit" name="purge" value="Delete Permanently">
  }else{
    @ <input type="submit" name="trash" value="Delete">
    if( d!=1 ){
      @ <input type="submit" name="unread" value="Mark as unread">
    }
    @ <input type="submit" name="read" value="Mark as read">
  }
  @ <button onclick="webmailSelectAll(); return false;">Select All</button>
  @ <a href="%h(url_render(&url,0,0,0,0))">refresh</a>
  @ </td><td align="right">
  if( pg>0 ){
    sqlite3_snprintf(sizeof(zPPg), zPPg, "%d", pg-1);
    @ <a href="%s(url_render(&url,"pg",zPPg,0,0))">&lt; Newer</a>&nbsp;&nbsp;
  }
  if( got>50 ){
    sqlite3_snprintf(sizeof(zNPg),zNPg,"%d",pg+1);
    @ <a href="%s(url_render(&url,"pg",zNPg,0,0))">Older &gt;</a></td>
  }
  @ </table>
  @ <table>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zId = db_column_text(&q,0);
    const char *zFrom = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zSubject = db_column_text(&q, 4);
    if( zSubject==0 || zSubject[0]==0 ) zSubject = "(no subject)";
    @ <tr>
    @ <td><input type="checkbox" class="webmailckbox" name="e%s(zId)"></td>
    @ <td>%h(zFrom)</td>
    @ <td><a href="%h(url_render(&url,"id",zId,0,0))">%h(zSubject)</a> \
    @ %s(zDate)</td>
    if( showAll ){
      const char *zTo = db_column_text(&q,5);
      @ <td><a href="%h(url_render(&url,"user",zTo,0,0))">%h(zTo)</a></td>
    }
    @ </tr>
  }
  db_finalize(&q);
  @ </table>
  @ </form>

  @ <script>
  @ function webmailSelectAll(){
  @   var x = document.getElementsByClassName("webmailckbox");
  @   for(i=0; i<x.length; i++){
  @     x[i].checked = true;
  @   }
  @ }
  @ </script>
  style_footer();
  db_end_transaction(0);
}

/*
** WEBPAGE:  emailblob
**
** This page, accessible only to administrators, allows easy viewing of
816
817
818
819
820
821
822

823
824
825
826
827

828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851

852
853
854

























































               id);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zContent = db_column_text(&q, 0);
      @ <pre>%h(zContent)</pre>
    }
    db_finalize(&q);
  }else{

    db_prepare(&q,
       "SELECT emailid, enref, ets, datetime(etime,'unixepoch'), esz,"
       " length(etxt)"
       " FROM emailblob ORDER BY etime DESC, emailid DESC");
    @ <table border="1" cellpadding="5" cellspacing="0">

    @ <tr><th> emailid <th> enref <th> ets <th> etime \
    @ <th> uncompressed <th> compressed </tr>
    while( db_step(&q)==SQLITE_ROW ){
      int id = db_column_int(&q, 0);
      int nref = db_column_int(&q, 1);
      int ets = db_column_int(&q, 2);
      const char *zDate = db_column_text(&q, 3);
      int sz = db_column_int(&q,4);
      int csz = db_column_int(&q,5);
      @ <tr>
      @  <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a>
      @  <td align="right">%d(nref)</td>
      if( ets>0 ){
        @  <td align="right">%d(ets)</td>
      }else{
        @  <td>&nbsp;</td>
      }
      @  <td>%h(zDate)</td>
      @  <td align="right">%,d(sz)</td>
      @  <td align="right">%,d(csz)</td>
      @ </tr>
    }
    @ </table>
    db_finalize(&q);

  }
  style_footer();
}
































































>




|
>
|
|
















|
|


|

>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
               id);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zContent = db_column_text(&q, 0);
      @ <pre>%h(zContent)</pre>
    }
    db_finalize(&q);
  }else{
    style_submenu_element("emailoutq table","%R/emailoutq");
    db_prepare(&q,
       "SELECT emailid, enref, ets, datetime(etime,'unixepoch'), esz,"
       " length(etxt)"
       " FROM emailblob ORDER BY etime DESC, emailid DESC");
    @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \
    @ data-column-types='nnntkk'>
    @ <thead><tr><th> emailid <th> enref <th> ets <th> etime \
    @ <th> uncompressed <th> compressed </tr></thead><tbody>
    while( db_step(&q)==SQLITE_ROW ){
      int id = db_column_int(&q, 0);
      int nref = db_column_int(&q, 1);
      int ets = db_column_int(&q, 2);
      const char *zDate = db_column_text(&q, 3);
      int sz = db_column_int(&q,4);
      int csz = db_column_int(&q,5);
      @ <tr>
      @  <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a>
      @  <td align="right">%d(nref)</td>
      if( ets>0 ){
        @  <td align="right">%d(ets)</td>
      }else{
        @  <td>&nbsp;</td>
      }
      @  <td>%h(zDate)</td>
      @  <td align="right" data-sortkey='%08x(sz)'>%,d(sz)</td>
      @  <td align="right" data-sortkey='%08x(csz)'>%,d(csz)</td>
      @ </tr>
    }
    @ </tbody></table>
    db_finalize(&q);
    style_table_sorter();
  }
  style_footer();
}

/*
** WEBPAGE:  emailoutq
**
** This page, accessible only to administrators, allows easy viewing of
** the emailoutq table - the table that contains the email messages
** that are queued for transmission via SMTP.
*/
void webmail_emailoutq_page(void){
  Stmt q;
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  add_content_sql_commands(g.db);
  style_header("emailoutq table");
  style_submenu_element("emailblob table","%R/emailblob");
  db_prepare(&q,
     "SELECT edomain, efrom, eto, emsgid, "
     "       datetime(ectime,'unixepoch'),"
     "       datetime(nullif(emtime,0),'unixepoch'),"
     "       ensend, ets"
     " FROM emailoutq"
  );
  @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \
  @ data-column-types='tttnttnn'>
  @ <thead><tr><th> edomain <th> efrom <th> eto <th> emsgid \
  @ <th> ectime <th> emtime <th> ensend <th> ets </tr></thead><tbody>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDomain = db_column_text(&q, 0);
    const char *zFrom = db_column_text(&q, 1);
    const char *zTo = db_column_text(&q, 2);
    int emsgid = db_column_int(&q, 3);
    const char *zCTime = db_column_text(&q, 4);
    const char *zMTime = db_column_text(&q, 5);
    int ensend = db_column_int(&q, 6);
    int ets = db_column_int(&q, 7);
    @ <tr>
    @  <td>%h(zDomain)
    @  <td>%h(zFrom)
    @  <td>%h(zTo)
    @  <td align="right"><a href="%R/emailblob?id=%d(emsgid)">%d(emsgid)</a>
    @  <td>%h(zCTime)
    @  <td>%h(zMTime)
    @  <td align="right">%d(ensend)
    if( ets>0 ){
      @  <td align="right"><a href="%R/emailblob?id=%d(ets)">%d(ets)</a></td>
    }else{
      @  <td>&nbsp;</td>
    }
  }
  @ </tbody></table>
  db_finalize(&q);
  style_table_sorter();
  style_footer();
}

Changes to src/wiki.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
** Copyright (c) 2007 D. Richard Hipp
** Copyright (c) 2008 Stephan Beal
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)

** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
** Copyright (c) 2007 D. Richard Hipp
** Copyright (c) 2008 Stephan Beal
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
    wiki_convert(pWiki, 0, 0);
  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
    Blob tail = BLOB_INITIALIZER;
    markdown_to_html(pWiki, 0, &tail);
    @ %s(blob_str(&tail))
    blob_reset(&tail);
  }else{
    @ <pre>
    @ %h(blob_str(pWiki))
    @ </pre>
  }
}

/*
** WEBPAGE: md_rules







|







165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
    wiki_convert(pWiki, 0, 0);
  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
    Blob tail = BLOB_INITIALIZER;
    markdown_to_html(pWiki, 0, &tail);
    @ %s(blob_str(&tail))
    blob_reset(&tail);
  }else{
    @ <pre class='textPlain'>
    @ %h(blob_str(pWiki))
    @ </pre>
  }
}

/*
** WEBPAGE: md_rules
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443

444
445
446
447
448
449
450
  manifest_destroy(pWiki);
  style_footer();
}

/*
** Write a wiki artifact into the repository
*/
static void wiki_put(Blob *pWiki, int parent, int needMod){
  int nrid;
  if( !needMod ){
    nrid = content_put_ex(pWiki, 0, 0, 0, 0);
    if( parent) content_deltify(parent, &nrid, 1, 0);
  }else{
    nrid = content_put_ex(pWiki, 0, 0, 0, 1);
    moderation_table_create();
    db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
  }
  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
  db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
  manifest_crosslink(nrid, pWiki, MC_NONE);

}

/*
** Output a selection box from which the user can select the
** wiki mimetype.
*/
void mimetype_option_menu(const char *zMimetype){







|












>







424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
  manifest_destroy(pWiki);
  style_footer();
}

/*
** Write a wiki artifact into the repository
*/
int wiki_put(Blob *pWiki, int parent, int needMod){
  int nrid;
  if( !needMod ){
    nrid = content_put_ex(pWiki, 0, 0, 0, 0);
    if( parent) content_deltify(parent, &nrid, 1, 0);
  }else{
    nrid = content_put_ex(pWiki, 0, 0, 0, 1);
    moderation_table_create();
    db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
  }
  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
  db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
  manifest_crosslink(nrid, pWiki, MC_NONE);
  return nrid;
}

/*
** Output a selection box from which the user can select the
** wiki mimetype.
*/
void mimetype_option_menu(const char *zMimetype){

Changes to src/winhttp.c.

403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
  }
  fossil_free(zIp);
  aux = fossil_fopen(zCmdFName, "wb");
  if( aux==0 ) goto end_request;
  fwrite(zCmd, 1, strlen(zCmd), aux);

  sqlite3_snprintf(sizeof(zCmd), zCmd,
    "\"%s\" http -args \"%s\" --nossl --nodelay%s",
    g.nameOfExe, zCmdFName, p->zOptions
  );
  in = fossil_fopen(zReplyFName, "w+b");
  fflush(out);
  fflush(aux);
  fossil_system(zCmd);
  if( in ){







|







403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
  }
  fossil_free(zIp);
  aux = fossil_fopen(zCmdFName, "wb");
  if( aux==0 ) goto end_request;
  fwrite(zCmd, 1, strlen(zCmd), aux);

  sqlite3_snprintf(sizeof(zCmd), zCmd,
    "\"%s\" http -args \"%s\" --nossl%s",
    g.nameOfExe, zCmdFName, p->zOptions
  );
  in = fossil_fopen(zReplyFName, "w+b");
  fflush(out);
  fflush(aux);
  fossil_system(zCmd);
  if( in ){
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    fwrite(zHdr, 1, got, out);
    wanted += got;
  }
  assert( g.zRepositoryName && g.zRepositoryName[0] );
  zIp = SocketAddr_toString(&p->addr);
  sqlite3_snprintf(sizeof(zCmd), zCmd,
    "\"%s\" http --in \"%s\" --out \"%s\" --ipaddr %s \"%s\""
    " --scgi --nossl --nodelay%s",
    g.nameOfExe, zRequestFName, zReplyFName, zIp,
    g.zRepositoryName, p->zOptions
  );
  fossil_free(zIp);
  in = fossil_fopen(zReplyFName, "w+b");
  fflush(out);
  fossil_system(zCmd);







|







474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    fwrite(zHdr, 1, got, out);
    wanted += got;
  }
  assert( g.zRepositoryName && g.zRepositoryName[0] );
  zIp = SocketAddr_toString(&p->addr);
  sqlite3_snprintf(sizeof(zCmd), zCmd,
    "\"%s\" http --in \"%s\" --out \"%s\" --ipaddr %s \"%s\""
    " --scgi --nossl%s",
    g.nameOfExe, zRequestFName, zReplyFName, zIp,
    g.zRepositoryName, p->zOptions
  );
  fossil_free(zIp);
  in = fossil_fopen(zReplyFName, "w+b");
  fflush(out);
  fossil_system(zCmd);

Changes to src/xfer.c.

1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
    if( xfer.nToken==3
     && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
     && blob_is_hname(&xfer.aToken[2])
    ){
      const char *zPCode;
      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_panic("missing project code");
      }
      if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
        nErr++;
        break;
      }







|







1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
    if( xfer.nToken==3
     && (blob_eq(&xfer.aToken[0], "pull") || blob_eq(&xfer.aToken[0], "push"))
     && blob_is_hname(&xfer.aToken[2])
    ){
      const char *zPCode;
      zPCode = db_get("project-code", 0);
      if( zPCode==0 ){
        fossil_fatal("missing project code");
      }
      if( !blob_eq_str(&xfer.aToken[2], zPCode, -1) ){
        cgi_reset_content();
        @ error wrong\sproject
        nErr++;
        break;
      }
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
    ){
      if( g.perm.Read ){
        char *zName = blob_str(&xfer.aToken[1]);
        if( zName[0]=='/' ){
          /* New style configuration transfer */
          int groupMask = configure_name_to_mask(&zName[1], 0);
          if( !g.perm.Admin ) groupMask &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
          if( !g.perm.RdForum ) groupMask &= ~CONFIGSET_FORUM;
          if( !g.perm.RdAddr ) groupMask &= ~CONFIGSET_ADDR;
          configure_send_group(xfer.pOut, groupMask, 0);
        }
      }
    }else

    /*   config NAME SIZE \n CONTENT







<







1419
1420
1421
1422
1423
1424
1425

1426
1427
1428
1429
1430
1431
1432
    ){
      if( g.perm.Read ){
        char *zName = blob_str(&xfer.aToken[1]);
        if( zName[0]=='/' ){
          /* New style configuration transfer */
          int groupMask = configure_name_to_mask(&zName[1], 0);
          if( !g.perm.Admin ) groupMask &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);

          if( !g.perm.RdAddr ) groupMask &= ~CONFIGSET_ADDR;
          configure_send_group(xfer.pOut, groupMask, 0);
        }
      }
    }else

    /*   config NAME SIZE \n CONTENT

Changes to test/commit-warning.test.

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

303
304
305
306
# TODO: Change to a collection of test-case crafted files
#       rather than depend on this list of files that will
#       be fragile as development progresses.
#
# Unless the real goal of this test is to document a collection
# of source files that MUST NEVER BE TEXT.
#
run_in_checkout {
  fossil test-commit-warning --no-settings
}

test pre-commit-warnings-fossil-1 {[normalize_result] eq \
    [subst -nocommands -novariables [string trim {
1\tart/branching.odp\tbinary data
1\tart/concept1.dia\tbinary data
1\tart/concept2.dia\tbinary data
1\tcompat/zlib/contrib/blast/test.pk\tbinary data
1\tcompat/zlib/contrib/dotzlib/DotZLib.build\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib.chm\tbinary data
1\tcompat/zlib/contrib/dotzlib/DotZLib.sln\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/Deflater.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/Inflater.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/LICENSE_1_0.txt\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/gcc_gvmat64/gvmat64.S\tCR/LF line endings
1\tcompat/zlib/contrib/masmx64/bld_ml64.bat\tCR/LF line endings
1\tcompat/zlib/contrib/masmx64/gvmat64.asm\tCR/LF line endings
1\tcompat/zlib/contrib/masmx64/inffas8664.c\tCR/LF line endings
1\tcompat/zlib/contrib/masmx64/inffasx64.asm\tCR/LF line endings
1\tcompat/zlib/contrib/masmx64/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/masmx86/bld_ml32.bat\tCR/LF line endings
1\tcompat/zlib/contrib/masmx86/inffas32.asm\tCR/LF line endings
1\tcompat/zlib/contrib/masmx86/match686.asm\tCR/LF line endings
1\tcompat/zlib/contrib/masmx86/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/puff/zeros.raw\tbinary data
1\tcompat/zlib/contrib/testzlib/testzlib.c\tCR/LF line endings
1\tcompat/zlib/contrib/testzlib/testzlib.txt\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/miniunz.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/minizip.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/testzlib.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc12/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc14/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/miniunz.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/minizip.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/testzlib.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibstat.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.vcproj\tCR/LF line endings
1\tcompat/zlib/win32/zlib.def\tCR/LF line endings
1\tcompat/zlib/zlib.3.pdf\tbinary data
1\tcompat/zlib/zlib.map\tCR/LF line endings
1\tsetup/fossil.iss\tCR/LF line endings
1\tskins/blitz/arrow_project.png\tbinary data
1\tskins/blitz/dir.png\tbinary data
1\tskins/blitz/file.png\tbinary data
1\tskins/blitz/fossil_100.png\tbinary data
1\tskins/blitz/fossil_80_reversed_darkcyan.png\tbinary data
1\tskins/blitz/fossil_80_reversed_darkcyan_text.png\tbinary data
1\tskins/blitz/rss_20.png\tbinary data
1\tskins/bootstrap/css.txt\tlong lines
1\ttest/th1-docs-input.txt\tCR/LF line endings
1\ttest/th1-hooks-input.txt\tCR/LF line endings
1\ttest/utf16be.txt\tUnicode
1\ttest/utf16le.txt\tUnicode
1\twin/buildmsvc.bat\tCR/LF line endings
1\twin/fossil.ico\tbinary data
1\twin/fossil.rc\tinvalid UTF-8
1\twww/CollRev1.gif\tbinary data
1\twww/CollRev2.gif\tbinary data
1\twww/CollRev3.gif\tbinary data
1\twww/CollRev4.gif\tbinary data
1\twww/apple-touch-icon.png\tbinary data
1\twww/background.jpg\tbinary data
1\twww/branch01.gif\tbinary data
1\twww/branch02.gif\tbinary data
1\twww/branch03.gif\tbinary data
1\twww/branch04.gif\tbinary data
1\twww/branch05.gif\tbinary data
1\twww/build-icons/linux.gif\tbinary data
1\twww/build-icons/linux64.gif\tbinary data
1\twww/build-icons/mac.gif\tbinary data
1\twww/build-icons/openbsd.gif\tbinary data
1\twww/build-icons/src.gif\tbinary data
1\twww/build-icons/win32.gif\tbinary data
1\twww/concept1.gif\tbinary data
1\twww/concept2.gif\tbinary data
1\twww/copyright-release.pdf\tbinary data
1\twww/delta1.gif\tbinary data
1\twww/delta2.gif\tbinary data
1\twww/delta3.gif\tbinary data
1\twww/delta4.gif\tbinary data
1\twww/delta5.gif\tbinary data
1\twww/delta6.gif\tbinary data
1\twww/encode1.gif\tbinary data
1\twww/encode10.gif\tbinary data
1\twww/encode2.gif\tbinary data
1\twww/encode3.gif\tbinary data
1\twww/encode4.gif\tbinary data
1\twww/encode5.gif\tbinary data
1\twww/encode6.gif\tbinary data
1\twww/encode7.gif\tbinary data
1\twww/encode8.gif\tbinary data
1\twww/encode9.gif\tbinary data
1\twww/fossil.gif\tbinary data
1\twww/fossil2.gif\tbinary data
1\twww/fossil3.gif\tbinary data
1\twww/fossil_logo_small.gif\tbinary data
1\twww/fossil_logo_small2.gif\tbinary data
1\twww/fossil_logo_small3.gif\tbinary data
1\twww/xkcd-git.gif\tbinary data
1}]]}


###############################################################################

test_cleanup







|

|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>




159
160
161
162
163
164
165
166
167
168

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# TODO: Change to a collection of test-case crafted files
#       rather than depend on this list of files that will
#       be fragile as development progresses.
#
# Unless the real goal of this test is to document a collection
# of source files that MUST NEVER BE TEXT.
#
test_block_in_checkout pre-commit-warnings-fossil-1 {
  fossil test-commit-warning --no-settings
} {

  test pre-commit-warnings-fossil-1 {[normalize_result] eq \
      [subst -nocommands -novariables [string trim {
  1\tart/branching.odp\tbinary data
  1\tart/concept1.dia\tbinary data
  1\tart/concept2.dia\tbinary data
  1\tcompat/zlib/contrib/blast/test.pk\tbinary data
  1\tcompat/zlib/contrib/dotzlib/DotZLib.build\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/DotZLib.chm\tbinary data
  1\tcompat/zlib/contrib/dotzlib/DotZLib.sln\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/Deflater.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/Inflater.cs\tinvalid UTF-8
  1\tcompat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/LICENSE_1_0.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/dotzlib/readme.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/gcc_gvmat64/gvmat64.S\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx64/bld_ml64.bat\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx64/gvmat64.asm\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx64/inffas8664.c\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx64/inffasx64.asm\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx64/readme.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx86/bld_ml32.bat\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx86/inffas32.asm\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx86/match686.asm\tCR/LF line endings
  1\tcompat/zlib/contrib/masmx86/readme.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/puff/zeros.raw\tbinary data
  1\tcompat/zlib/contrib/testzlib/testzlib.c\tCR/LF line endings
  1\tcompat/zlib/contrib/testzlib/testzlib.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/readme.txt\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlib.rc\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.def\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.sln\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/miniunz.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/minizip.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/testzlib.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/zlib.rc\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.def\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.sln\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc12/zlibvc.def\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc14/zlibvc.def\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/miniunz.vcproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/minizip.vcproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/testzlib.vcproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/zlib.rc\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/zlibstat.vcproj\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.def\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.sln\tCR/LF line endings
  1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.vcproj\tCR/LF line endings
  1\tcompat/zlib/win32/zlib.def\tCR/LF line endings
  1\tcompat/zlib/zlib.3.pdf\tbinary data
  1\tcompat/zlib/zlib.map\tCR/LF line endings
  1\tsetup/fossil.iss\tCR/LF line endings
  1\tskins/blitz/arrow_project.png\tbinary data
  1\tskins/blitz/dir.png\tbinary data
  1\tskins/blitz/file.png\tbinary data
  1\tskins/blitz/fossil_100.png\tbinary data
  1\tskins/blitz/fossil_80_reversed_darkcyan.png\tbinary data
  1\tskins/blitz/fossil_80_reversed_darkcyan_text.png\tbinary data
  1\tskins/blitz/rss_20.png\tbinary data
  1\tskins/bootstrap/css.txt\tlong lines
  1\ttest/th1-docs-input.txt\tCR/LF line endings
  1\ttest/th1-hooks-input.txt\tCR/LF line endings
  1\ttest/utf16be.txt\tUnicode
  1\ttest/utf16le.txt\tUnicode
  1\twin/buildmsvc.bat\tCR/LF line endings
  1\twin/fossil.ico\tbinary data
  1\twin/fossil.rc\tinvalid UTF-8
  1\twww/CollRev1.gif\tbinary data
  1\twww/CollRev2.gif\tbinary data
  1\twww/CollRev3.gif\tbinary data
  1\twww/CollRev4.gif\tbinary data
  1\twww/apple-touch-icon.png\tbinary data
  1\twww/background.jpg\tbinary data
  1\twww/branch01.gif\tbinary data
  1\twww/branch02.gif\tbinary data
  1\twww/branch03.gif\tbinary data
  1\twww/branch04.gif\tbinary data
  1\twww/branch05.gif\tbinary data
  1\twww/build-icons/linux.gif\tbinary data
  1\twww/build-icons/linux64.gif\tbinary data
  1\twww/build-icons/mac.gif\tbinary data
  1\twww/build-icons/openbsd.gif\tbinary data
  1\twww/build-icons/src.gif\tbinary data
  1\twww/build-icons/win32.gif\tbinary data
  1\twww/concept1.gif\tbinary data
  1\twww/concept2.gif\tbinary data
  1\twww/copyright-release.pdf\tbinary data
  1\twww/delta1.gif\tbinary data
  1\twww/delta2.gif\tbinary data
  1\twww/delta3.gif\tbinary data
  1\twww/delta4.gif\tbinary data
  1\twww/delta5.gif\tbinary data
  1\twww/delta6.gif\tbinary data
  1\twww/encode1.gif\tbinary data
  1\twww/encode10.gif\tbinary data
  1\twww/encode2.gif\tbinary data
  1\twww/encode3.gif\tbinary data
  1\twww/encode4.gif\tbinary data
  1\twww/encode5.gif\tbinary data
  1\twww/encode6.gif\tbinary data
  1\twww/encode7.gif\tbinary data
  1\twww/encode8.gif\tbinary data
  1\twww/encode9.gif\tbinary data
  1\twww/fossil.gif\tbinary data
  1\twww/fossil2.gif\tbinary data
  1\twww/fossil3.gif\tbinary data
  1\twww/fossil_logo_small.gif\tbinary data
  1\twww/fossil_logo_small2.gif\tbinary data
  1\twww/fossil_logo_small3.gif\tbinary data
  1\twww/xkcd-git.gif\tbinary data
  1}]]}
}

###############################################################################

test_cleanup

Changes to test/tester.tcl.

18
19
20
21
22
23
24




25
26
27
28
29

30
31
32
33
34







35
36
37
38
39
40
41
# This is the main test script.  To run a regression test, do this:
#
#     tclsh ../test/tester.tcl ../bld/fossil
#
# Where ../test/tester.tcl is the name of this file and ../bld/fossil
# is the name of the executable to be tested.
#





set testfiledir [file normalize [file dirname [info script]]]
set testrundir [pwd]
set testdir [file normalize [file dirname $argv0]]
set fossilexe [file normalize [lindex $argv 0]]


if {$tcl_platform(platform) eq "windows" && \
    [string length [file extension $fossilexe]] == 0} {
  append fossilexe .exe
}








set argv [lrange $argv 1 end]

set i [lsearch $argv -keep]
if {$i>=0} {
  set KEEP 1
  set argv [lreplace $argv $i $i]







>
>
>
>





>

|
|
|
|
>
>
>
>
>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# This is the main test script.  To run a regression test, do this:
#
#     tclsh ../test/tester.tcl ../bld/fossil
#
# Where ../test/tester.tcl is the name of this file and ../bld/fossil
# is the name of the executable to be tested.
#

# We use some things introduced in 8.6 such as lmap.  auto.def should
# have found us a suitable Tcl installation.
package require Tcl 8.6

set testfiledir [file normalize [file dirname [info script]]]
set testrundir [pwd]
set testdir [file normalize [file dirname $argv0]]
set fossilexe [file normalize [lindex $argv 0]]
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]

if {$::is_windows} {
  if {[string length [file extension $fossilexe]] == 0} {
    append fossilexe .exe
  }
  set outside_fossil_repo [expr ![file exists "$::testfiledir\\..\\_FOSSIL_"]]
} else {
  set outside_fossil_repo [expr ![file exists "$::testfiledir/../.fslckout"]]
}

catch {exec $::fossilexe changes --changed} res
set dirty_ckout [string length $res]

set argv [lrange $argv 1 end]

set i [lsearch $argv -keep]
if {$i>=0} {
  set KEEP 1
  set argv [lreplace $argv $i $i]
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
  # Finally, attempt to gracefully delete the temporary home directory,
  # unless forbidden by external forces.
  if {![info exists ::tempKeepHome]} {delete_temporary_home}
}

proc delete_temporary_home {} {
  if {$::KEEP} {return}; # All cleanup disabled?
  if {$::tcl_platform(platform) eq "windows"} {
    robust_delete [file join $::tempHomePath _fossil]
  } else {
    robust_delete [file join $::tempHomePath .fossil]
  }
  robust_delete $::tempHomePath
}








|







449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
  # Finally, attempt to gracefully delete the temporary home directory,
  # unless forbidden by external forces.
  if {![info exists ::tempKeepHome]} {delete_temporary_home}
}

proc delete_temporary_home {} {
  if {$::KEEP} {return}; # All cleanup disabled?
  if {$::is_windows} {
    robust_delete [file join $::tempHomePath _fossil]
  } else {
    robust_delete [file join $::tempHomePath .fossil]
  }
  robust_delete $::tempHomePath
}

510
511
512
513
514
515
516
517
518




519
520
521



522





523

524
525
526
527
528
529
530
531
532





























533
534
535
536
537
538
539
  fossil test-th-eval "setting th1-hooks"
  if {[normalize_result] eq "1"} {return 1}
  fossil test-th-eval --open-config "setting th1-hooks"
  if {[normalize_result] eq "1"} {return 1}
  return [info exists ::env(TH1_ENABLE_HOOKS)]
}

# This (rarely used) procedure is designed to run a test within the Fossil
# source checkout (e.g. one that does NOT modify any state), while saving




# and restoring the current directory (e.g. one used when running a test
# file outside of the Fossil source checkout).  Please do NOT use this
# procedure unless you are absolutely sure it does not modify the state of



# the repository or source checkout in any way.





#

proc run_in_checkout { script {dir ""} } {
  if {[string length $dir] == 0} {set dir $::testfiledir}
  set savedPwd [pwd]; cd $dir
  set code [catch {
    uplevel 1 $script
  } result]
  cd $savedPwd; unset savedPwd
  return -code $code $result
}






























# Normalize file status lists (like those returned by 'fossil changes')
# so they can be compared using simple string comparison
#
proc normalize_status_list {list} {
  set normalized [list]
  set matches [regexp -all -inline -line {^\s*([A-Z_]+:?)\x20+(\S.*)$} $list]







<
|
>
>
>
>
|
|
<
>
>
>
|
>
>
>
>
>

>









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







522
523
524
525
526
527
528

529
530
531
532
533
534
535

536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
  fossil test-th-eval "setting th1-hooks"
  if {[normalize_result] eq "1"} {return 1}
  fossil test-th-eval --open-config "setting th1-hooks"
  if {[normalize_result] eq "1"} {return 1}
  return [info exists ::env(TH1_ENABLE_HOOKS)]
}


# Run the given command script inside the Fossil source repo checkout.
#
# Callers of this function must ensure two things:
#
# 1. This test run is in fact being done from within a Fossil repo
#    checkout directory.  If you are unsure, test $::outside_fossil_repo
#    or call one of the test_* wrappers below which do that for you.

#
#    As a rule, you should not be calling this function directly!
#
# 2. This test run is being done from a repo checkout directory that
#    doesn't have any uncommitted changes.  If it does, that affects the
#    output of any test based on the output of "fossil status",
#    "... diff", etc., which is likely to make the test appear to fail.
#    If you must call this function directly, test $::dirty_ckout and
#    skip the call if it's true.  The test_* wrappers do this for you.
#
# 3. The test does NOT modify the Fossil checkout tree in any way.
proc run_in_checkout { script {dir ""} } {
  if {[string length $dir] == 0} {set dir $::testfiledir}
  set savedPwd [pwd]; cd $dir
  set code [catch {
    uplevel 1 $script
  } result]
  cd $savedPwd; unset savedPwd
  return -code $code $result
}

# Wrapper for the above function pair.  The tscript parameter is an
# optional post-run test script.  Some callers choose instead to put
# the tests inline with the rscript commands.
#
# Be sure to adhere to the requirements of run_in_checkout!
proc test_block_in_checkout { name rscript {tscript ""} } {
  if {$::outside_fossil_repo || $::dirty_ckout} {
    set $::CODE 0
    set $::RESULT ""
  } else {
    run_in_checkout $rscript
    if {[string length $tscript] == 0} {
      return ""
    } else {
      set code [catch {
        uplevel 1 $tscript
      } result]
      return -code $code $result
    }
  }
}

# Single-test wrapper for the above.
proc test_in_checkout { name rscript tscript } {
  return test_block_in_checkout name rscript {
    test $name $tscript
  }
}

# Normalize file status lists (like those returned by 'fossil changes')
# so they can be compared using simple string comparison
#
proc normalize_status_list {list} {
  set normalized [list]
  set matches [regexp -all -inline -line {^\s*([A-Z_]+:?)\x20+(\S.*)$} $list]
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
      }
    }
  }

  #
  # NOTE: On non-Windows systems, fallback to /tmp if it is usable.
  #
  if {$::tcl_platform(platform) ne "windows"} {
    set value /tmp

    if {[file exists $value] && [file isdirectory $value]} {
      return $value
    }
  }








|







674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
      }
    }
  }

  #
  # NOTE: On non-Windows systems, fallback to /tmp if it is usable.
  #
  if {!$::is_windows} {
    set value /tmp

    if {[file exists $value] && [file isdirectory $value]} {
      return $value
    }
  }

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
# passed to the [test_stop_server] procedure.
proc test_start_server { repository {varName ""} } {
  global fossilexe tempPath
  set command [list exec $fossilexe server --localhost]
  if {[string length $varName] > 0} {
    upvar 1 $varName stopArg
  }
  if {$::tcl_platform(platform) eq "windows"} {
    set stopArg [file join [getTemporaryPath] [appendArgs \
        [string trim [clock seconds] -] _ [getSeqNo] .stopper]]
    lappend command --stopper $stopArg
  }
  set outFileName [file join $tempPath [appendArgs \
      fossil_server_ [string trim [clock seconds] -] _ \
      [getSeqNo]]].out
  lappend command $repository >&$outFileName &
  set pid [eval $command]
  if {$::tcl_platform(platform) ne "windows"} {
    set stopArg $pid
  }
  after 1000; # output might not be there yet
  set output [read_file $outFileName]
  if {![regexp {Listening.*TCP port (\d+)} $output dummy port]} {
    puts stdout "Could not detect Fossil server port, using default..."
    set port 8080; # return the default port just in case
  }
  return [list $pid $port $outFileName]
}

# This procedure stops a Fossil server instance that was previously started
# by the [test_start_server] procedure.  The value of the "stop argument"
# will vary by platform as will the exact method used to stop the server.
# The fileName argument is the name of a temporary output file to delete.
proc test_stop_server { stopArg pid fileName } {
  if {$::tcl_platform(platform) eq "windows"} {
    #
    # NOTE: On Windows, the "stop argument" must be the name of a file
    #       that does NOT already exist.
    #
    if {[string length $stopArg] > 0 && \
        ![file exists $stopArg] && \
        [catch {write_file $stopArg [clock seconds]}] == 0} {







|









|
















|







846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
# passed to the [test_stop_server] procedure.
proc test_start_server { repository {varName ""} } {
  global fossilexe tempPath
  set command [list exec $fossilexe server --localhost]
  if {[string length $varName] > 0} {
    upvar 1 $varName stopArg
  }
  if {$::is_windows} {
    set stopArg [file join [getTemporaryPath] [appendArgs \
        [string trim [clock seconds] -] _ [getSeqNo] .stopper]]
    lappend command --stopper $stopArg
  }
  set outFileName [file join $tempPath [appendArgs \
      fossil_server_ [string trim [clock seconds] -] _ \
      [getSeqNo]]].out
  lappend command $repository >&$outFileName &
  set pid [eval $command]
  if {!$::is_windows} {
    set stopArg $pid
  }
  after 1000; # output might not be there yet
  set output [read_file $outFileName]
  if {![regexp {Listening.*TCP port (\d+)} $output dummy port]} {
    puts stdout "Could not detect Fossil server port, using default..."
    set port 8080; # return the default port just in case
  }
  return [list $pid $port $outFileName]
}

# This procedure stops a Fossil server instance that was previously started
# by the [test_start_server] procedure.  The value of the "stop argument"
# will vary by platform as will the exact method used to stop the server.
# The fileName argument is the name of a temporary output file to delete.
proc test_stop_server { stopArg pid fileName } {
  if {$::is_windows} {
    #
    # NOTE: On Windows, the "stop argument" must be the name of a file
    #       that does NOT already exist.
    #
    if {[string length $stopArg] > 0 && \
        ![file exists $stopArg] && \
        [catch {write_file $stopArg [clock seconds]}] == 0} {
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
# returns the third to last line of the normalized result.
proc third_to_last_data_line {} {
  return [lindex [split [normalize_result] \n] end-2]
}

set tempPath [getTemporaryPath]

if {$tcl_platform(platform) eq "windows"} {
  set tempPath [string map [list \\ /] $tempPath]
}

if {[catch {
  set tempFile [file join $tempPath temporary.txt]
  write_file $tempFile [clock seconds]; file delete $tempFile
} error] != 0} {







|







1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
# returns the third to last line of the normalized result.
proc third_to_last_data_line {} {
  return [lindex [split [normalize_result] \n] end-2]
}

set tempPath [getTemporaryPath]

if {$is_windows} {
  set tempPath [string map [list \\ /] $tempPath]
}

if {[catch {
  set tempFile [file join $tempPath temporary.txt]
  write_file $tempFile [clock seconds]; file delete $tempFile
} error] != 0} {

Changes to test/th1-docs.test.

27
28
29
30
31
32
33








34
35
36
37
38
39
40

fossil test-th-eval "hasfeature tcl"

if {[normalize_result] ne "1"} {
  puts "Fossil was not compiled with Tcl support."
  test_cleanup_then_return
}









###############################################################################

test_setup ""

###############################################################################








>
>
>
>
>
>
>
>







27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

fossil test-th-eval "hasfeature tcl"

if {[normalize_result] ne "1"} {
  puts "Fossil was not compiled with Tcl support."
  test_cleanup_then_return
}

if {$::outside_fossil_repo} {
  puts "Skipping th1-docs-* tests: not in Fossil repo checkout."
  test_cleanup_then_return
} elseif ($::dirty_ckout) {
  puts "Skipping th1-docs-* tests: uncommitted changes in Fossil checkout."
  test_cleanup_then_return
}

###############################################################################

test_setup ""

###############################################################################

Changes to test/th1.test.

551
552
553
554
555
556
557









558
559
560
561
562
563
564
565
566
567
568
569
570


571
572
573
574
575
576
577

###############################################################################

fossil test-th-eval "lindex list -0x"
test th1-expr-49 {$RESULT eq {TH_ERROR: expected integer, got: "-0x"}}

###############################################################################










foreach perm [list a b c d e f g h i j k l m n o p q r s t u v w x y z] {
  if {$perm eq "u"} continue; # NOTE: Skip "reader" meta-permission.
  if {$perm eq "v"} continue; # NOTE: Skip "developer" meta-permission.

  fossil test-th-eval "anycap $perm"
  test th1-anycap-no-$perm-1 {$RESULT eq {0}}

  fossil test-th-eval "hascap $perm"
  test th1-hascap-no-$perm-1 {$RESULT eq {0}}

  fossil test-th-eval "anoncap $perm"
  test th1-anoncap-no-$perm-1 {$RESULT eq {0}}



  run_in_checkout {
    fossil test-th-eval --set-user-caps "anycap $perm"
    test th1-anycap-yes-$perm-1 {$RESULT eq {1}}

    set ::env(TH1_TEST_USER_CAPS) 1; # NOTE: Bad permission.
    fossil test-th-eval --set-user-caps "anycap $perm"







>
>
>
>
>
>
>
>
>













>
>







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588

###############################################################################

fossil test-th-eval "lindex list -0x"
test th1-expr-49 {$RESULT eq {TH_ERROR: expected integer, got: "-0x"}}

###############################################################################

set skip_anycap 1
if {$::outside_fossil_repo} {
  puts "Skipping th1-anycap-*-1 perm tests: not in Fossil repo checkout."
} elseif ($::dirty_ckout) {
  puts "Skipping th1-anycap-*-1 perm tests: uncommitted changes in Fossil checkout."
} else {
  set skip_anycap 0
}

foreach perm [list a b c d e f g h i j k l m n o p q r s t u v w x y z] {
  if {$perm eq "u"} continue; # NOTE: Skip "reader" meta-permission.
  if {$perm eq "v"} continue; # NOTE: Skip "developer" meta-permission.

  fossil test-th-eval "anycap $perm"
  test th1-anycap-no-$perm-1 {$RESULT eq {0}}

  fossil test-th-eval "hascap $perm"
  test th1-hascap-no-$perm-1 {$RESULT eq {0}}

  fossil test-th-eval "anoncap $perm"
  test th1-anoncap-no-$perm-1 {$RESULT eq {0}}

  if {$skip_anycap} { continue }

  run_in_checkout {
    fossil test-th-eval --set-user-caps "anycap $perm"
    test th1-anycap-yes-$perm-1 {$RESULT eq {1}}

    set ::env(TH1_TEST_USER_CAPS) 1; # NOTE: Bad permission.
    fossil test-th-eval --set-user-caps "anycap $perm"
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
###############################################################################

fossil test-th-eval "anoncap o h"
test th1-anoncap-no-multiple-2 {$RESULT eq {0}}

###############################################################################

run_in_checkout {
  fossil test-th-eval --set-user-caps "anycap oh"
  test th1-anycap-yes-multiple-1 {$RESULT eq {1}}

  set ::env(TH1_TEST_USER_CAPS) o
  fossil test-th-eval --set-user-caps "anycap oh"
  test th1-anycap-yes-multiple-2 {$RESULT eq {1}}
  unset ::env(TH1_TEST_USER_CAPS)







|







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
###############################################################################

fossil test-th-eval "anoncap o h"
test th1-anoncap-no-multiple-2 {$RESULT eq {0}}

###############################################################################

test_block_in_checkout "test-anoncap-*" {
  fossil test-th-eval --set-user-caps "anycap oh"
  test th1-anycap-yes-multiple-1 {$RESULT eq {1}}

  set ::env(TH1_TEST_USER_CAPS) o
  fossil test-th-eval --set-user-caps "anycap oh"
  test th1-anycap-yes-multiple-2 {$RESULT eq {1}}
  unset ::env(TH1_TEST_USER_CAPS)
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
  fossil test-th-eval --set-anon-caps "anoncap o h"
  test th1-anoncap-no-multiple-4 {$RESULT eq {0}}
  unset ::env(TH1_TEST_ANON_CAPS)
}

###############################################################################

run_in_checkout {
  # NOTE: The "1" here forces the checkout to be opened.
  fossil test-th-eval "checkout 1"
}

test th1-checkout-1 {[string length $RESULT] > 0}

###############################################################################

run_in_checkout {
  if {$th1Hooks} {
    fossil test-th-eval "checkout"
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "checkout"
  }
}

test th1-checkout-2 {[string length $RESULT] > 0}

###############################################################################

set savedPwd [pwd]; cd /
fossil test-th-eval "checkout 1"
cd $savedPwd; unset savedPwd
test th1-checkout-3 {[string length $RESULT] == 0}







|


<
<
|



|






<
<
|







674
675
676
677
678
679
680
681
682
683


684
685
686
687
688
689
690
691
692
693
694


695
696
697
698
699
700
701
702
  fossil test-th-eval --set-anon-caps "anoncap o h"
  test th1-anoncap-no-multiple-4 {$RESULT eq {0}}
  unset ::env(TH1_TEST_ANON_CAPS)
}

###############################################################################

test_in_checkout th1-checkout-1 {
  # NOTE: The "1" here forces the checkout to be opened.
  fossil test-th-eval "checkout 1"


} {[string length $RESULT] > 0}

###############################################################################

test_in_checkout th1-checkout-2 {
  if {$th1Hooks} {
    fossil test-th-eval "checkout"
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "checkout"
  }


} {[string length $RESULT] > 0}

###############################################################################

set savedPwd [pwd]; cd /
fossil test-th-eval "checkout 1"
cd $savedPwd; unset savedPwd
test th1-checkout-3 {[string length $RESULT] == 0}
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
###############################################################################

fossil test-th-eval "styleHeader {Page Title Here}"
test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

run_in_checkout {
  fossil test-th-eval --open-config "styleHeader {Page Title Here}"
}

test th1-header-2 {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}

###############################################################################

fossil test-th-eval "styleFooter"
test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################







|

<
<
|







767
768
769
770
771
772
773
774
775


776
777
778
779
780
781
782
783
###############################################################################

fossil test-th-eval "styleHeader {Page Title Here}"
test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-header-2 {
  fossil test-th-eval --open-config "styleHeader {Page Title Here}"


} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}

###############################################################################

fossil test-th-eval "styleFooter"
test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
###############################################################################

fossil test-th-eval "artifact tip"
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

run_in_checkout {
  fossil test-th-eval --open-config "artifact tip"
}

test th1-artifact-3 {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000"
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}

###############################################################################

fossil test-th-eval "artifact tip test/th1.test"
test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

run_in_checkout {
  fossil test-th-eval --open-config "artifact tip test/th1.test"
}

test th1-artifact-7 {[regexp -- {th1-artifact-7} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000 test/th1.test"
test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}

###############################################################################

run_in_checkout {
  if {$th1Hooks} {
    fossil test-th-eval "globalState checkout"
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "globalState checkout"
  }
}

test th1-globalState-1 {[string length $RESULT] > 0}

###############################################################################

run_in_checkout {
  if {$th1Hooks} {
    fossil test-th-eval "globalState checkout"
    test th1-globalState-2 {$RESULT eq [fossil test-th-eval checkout]}
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "globalState checkout"








|

<
<
|


















|

<
<
|













|






<
<
|



|







851
852
853
854
855
856
857
858
859


860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880


881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901


902
903
904
905
906
907
908
909
910
911
912
913
###############################################################################

fossil test-th-eval "artifact tip"
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-3 {
  fossil test-th-eval --open-config "artifact tip"


} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000"
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}

###############################################################################

fossil test-th-eval "artifact tip test/th1.test"
test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-7 {
  fossil test-th-eval --open-config "artifact tip test/th1.test"


} {[regexp -- {th1-artifact-7} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000 test/th1.test"
test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}

###############################################################################

test_in_checkout th1-globalState-1 {
  if {$th1Hooks} {
    fossil test-th-eval "globalState checkout"
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "globalState checkout"
  }


} {[string length $RESULT] > 0}

###############################################################################

test_block_in_checkout th1-globalState-2 {
  if {$th1Hooks} {
    fossil test-th-eval "globalState checkout"
    test th1-globalState-2 {$RESULT eq [fossil test-th-eval checkout]}
  } else {
    # NOTE: No TH1 hooks, force checkout to be populated.
    fossil test-th-eval --open-config "globalState checkout"

940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
###############################################################################

fossil test-th-eval --errorlog foserrors.log "globalState log"
test th1-globalState-7 {$RESULT eq "foserrors.log"}

###############################################################################

run_in_checkout {
  if {$th1Hooks} {
    fossil test-th-eval "globalState repository"
  } else {
    # NOTE: No TH1 hooks, force repository to be populated.
    fossil test-th-eval --open-config "globalState repository"
  }
}

test th1-globalState-8 {[string length $RESULT] > 0}

###############################################################################

run_in_checkout {
  if {$th1Hooks} {
    fossil test-th-eval "globalState repository"
    test th1-globalState-9 {$RESULT eq [fossil test-th-eval repository]}
  } else {
    # NOTE: No TH1 hooks, force repository to be populated.
    fossil test-th-eval --open-config "globalState repository"








|






<
<
|



|







939
940
941
942
943
944
945
946
947
948
949
950
951
952


953
954
955
956
957
958
959
960
961
962
963
964
###############################################################################

fossil test-th-eval --errorlog foserrors.log "globalState log"
test th1-globalState-7 {$RESULT eq "foserrors.log"}

###############################################################################

test_in_checkout th1-globalState-8 {
  if {$th1Hooks} {
    fossil test-th-eval "globalState repository"
  } else {
    # NOTE: No TH1 hooks, force repository to be populated.
    fossil test-th-eval --open-config "globalState repository"
  }


} {[string length $RESULT] > 0}

###############################################################################

test_block_in_checkout th1-globalState-9 {
  if {$th1Hooks} {
    fossil test-th-eval "globalState repository"
    test th1-globalState-9 {$RESULT eq [fossil test-th-eval repository]}
  } else {
    # NOTE: No TH1 hooks, force repository to be populated.
    fossil test-th-eval --open-config "globalState repository"

1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614

1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625

fossil test-th-eval {encode64 test\x00}
test th1-encode64-2 {$RESULT eq "dGVzdAA="}

###############################################################################

#
# TODO: Modify the result of this test if the source file (i.e.
#       "ajax/cgi-bin/fossil-json.cgi.example") changes.
#
run_in_checkout {
  fossil test-th-eval --open-config \
      {encode64 [artifact trunk ajax/cgi-bin/fossil-json.cgi.example]}
}


test th1-encode64-3 {$RESULT eq \
"IyEvcGF0aC90by9mb3NzaWwvYmluYXJ5CnJlcG9zaXRvcnk6IC9wYXRoL3RvL3JlcG8uZnNsCg=="}

###############################################################################

fossil test-th-eval {array exists tcl_platform}
test th1-platform-1 {$RESULT eq "1"}

###############################################################################








|
|

|


|
>
|
<
<








1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613


1614
1615
1616
1617
1618
1619
1620
1621

fossil test-th-eval {encode64 test\x00}
test th1-encode64-2 {$RESULT eq "dGVzdAA="}

###############################################################################

#
# This test will fail if the Fossil source file named below changes.  Update
# the expected result string below if that happens.
#
test_in_checkout th1-encode64-3 {
  fossil test-th-eval --open-config \
      {encode64 [artifact trunk ajax/cgi-bin/fossil-json.cgi.example]}
} {
  $RESULT eq "IyEvcGF0aC90by9mb3NzaWwvYmluYXJ5CnJlcG9zaXRvcnk6IC9wYXRoL3RvL3JlcG8uZnNsCg=="
}



###############################################################################

fossil test-th-eval {array exists tcl_platform}
test th1-platform-1 {$RESULT eq "1"}

###############################################################################

Changes to tools/email-sender.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/tcl
#
# Monitor the database file named by the DBFILE variable
# looking for email messages sent by Fossil.  Forward each
# to /usr/sbin/sendmail.
#
set POLLING_INTERVAL 10000   ;# milliseconds
set DBFILE /home/www/fossil/emailqueue.db
set PIPE "/usr/sbin/sendmail -t"

package require sqlite3
# puts "SQLite version [sqlite3 -version]"
sqlite3 db $DBFILE
db timeout 5000
catch {db eval {PRAGMA journal_mode=WAL}}
db eval {








|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/tcl
#
# Monitor the database file named by the DBFILE variable
# looking for email messages sent by Fossil.  Forward each
# to /usr/sbin/sendmail.
#
set POLLING_INTERVAL 10000   ;# milliseconds
set DBFILE /home/www/fossil/emailqueue.db
set PIPE "/usr/sbin/sendmail -ti"

package require sqlite3
# puts "SQLite version [sqlite3 -version]"
sqlite3 db $DBFILE
db timeout 5000
catch {db eval {PRAGMA journal_mode=WAL}}
db eval {

Changes to win/Makefile.dmc.

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi

SQLITE_OPTIONS = -DNDEBUG=1 -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_GET_TABLE -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_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB

SHELL_OPTIONS = -DNDEBUG=1 -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_GET_TABLE -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_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -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

SRC   = add_.c allrepo_.c attach_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.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 descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c email_.c encode_.c etag_.c event_.c export_.c file_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c hname_.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 report_.c rss_.c schema_.c search_.c security_audit_.c setup_.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 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 wysiwyg_.c xfer_.c xfersetup_.c zip_.c

OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$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)\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)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\email$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$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)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$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)\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)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O


RC=$(DMDIR)\bin\rcc
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__

APPNAME = $(OBJDIR)\fossil$(E)

all: $(APPNAME)

$(APPNAME) : translate$E mkindex$E codecheck1$E headers  $(OBJ) $(OBJDIR)\link
	cd $(OBJDIR)
	codecheck1$E $(SRC)
	$(DMDIR)\bin\link @link

$(OBJDIR)\fossil.res:	$B\win\fossil.rc
	$(RC) $(RCFLAGS) -o$@ $**

$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
	+echo add allrepo attach backoffice bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd descendants diff diffcmd dispatch doc email encode etag event export file finfo foci forum fshell fusefs glob graph gzip hname 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 report rss schema search security_audit setup sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
	+echo fossil >> $@
	+echo fossil >> $@
	+echo $(LIBS) >> $@
	+echo. >> $@
	+echo fossil >> $@

translate$E: $(SRCDIR)\translate.c







|

|


















|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi

SQLITE_OPTIONS = -DNDEBUG=1 -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_GET_TABLE -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_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB

SHELL_OPTIONS = -DNDEBUG=1 -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_GET_TABLE -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_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -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

SRC   = add_.c alerts_.c allrepo_.c attach_.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 descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c file_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c hname_.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 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 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 wysiwyg_.c xfer_.c xfersetup_.c zip_.c

OBJ   = $(OBJDIR)\add$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$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)\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)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$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)\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)\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)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O


RC=$(DMDIR)\bin\rcc
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__

APPNAME = $(OBJDIR)\fossil$(E)

all: $(APPNAME)

$(APPNAME) : translate$E mkindex$E codecheck1$E headers  $(OBJ) $(OBJDIR)\link
	cd $(OBJDIR)
	codecheck1$E $(SRC)
	$(DMDIR)\bin\link @link

$(OBJDIR)\fossil.res:	$B\win\fossil.rc
	$(RC) $(RCFLAGS) -o$@ $**

$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
	+echo add alerts allrepo attach backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd descendants diff diffcmd dispatch doc encode etag event export file finfo foci forum fshell fusefs glob graph gzip hname 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 report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
	+echo fossil >> $@
	+echo fossil >> $@
	+echo $(LIBS) >> $@
	+echo. >> $@
	+echo fossil >> $@

translate$E: $(SRCDIR)\translate.c
132
133
134
135
136
137
138






139
140
141
142
143
144
145


$(OBJDIR)\add$O : add_.c add.h
	$(TCC) -o$@ -c add_.c

add_.c : $(SRCDIR)\add.c
	+translate$E $** > $@







$(OBJDIR)\allrepo$O : allrepo_.c allrepo.h
	$(TCC) -o$@ -c allrepo_.c

allrepo_.c : $(SRCDIR)\allrepo.c
	+translate$E $** > $@








>
>
>
>
>
>







132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151


$(OBJDIR)\add$O : add_.c add.h
	$(TCC) -o$@ -c add_.c

add_.c : $(SRCDIR)\add.c
	+translate$E $** > $@

$(OBJDIR)\alerts$O : alerts_.c alerts.h
	$(TCC) -o$@ -c alerts_.c

alerts_.c : $(SRCDIR)\alerts.c
	+translate$E $** > $@

$(OBJDIR)\allrepo$O : allrepo_.c allrepo.h
	$(TCC) -o$@ -c allrepo_.c

allrepo_.c : $(SRCDIR)\allrepo.c
	+translate$E $** > $@

198
199
200
201
202
203
204






205
206
207
208
209
210
211
	+translate$E $** > $@

$(OBJDIR)\cache$O : cache_.c cache.h
	$(TCC) -o$@ -c cache_.c

cache_.c : $(SRCDIR)\cache.c
	+translate$E $** > $@







$(OBJDIR)\captcha$O : captcha_.c captcha.h
	$(TCC) -o$@ -c captcha_.c

captcha_.c : $(SRCDIR)\captcha.c
	+translate$E $** > $@








>
>
>
>
>
>







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
	+translate$E $** > $@

$(OBJDIR)\cache$O : cache_.c cache.h
	$(TCC) -o$@ -c cache_.c

cache_.c : $(SRCDIR)\cache.c
	+translate$E $** > $@

$(OBJDIR)\capabilities$O : capabilities_.c capabilities.h
	$(TCC) -o$@ -c capabilities_.c

capabilities_.c : $(SRCDIR)\capabilities.c
	+translate$E $** > $@

$(OBJDIR)\captcha$O : captcha_.c captcha.h
	$(TCC) -o$@ -c captcha_.c

captcha_.c : $(SRCDIR)\captcha.c
	+translate$E $** > $@

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326

$(OBJDIR)\doc$O : doc_.c doc.h
	$(TCC) -o$@ -c doc_.c

doc_.c : $(SRCDIR)\doc.c
	+translate$E $** > $@

$(OBJDIR)\email$O : email_.c email.h
	$(TCC) -o$@ -c email_.c

email_.c : $(SRCDIR)\email.c
	+translate$E $** > $@

$(OBJDIR)\encode$O : encode_.c encode.h
	$(TCC) -o$@ -c encode_.c

encode_.c : $(SRCDIR)\encode.c
	+translate$E $** > $@

$(OBJDIR)\etag$O : etag_.c etag.h







<
<
<
<
<
<







319
320
321
322
323
324
325






326
327
328
329
330
331
332

$(OBJDIR)\doc$O : doc_.c doc.h
	$(TCC) -o$@ -c doc_.c

doc_.c : $(SRCDIR)\doc.c
	+translate$E $** > $@







$(OBJDIR)\encode$O : encode_.c encode.h
	$(TCC) -o$@ -c encode_.c

encode_.c : $(SRCDIR)\encode.c
	+translate$E $** > $@

$(OBJDIR)\etag$O : etag_.c etag.h
696
697
698
699
700
701
702






703
704
705
706
707
708
709
	+translate$E $** > $@

$(OBJDIR)\setup$O : setup_.c setup.h
	$(TCC) -o$@ -c setup_.c

setup_.c : $(SRCDIR)\setup.c
	+translate$E $** > $@







$(OBJDIR)\sha1$O : sha1_.c sha1.h
	$(TCC) -o$@ -c sha1_.c

sha1_.c : $(SRCDIR)\sha1.c
	+translate$E $** > $@








>
>
>
>
>
>







702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
	+translate$E $** > $@

$(OBJDIR)\setup$O : setup_.c setup.h
	$(TCC) -o$@ -c setup_.c

setup_.c : $(SRCDIR)\setup.c
	+translate$E $** > $@

$(OBJDIR)\setupuser$O : setupuser_.c setupuser.h
	$(TCC) -o$@ -c setupuser_.c

setupuser_.c : $(SRCDIR)\setupuser.c
	+translate$E $** > $@

$(OBJDIR)\sha1$O : sha1_.c sha1.h
	$(TCC) -o$@ -c sha1_.c

sha1_.c : $(SRCDIR)\sha1.c
	+translate$E $** > $@

926
927
928
929
930
931
932
933
934
$(OBJDIR)\zip$O : zip_.c zip.h
	$(TCC) -o$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	+translate$E $** > $@

headers: makeheaders$E page_index.h builtin_data.h default_css.h VERSION.h
	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.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 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 descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h email_.c:email.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.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 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 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 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 wysiwyg_.c:wysiwyg.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
	@copy /Y nul: headers







|

938
939
940
941
942
943
944
945
946
$(OBJDIR)\zip$O : zip_.c zip.h
	$(TCC) -o$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	+translate$E $** > $@

headers: makeheaders$E page_index.h builtin_data.h default_css.h VERSION.h
	 +makeheaders$E add_.c:add.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.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 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 file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.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 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 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 wysiwyg_.c:wysiwyg.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
	@copy /Y nul: headers

Changes to win/Makefile.mingw.

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2o
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If







|













|







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-weak-ssl-ciphers no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2p
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If
435
436
437
438
439
440
441

442
443
444
445
446
447
448
449
450
451
452

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# You should not need to change anything below this line
#--------------------------------------------------------
XBCC = $(BCC) $(CFLAGS)
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)

SRC = \
  $(SRCDIR)/add.c \

  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \

  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \
  $(SRCDIR)/email.c \
  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \







>











>


















<







435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472

473
474
475
476
477
478
479
# You should not need to change anything below this line
#--------------------------------------------------------
XBCC = $(BCC) $(CFLAGS)
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)

SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/alerts.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \
  $(SRCDIR)/capabilities.c \
  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \

  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \
529
530
531
532
533
534
535

536
537
538
539
540
541
542
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \

  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \







>







530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/setupuser.c \
  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \
596
597
598
599
600
601
602

603
604
605
606
607
608
609
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \

  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \







>







598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \
  $(SRCDIR)/../skins/default/js.txt \
  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \
626
627
628
629
630
631
632

633
634
635
636
637
638
639
640
641
642
643
644
645
646
647

648
649
650
651
652
653
654
655
656
657
658

659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \

  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \

  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \

  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \
  $(OBJDIR)/email_.c \
  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \







>















>











>


















<







629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682

683
684
685
686
687
688
689
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/alerts_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \
  $(OBJDIR)/capabilities_.c \
  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \

  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \
735
736
737
738
739
740
741

742
743
744
745
746
747
748
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \

  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \







>







740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \
  $(OBJDIR)/setupuser_.c \
  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \
776
777
778
779
780
781
782

783
784
785
786
787
788
789
790
791
792
793

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \

 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/descendants.o \
 $(OBJDIR)/diff.o \
 $(OBJDIR)/diffcmd.o \
 $(OBJDIR)/dispatch.o \
 $(OBJDIR)/doc.o \
 $(OBJDIR)/email.o \
 $(OBJDIR)/encode.o \
 $(OBJDIR)/etag.o \
 $(OBJDIR)/event.o \
 $(OBJDIR)/export.o \
 $(OBJDIR)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \







>











>


















<







782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

820
821
822
823
824
825
826
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/alerts.o \
 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \
870
871
872
873
874
875
876

877
878
879
880
881
882
883
 $(OBJDIR)/regexp.o \
 $(OBJDIR)/report.o \
 $(OBJDIR)/rss.o \
 $(OBJDIR)/schema.o \
 $(OBJDIR)/search.o \
 $(OBJDIR)/security_audit.o \
 $(OBJDIR)/setup.o \

 $(OBJDIR)/sha1.o \
 $(OBJDIR)/sha1hard.o \
 $(OBJDIR)/sha3.o \
 $(OBJDIR)/shun.o \
 $(OBJDIR)/sitemap.o \
 $(OBJDIR)/skins.o \
 $(OBJDIR)/smtp.o \







>







877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
 $(OBJDIR)/regexp.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 \
1130
1131
1132
1133
1134
1135
1136

1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147

1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
	$(MKINDEX) $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h:	$(MKBUILTIN) $(EXTRA_FILES)
	$(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
	$(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \

		$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
		$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
		$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
		$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
		$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
		$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
		$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
		$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
		$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
		$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
		$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \

		$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
		$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
		$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
		$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
		$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
		$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
		$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
		$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
		$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
		$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
		$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
		$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
		$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
		$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
		$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
		$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
		$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
		$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \
		$(OBJDIR)/email_.c:$(OBJDIR)/email.h \
		$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
		$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
		$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
		$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
		$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
		$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
		$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \







>











>


















<







1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175

1176
1177
1178
1179
1180
1181
1182
	$(MKINDEX) $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h:	$(MKBUILTIN) $(EXTRA_FILES)
	$(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
	$(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
		$(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
		$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
		$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
		$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
		$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
		$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
		$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
		$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
		$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
		$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
		$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
		$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \
		$(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \
		$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
		$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
		$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
		$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
		$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
		$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
		$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
		$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
		$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
		$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
		$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
		$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
		$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
		$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
		$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
		$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
		$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
		$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \

		$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
		$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
		$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
		$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
		$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
		$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
		$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \
1224
1225
1226
1227
1228
1229
1230

1231
1232
1233
1234
1235
1236
1237
		$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
		$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
		$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
		$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
		$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
		$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
		$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \

		$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
		$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
		$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
		$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
		$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
		$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
		$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \







>







1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
		$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
		$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
		$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
		$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
		$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
		$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
		$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \
		$(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \
		$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
		$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
		$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
		$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
		$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
		$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
		$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
1278
1279
1280
1281
1282
1283
1284








1285
1286
1287
1288
1289
1290
1291
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers









$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c








>
>
>
>
>
>
>
>







1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers

$(OBJDIR)/alerts_.c:	$(SRCDIR)/alerts.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/alerts.c >$@

$(OBJDIR)/alerts.o:	$(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c

$(OBJDIR)/alerts.h:	$(OBJDIR)/headers

$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c

1366
1367
1368
1369
1370
1371
1372








1373
1374
1375
1376
1377
1378
1379
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers









$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c








>
>
>
>
>
>
>
>







1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers

$(OBJDIR)/capabilities_.c:	$(SRCDIR)/capabilities.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/capabilities.c >$@

$(OBJDIR)/capabilities.o:	$(OBJDIR)/capabilities_.c $(OBJDIR)/capabilities.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/capabilities.o -c $(OBJDIR)/capabilities_.c

$(OBJDIR)/capabilities.h:	$(OBJDIR)/headers

$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c

1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
	$(TRANSLATE) $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers

$(OBJDIR)/email_.c:	$(SRCDIR)/email.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/email.c >$@

$(OBJDIR)/email.o:	$(OBJDIR)/email_.c $(OBJDIR)/email.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/email.o -c $(OBJDIR)/email_.c

$(OBJDIR)/email.h:	$(OBJDIR)/headers

$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<
<







1537
1538
1539
1540
1541
1542
1543








1544
1545
1546
1547
1548
1549
1550
	$(TRANSLATE) $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers









$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers
2030
2031
2032
2033
2034
2035
2036








2037
2038
2039
2040
2041
2042
2043
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers









$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c








>
>
>
>
>
>
>
>







2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers

$(OBJDIR)/setupuser_.c:	$(SRCDIR)/setupuser.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setupuser.c >$@

$(OBJDIR)/setupuser.o:	$(OBJDIR)/setupuser_.c $(OBJDIR)/setupuser.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setupuser.o -c $(OBJDIR)/setupuser_.c

$(OBJDIR)/setupuser.h:	$(OBJDIR)/headers

$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c

Changes to win/Makefile.mingw.mistachkin.

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2o
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If







|













|







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
ZLIBCONFIG =
ZLIBTARGETS =
endif

#### Disable creation of the OpenSSL shared libraries.  Also, disable support
#    for both SSLv2 and SSLv3 (i.e. thereby forcing the use of TLS).
#
SSLCONFIG += no-ssl2 no-ssl3 no-weak-ssl-ciphers no-shared

#### When using zlib, make sure that OpenSSL is configured to use the zlib
#    that Fossil knows about (i.e. the one within the source tree).
#
ifndef FOSSIL_ENABLE_MINIZ
SSLCONFIG +=  --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib
endif

#### The directories where the OpenSSL include and library files are located.
#    The recommended usage here is to use the Sysinternals junction tool
#    to create a hard link between an "openssl-1.x" sub-directory of the
#    Fossil source code directory and the target OpenSSL source directory.
#
OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2p
OPENSSLINCDIR = $(OPENSSLDIR)/include
OPENSSLLIBDIR = $(OPENSSLDIR)

#### Either the directory where the Tcl library is installed or the Tcl
#    source code directory resides (depending on the value of the macro
#    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
#    this directory must have "include" and "lib" sub-directories.  If
435
436
437
438
439
440
441

442
443
444
445
446
447
448
449
450
451
452

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# You should not need to change anything below this line
#--------------------------------------------------------
XBCC = $(BCC) $(CFLAGS)
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)

SRC = \
  $(SRCDIR)/add.c \

  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \

  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \
  $(SRCDIR)/email.c \
  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \







>











>


















<







435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472

473
474
475
476
477
478
479
# You should not need to change anything below this line
#--------------------------------------------------------
XBCC = $(BCC) $(CFLAGS)
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)

SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/alerts.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/backoffice.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/bisect.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/builtin.c \
  $(SRCDIR)/bundle.c \
  $(SRCDIR)/cache.c \
  $(SRCDIR)/capabilities.c \
  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/configure.c \
  $(SRCDIR)/content.c \
  $(SRCDIR)/cookies.c \
  $(SRCDIR)/db.c \
  $(SRCDIR)/delta.c \
  $(SRCDIR)/deltacmd.c \
  $(SRCDIR)/descendants.c \
  $(SRCDIR)/diff.c \
  $(SRCDIR)/diffcmd.c \
  $(SRCDIR)/dispatch.c \
  $(SRCDIR)/doc.c \

  $(SRCDIR)/encode.c \
  $(SRCDIR)/etag.c \
  $(SRCDIR)/event.c \
  $(SRCDIR)/export.c \
  $(SRCDIR)/file.c \
  $(SRCDIR)/finfo.c \
  $(SRCDIR)/foci.c \
529
530
531
532
533
534
535

536
537
538
539
540
541
542
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \

  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \







>







530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/setupuser.c \
  $(SRCDIR)/sha1.c \
  $(SRCDIR)/sha1hard.c \
  $(SRCDIR)/sha3.c \
  $(SRCDIR)/shun.c \
  $(SRCDIR)/sitemap.c \
  $(SRCDIR)/skins.c \
  $(SRCDIR)/smtp.c \
596
597
598
599
600
601
602

603
604
605
606
607
608
609
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \

  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \







>







598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
  $(SRCDIR)/../skins/bootstrap/details.txt \
  $(SRCDIR)/../skins/bootstrap/footer.txt \
  $(SRCDIR)/../skins/bootstrap/header.txt \
  $(SRCDIR)/../skins/default/css.txt \
  $(SRCDIR)/../skins/default/details.txt \
  $(SRCDIR)/../skins/default/footer.txt \
  $(SRCDIR)/../skins/default/header.txt \
  $(SRCDIR)/../skins/default/js.txt \
  $(SRCDIR)/../skins/eagle/css.txt \
  $(SRCDIR)/../skins/eagle/details.txt \
  $(SRCDIR)/../skins/eagle/footer.txt \
  $(SRCDIR)/../skins/eagle/header.txt \
  $(SRCDIR)/../skins/enhanced1/css.txt \
  $(SRCDIR)/../skins/enhanced1/details.txt \
  $(SRCDIR)/../skins/enhanced1/footer.txt \
626
627
628
629
630
631
632

633
634
635
636
637
638
639
640
641
642
643
644
645
646
647

648
649
650
651
652
653
654
655
656
657
658

659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \

  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \

  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \

  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \
  $(OBJDIR)/email_.c \
  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \







>















>











>


















<







629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682

683
684
685
686
687
688
689
  $(SRCDIR)/../skins/rounded1/header.txt \
  $(SRCDIR)/../skins/xekri/css.txt \
  $(SRCDIR)/../skins/xekri/details.txt \
  $(SRCDIR)/../skins/xekri/footer.txt \
  $(SRCDIR)/../skins/xekri/header.txt \
  $(SRCDIR)/ci_edit.js \
  $(SRCDIR)/diff.tcl \
  $(SRCDIR)/forum.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/sbsdiff.js \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
  $(OBJDIR)/alerts_.c \
  $(OBJDIR)/allrepo_.c \
  $(OBJDIR)/attach_.c \
  $(OBJDIR)/backoffice_.c \
  $(OBJDIR)/bag_.c \
  $(OBJDIR)/bisect_.c \
  $(OBJDIR)/blob_.c \
  $(OBJDIR)/branch_.c \
  $(OBJDIR)/browse_.c \
  $(OBJDIR)/builtin_.c \
  $(OBJDIR)/bundle_.c \
  $(OBJDIR)/cache_.c \
  $(OBJDIR)/capabilities_.c \
  $(OBJDIR)/captcha_.c \
  $(OBJDIR)/cgi_.c \
  $(OBJDIR)/checkin_.c \
  $(OBJDIR)/checkout_.c \
  $(OBJDIR)/clearsign_.c \
  $(OBJDIR)/clone_.c \
  $(OBJDIR)/comformat_.c \
  $(OBJDIR)/configure_.c \
  $(OBJDIR)/content_.c \
  $(OBJDIR)/cookies_.c \
  $(OBJDIR)/db_.c \
  $(OBJDIR)/delta_.c \
  $(OBJDIR)/deltacmd_.c \
  $(OBJDIR)/descendants_.c \
  $(OBJDIR)/diff_.c \
  $(OBJDIR)/diffcmd_.c \
  $(OBJDIR)/dispatch_.c \
  $(OBJDIR)/doc_.c \

  $(OBJDIR)/encode_.c \
  $(OBJDIR)/etag_.c \
  $(OBJDIR)/event_.c \
  $(OBJDIR)/export_.c \
  $(OBJDIR)/file_.c \
  $(OBJDIR)/finfo_.c \
  $(OBJDIR)/foci_.c \
735
736
737
738
739
740
741

742
743
744
745
746
747
748
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \

  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \







>







740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \
  $(OBJDIR)/setupuser_.c \
  $(OBJDIR)/sha1_.c \
  $(OBJDIR)/sha1hard_.c \
  $(OBJDIR)/sha3_.c \
  $(OBJDIR)/shun_.c \
  $(OBJDIR)/sitemap_.c \
  $(OBJDIR)/skins_.c \
  $(OBJDIR)/smtp_.c \
776
777
778
779
780
781
782

783
784
785
786
787
788
789
790
791
792
793

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \

 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/descendants.o \
 $(OBJDIR)/diff.o \
 $(OBJDIR)/diffcmd.o \
 $(OBJDIR)/dispatch.o \
 $(OBJDIR)/doc.o \
 $(OBJDIR)/email.o \
 $(OBJDIR)/encode.o \
 $(OBJDIR)/etag.o \
 $(OBJDIR)/event.o \
 $(OBJDIR)/export.o \
 $(OBJDIR)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \







>











>


















<







782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

820
821
822
823
824
825
826
  $(OBJDIR)/wysiwyg_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/alerts.o \
 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.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)/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)/file.o \
 $(OBJDIR)/finfo.o \
 $(OBJDIR)/foci.o \
870
871
872
873
874
875
876

877
878
879
880
881
882
883
 $(OBJDIR)/regexp.o \
 $(OBJDIR)/report.o \
 $(OBJDIR)/rss.o \
 $(OBJDIR)/schema.o \
 $(OBJDIR)/search.o \
 $(OBJDIR)/security_audit.o \
 $(OBJDIR)/setup.o \

 $(OBJDIR)/sha1.o \
 $(OBJDIR)/sha1hard.o \
 $(OBJDIR)/sha3.o \
 $(OBJDIR)/shun.o \
 $(OBJDIR)/sitemap.o \
 $(OBJDIR)/skins.o \
 $(OBJDIR)/smtp.o \







>







877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
 $(OBJDIR)/regexp.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 \
1130
1131
1132
1133
1134
1135
1136

1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147

1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
	$(MKINDEX) $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h:	$(MKBUILTIN) $(EXTRA_FILES)
	$(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
	$(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \

		$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
		$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
		$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
		$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
		$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
		$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
		$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
		$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
		$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
		$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
		$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \

		$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
		$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
		$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
		$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
		$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
		$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
		$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
		$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
		$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
		$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
		$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
		$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
		$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
		$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
		$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
		$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
		$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
		$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \
		$(OBJDIR)/email_.c:$(OBJDIR)/email.h \
		$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
		$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
		$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
		$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
		$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
		$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
		$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \







>











>


















<







1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175

1176
1177
1178
1179
1180
1181
1182
	$(MKINDEX) $(TRANS_SRC) >$@

$(OBJDIR)/builtin_data.h:	$(MKBUILTIN) $(EXTRA_FILES)
	$(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@

$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
	$(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
		$(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
		$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
		$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
		$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
		$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
		$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
		$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
		$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
		$(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \
		$(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \
		$(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \
		$(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \
		$(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \
		$(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \
		$(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \
		$(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \
		$(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \
		$(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \
		$(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \
		$(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \
		$(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \
		$(OBJDIR)/content_.c:$(OBJDIR)/content.h \
		$(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \
		$(OBJDIR)/db_.c:$(OBJDIR)/db.h \
		$(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \
		$(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \
		$(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \
		$(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \
		$(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \
		$(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \
		$(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \

		$(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \
		$(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \
		$(OBJDIR)/event_.c:$(OBJDIR)/event.h \
		$(OBJDIR)/export_.c:$(OBJDIR)/export.h \
		$(OBJDIR)/file_.c:$(OBJDIR)/file.h \
		$(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \
		$(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \
1224
1225
1226
1227
1228
1229
1230

1231
1232
1233
1234
1235
1236
1237
		$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
		$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
		$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
		$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
		$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
		$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
		$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \

		$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
		$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
		$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
		$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
		$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
		$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
		$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \







>







1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
		$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
		$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
		$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
		$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
		$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
		$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
		$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \
		$(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \
		$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
		$(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \
		$(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \
		$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
		$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
		$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
		$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
1278
1279
1280
1281
1282
1283
1284








1285
1286
1287
1288
1289
1290
1291
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers









$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c








>
>
>
>
>
>
>
>







1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/add.c >$@

$(OBJDIR)/add.o:	$(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c

$(OBJDIR)/add.h:	$(OBJDIR)/headers

$(OBJDIR)/alerts_.c:	$(SRCDIR)/alerts.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/alerts.c >$@

$(OBJDIR)/alerts.o:	$(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c

$(OBJDIR)/alerts.h:	$(OBJDIR)/headers

$(OBJDIR)/allrepo_.c:	$(SRCDIR)/allrepo.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/allrepo.c >$@

$(OBJDIR)/allrepo.o:	$(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c

1366
1367
1368
1369
1370
1371
1372








1373
1374
1375
1376
1377
1378
1379
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers









$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c








>
>
>
>
>
>
>
>







1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
$(OBJDIR)/cache_.c:	$(SRCDIR)/cache.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/cache.c >$@

$(OBJDIR)/cache.o:	$(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c

$(OBJDIR)/cache.h:	$(OBJDIR)/headers

$(OBJDIR)/capabilities_.c:	$(SRCDIR)/capabilities.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/capabilities.c >$@

$(OBJDIR)/capabilities.o:	$(OBJDIR)/capabilities_.c $(OBJDIR)/capabilities.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/capabilities.o -c $(OBJDIR)/capabilities_.c

$(OBJDIR)/capabilities.h:	$(OBJDIR)/headers

$(OBJDIR)/captcha_.c:	$(SRCDIR)/captcha.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/captcha.c >$@

$(OBJDIR)/captcha.o:	$(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c

1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
	$(TRANSLATE) $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers

$(OBJDIR)/email_.c:	$(SRCDIR)/email.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/email.c >$@

$(OBJDIR)/email.o:	$(OBJDIR)/email_.c $(OBJDIR)/email.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/email.o -c $(OBJDIR)/email_.c

$(OBJDIR)/email.h:	$(OBJDIR)/headers

$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<
<







1537
1538
1539
1540
1541
1542
1543








1544
1545
1546
1547
1548
1549
1550
	$(TRANSLATE) $(SRCDIR)/doc.c >$@

$(OBJDIR)/doc.o:	$(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c

$(OBJDIR)/doc.h:	$(OBJDIR)/headers









$(OBJDIR)/encode_.c:	$(SRCDIR)/encode.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/encode.c >$@

$(OBJDIR)/encode.o:	$(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c

$(OBJDIR)/encode.h:	$(OBJDIR)/headers
2030
2031
2032
2033
2034
2035
2036








2037
2038
2039
2040
2041
2042
2043
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers









$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c








>
>
>
>
>
>
>
>







2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
$(OBJDIR)/setup_.c:	$(SRCDIR)/setup.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setup.c >$@

$(OBJDIR)/setup.o:	$(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c

$(OBJDIR)/setup.h:	$(OBJDIR)/headers

$(OBJDIR)/setupuser_.c:	$(SRCDIR)/setupuser.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/setupuser.c >$@

$(OBJDIR)/setupuser.o:	$(OBJDIR)/setupuser_.c $(OBJDIR)/setupuser.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/setupuser.o -c $(OBJDIR)/setupuser_.c

$(OBJDIR)/setupuser.h:	$(OBJDIR)/headers

$(OBJDIR)/sha1_.c:	$(SRCDIR)/sha1.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/sha1.c >$@

$(OBJDIR)/sha1.o:	$(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c

Changes to win/Makefile.msc.

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

# Enable support for the SQLite Encryption Extension?
!ifndef USE_SEE
USE_SEE = 0
!endif

!if $(FOSSIL_ENABLE_SSL)!=0
SSLDIR    = $(B)\compat\openssl-1.0.2o
SSLINCDIR = $(SSLDIR)\inc32
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLLIBDIR = $(SSLDIR)\out32dll
!else
SSLLIBDIR = $(SSLDIR)\out32
!endif
SSLLFLAGS = /nologo /opt:ref /debug
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib crypt32.lib
!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64"
!message Using 'x64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN64A no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64a.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!elseif "$(PLATFORM)"=="ia64"
!message Using 'ia64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN64I no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64i.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!else
!message Assuming 'x86' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3
SSLCONFIG = VC-WIN32 no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_ms.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3
!endif
!endif
!endif

!if $(FOSSIL_ENABLE_TCL)!=0
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)







|











|














|




|














|




|














|







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

# Enable support for the SQLite Encryption Extension?
!ifndef USE_SEE
USE_SEE = 0
!endif

!if $(FOSSIL_ENABLE_SSL)!=0
SSLDIR    = $(B)\compat\openssl-1.0.2p
SSLINCDIR = $(SSLDIR)\inc32
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLLIBDIR = $(SSLDIR)\out32dll
!else
SSLLIBDIR = $(SSLDIR)\out32
!endif
SSLLFLAGS = /nologo /opt:ref /debug
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib crypt32.lib
!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64"
!message Using 'x64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64A no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN64A no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64a.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!elseif "$(PLATFORM)"=="ia64"
!message Using 'ia64' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN64I no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN64I no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_win64i.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!else
!message Assuming 'x86' platform for OpenSSL...
# BUGBUG (OpenSSL): Using "no-ssl*" here breaks the build.
# SSLCONFIG = VC-WIN32 no-asm no-ssl2 no-ssl3 no-weak-ssl-ciphers
SSLCONFIG = VC-WIN32 no-asm
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLCONFIG = $(SSLCONFIG) shared
!else
SSLCONFIG = $(SSLCONFIG) no-shared
!endif
SSLSETUP  = ms\do_ms.bat
!if $(FOSSIL_DYNAMIC_BUILD)!=0
SSLNMAKE  = ms\ntdll.mak all
!else
SSLNMAKE  = ms\nt.mak all
!endif
# BUGBUG (OpenSSL): Using "OPENSSL_NO_SSL*" here breaks dynamic builds.
!if $(FOSSIL_DYNAMIC_BUILD)==0
SSLCFLAGS = -DOPENSSL_NO_SSL2 -DOPENSSL_NO_SSL3 -DOPENSSL_NO_WEAK_SSL_CIPHERS
!endif
!endif
!endif

!if $(FOSSIL_ENABLE_TCL)!=0
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)
389
390
391
392
393
394
395

396
397
398
399
400
401
402
403
404
405
406

407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
                /Dfopen=fossil_fopen

MINIZ_OPTIONS = /DMINIZ_NO_STDIO \
                /DMINIZ_NO_TIME \
                /DMINIZ_NO_ARCHIVE_APIS

SRC   = "$(OX)\add_.c" \

        "$(OX)\allrepo_.c" \
        "$(OX)\attach_.c" \
        "$(OX)\backoffice_.c" \
        "$(OX)\bag_.c" \
        "$(OX)\bisect_.c" \
        "$(OX)\blob_.c" \
        "$(OX)\branch_.c" \
        "$(OX)\browse_.c" \
        "$(OX)\builtin_.c" \
        "$(OX)\bundle_.c" \
        "$(OX)\cache_.c" \

        "$(OX)\captcha_.c" \
        "$(OX)\cgi_.c" \
        "$(OX)\checkin_.c" \
        "$(OX)\checkout_.c" \
        "$(OX)\clearsign_.c" \
        "$(OX)\clone_.c" \
        "$(OX)\comformat_.c" \
        "$(OX)\configure_.c" \
        "$(OX)\content_.c" \
        "$(OX)\cookies_.c" \
        "$(OX)\db_.c" \
        "$(OX)\delta_.c" \
        "$(OX)\deltacmd_.c" \
        "$(OX)\descendants_.c" \
        "$(OX)\diff_.c" \
        "$(OX)\diffcmd_.c" \
        "$(OX)\dispatch_.c" \
        "$(OX)\doc_.c" \
        "$(OX)\email_.c" \
        "$(OX)\encode_.c" \
        "$(OX)\etag_.c" \
        "$(OX)\event_.c" \
        "$(OX)\export_.c" \
        "$(OX)\file_.c" \
        "$(OX)\finfo_.c" \
        "$(OX)\foci_.c" \







>











>


















<







389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426

427
428
429
430
431
432
433
                /Dfopen=fossil_fopen

MINIZ_OPTIONS = /DMINIZ_NO_STDIO \
                /DMINIZ_NO_TIME \
                /DMINIZ_NO_ARCHIVE_APIS

SRC   = "$(OX)\add_.c" \
        "$(OX)\alerts_.c" \
        "$(OX)\allrepo_.c" \
        "$(OX)\attach_.c" \
        "$(OX)\backoffice_.c" \
        "$(OX)\bag_.c" \
        "$(OX)\bisect_.c" \
        "$(OX)\blob_.c" \
        "$(OX)\branch_.c" \
        "$(OX)\browse_.c" \
        "$(OX)\builtin_.c" \
        "$(OX)\bundle_.c" \
        "$(OX)\cache_.c" \
        "$(OX)\capabilities_.c" \
        "$(OX)\captcha_.c" \
        "$(OX)\cgi_.c" \
        "$(OX)\checkin_.c" \
        "$(OX)\checkout_.c" \
        "$(OX)\clearsign_.c" \
        "$(OX)\clone_.c" \
        "$(OX)\comformat_.c" \
        "$(OX)\configure_.c" \
        "$(OX)\content_.c" \
        "$(OX)\cookies_.c" \
        "$(OX)\db_.c" \
        "$(OX)\delta_.c" \
        "$(OX)\deltacmd_.c" \
        "$(OX)\descendants_.c" \
        "$(OX)\diff_.c" \
        "$(OX)\diffcmd_.c" \
        "$(OX)\dispatch_.c" \
        "$(OX)\doc_.c" \

        "$(OX)\encode_.c" \
        "$(OX)\etag_.c" \
        "$(OX)\event_.c" \
        "$(OX)\export_.c" \
        "$(OX)\file_.c" \
        "$(OX)\finfo_.c" \
        "$(OX)\foci_.c" \
483
484
485
486
487
488
489

490
491
492
493
494
495
496
        "$(OX)\regexp_.c" \
        "$(OX)\report_.c" \
        "$(OX)\rss_.c" \
        "$(OX)\schema_.c" \
        "$(OX)\search_.c" \
        "$(OX)\security_audit_.c" \
        "$(OX)\setup_.c" \

        "$(OX)\sha1_.c" \
        "$(OX)\sha1hard_.c" \
        "$(OX)\sha3_.c" \
        "$(OX)\shun_.c" \
        "$(OX)\sitemap_.c" \
        "$(OX)\skins_.c" \
        "$(OX)\smtp_.c" \







>







484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
        "$(OX)\regexp_.c" \
        "$(OX)\report_.c" \
        "$(OX)\rss_.c" \
        "$(OX)\schema_.c" \
        "$(OX)\search_.c" \
        "$(OX)\security_audit_.c" \
        "$(OX)\setup_.c" \
        "$(OX)\setupuser_.c" \
        "$(OX)\sha1_.c" \
        "$(OX)\sha1hard_.c" \
        "$(OX)\sha3_.c" \
        "$(OX)\shun_.c" \
        "$(OX)\sitemap_.c" \
        "$(OX)\skins_.c" \
        "$(OX)\smtp_.c" \
549
550
551
552
553
554
555

556
557
558
559
560
561
562
        "$(SRCDIR)\..\skins\bootstrap\details.txt" \
        "$(SRCDIR)\..\skins\bootstrap\footer.txt" \
        "$(SRCDIR)\..\skins\bootstrap\header.txt" \
        "$(SRCDIR)\..\skins\default\css.txt" \
        "$(SRCDIR)\..\skins\default\details.txt" \
        "$(SRCDIR)\..\skins\default\footer.txt" \
        "$(SRCDIR)\..\skins\default\header.txt" \

        "$(SRCDIR)\..\skins\eagle\css.txt" \
        "$(SRCDIR)\..\skins\eagle\details.txt" \
        "$(SRCDIR)\..\skins\eagle\footer.txt" \
        "$(SRCDIR)\..\skins\eagle\header.txt" \
        "$(SRCDIR)\..\skins\enhanced1\css.txt" \
        "$(SRCDIR)\..\skins\enhanced1\details.txt" \
        "$(SRCDIR)\..\skins\enhanced1\footer.txt" \







>







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
        "$(SRCDIR)\..\skins\bootstrap\details.txt" \
        "$(SRCDIR)\..\skins\bootstrap\footer.txt" \
        "$(SRCDIR)\..\skins\bootstrap\header.txt" \
        "$(SRCDIR)\..\skins\default\css.txt" \
        "$(SRCDIR)\..\skins\default\details.txt" \
        "$(SRCDIR)\..\skins\default\footer.txt" \
        "$(SRCDIR)\..\skins\default\header.txt" \
        "$(SRCDIR)\..\skins\default\js.txt" \
        "$(SRCDIR)\..\skins\eagle\css.txt" \
        "$(SRCDIR)\..\skins\eagle\details.txt" \
        "$(SRCDIR)\..\skins\eagle\footer.txt" \
        "$(SRCDIR)\..\skins\eagle\header.txt" \
        "$(SRCDIR)\..\skins\enhanced1\css.txt" \
        "$(SRCDIR)\..\skins\enhanced1\details.txt" \
        "$(SRCDIR)\..\skins\enhanced1\footer.txt" \
579
580
581
582
583
584
585

586
587
588
589
590
591
592
593
594
595
596
597
598
599

600
601
602
603
604
605
606
607
608
609
610

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
        "$(SRCDIR)\..\skins\rounded1\header.txt" \
        "$(SRCDIR)\..\skins\xekri\css.txt" \
        "$(SRCDIR)\..\skins\xekri\details.txt" \
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\diff.tcl" \

        "$(SRCDIR)\graph.js" \
        "$(SRCDIR)\href.js" \
        "$(SRCDIR)\login.js" \
        "$(SRCDIR)\markdown.md" \
        "$(SRCDIR)\menu.js" \
        "$(SRCDIR)\sbsdiff.js" \
        "$(SRCDIR)\scroll.js" \
        "$(SRCDIR)\skin.js" \
        "$(SRCDIR)\sorttable.js" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

OBJ   = "$(OX)\add$O" \

        "$(OX)\allrepo$O" \
        "$(OX)\attach$O" \
        "$(OX)\backoffice$O" \
        "$(OX)\bag$O" \
        "$(OX)\bisect$O" \
        "$(OX)\blob$O" \
        "$(OX)\branch$O" \
        "$(OX)\browse$O" \
        "$(OX)\builtin$O" \
        "$(OX)\bundle$O" \
        "$(OX)\cache$O" \

        "$(OX)\captcha$O" \
        "$(OX)\cgi$O" \
        "$(OX)\checkin$O" \
        "$(OX)\checkout$O" \
        "$(OX)\clearsign$O" \
        "$(OX)\clone$O" \
        "$(OX)\comformat$O" \
        "$(OX)\configure$O" \
        "$(OX)\content$O" \
        "$(OX)\cookies$O" \
        "$(OX)\cson_amalgamation$O" \
        "$(OX)\db$O" \
        "$(OX)\delta$O" \
        "$(OX)\deltacmd$O" \
        "$(OX)\descendants$O" \
        "$(OX)\diff$O" \
        "$(OX)\diffcmd$O" \
        "$(OX)\dispatch$O" \
        "$(OX)\doc$O" \
        "$(OX)\email$O" \
        "$(OX)\encode$O" \
        "$(OX)\etag$O" \
        "$(OX)\event$O" \
        "$(OX)\export$O" \
        "$(OX)\file$O" \
        "$(OX)\finfo$O" \
        "$(OX)\foci$O" \







>














>











>



















<







582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635

636
637
638
639
640
641
642
        "$(SRCDIR)\..\skins\rounded1\header.txt" \
        "$(SRCDIR)\..\skins\xekri\css.txt" \
        "$(SRCDIR)\..\skins\xekri\details.txt" \
        "$(SRCDIR)\..\skins\xekri\footer.txt" \
        "$(SRCDIR)\..\skins\xekri\header.txt" \
        "$(SRCDIR)\ci_edit.js" \
        "$(SRCDIR)\diff.tcl" \
        "$(SRCDIR)\forum.js" \
        "$(SRCDIR)\graph.js" \
        "$(SRCDIR)\href.js" \
        "$(SRCDIR)\login.js" \
        "$(SRCDIR)\markdown.md" \
        "$(SRCDIR)\menu.js" \
        "$(SRCDIR)\sbsdiff.js" \
        "$(SRCDIR)\scroll.js" \
        "$(SRCDIR)\skin.js" \
        "$(SRCDIR)\sorttable.js" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

OBJ   = "$(OX)\add$O" \
        "$(OX)\alerts$O" \
        "$(OX)\allrepo$O" \
        "$(OX)\attach$O" \
        "$(OX)\backoffice$O" \
        "$(OX)\bag$O" \
        "$(OX)\bisect$O" \
        "$(OX)\blob$O" \
        "$(OX)\branch$O" \
        "$(OX)\browse$O" \
        "$(OX)\builtin$O" \
        "$(OX)\bundle$O" \
        "$(OX)\cache$O" \
        "$(OX)\capabilities$O" \
        "$(OX)\captcha$O" \
        "$(OX)\cgi$O" \
        "$(OX)\checkin$O" \
        "$(OX)\checkout$O" \
        "$(OX)\clearsign$O" \
        "$(OX)\clone$O" \
        "$(OX)\comformat$O" \
        "$(OX)\configure$O" \
        "$(OX)\content$O" \
        "$(OX)\cookies$O" \
        "$(OX)\cson_amalgamation$O" \
        "$(OX)\db$O" \
        "$(OX)\delta$O" \
        "$(OX)\deltacmd$O" \
        "$(OX)\descendants$O" \
        "$(OX)\diff$O" \
        "$(OX)\diffcmd$O" \
        "$(OX)\dispatch$O" \
        "$(OX)\doc$O" \

        "$(OX)\encode$O" \
        "$(OX)\etag$O" \
        "$(OX)\event$O" \
        "$(OX)\export$O" \
        "$(OX)\file$O" \
        "$(OX)\finfo$O" \
        "$(OX)\foci$O" \
688
689
690
691
692
693
694

695
696
697
698
699
700
701
        "$(OX)\regexp$O" \
        "$(OX)\report$O" \
        "$(OX)\rss$O" \
        "$(OX)\schema$O" \
        "$(OX)\search$O" \
        "$(OX)\security_audit$O" \
        "$(OX)\setup$O" \

        "$(OX)\sha1$O" \
        "$(OX)\sha1hard$O" \
        "$(OX)\sha3$O" \
        "$(OX)\shell$O" \
        "$(OX)\shun$O" \
        "$(OX)\sitemap$O" \
        "$(OX)\skins$O" \







>







693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
        "$(OX)\regexp$O" \
        "$(OX)\report$O" \
        "$(OX)\rss$O" \
        "$(OX)\schema$O" \
        "$(OX)\search$O" \
        "$(OX)\security_audit$O" \
        "$(OX)\setup$O" \
        "$(OX)\setupuser$O" \
        "$(OX)\sha1$O" \
        "$(OX)\sha1hard$O" \
        "$(OX)\sha3$O" \
        "$(OX)\shell$O" \
        "$(OX)\shun$O" \
        "$(OX)\sitemap$O" \
        "$(OX)\skins$O" \
804
805
806
807
808
809
810

811
812
813
814
815
816
817
818
819
820
821

822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
	"$(OBJDIR)\codecheck1$E" $(SRC)
	link $(LDFLAGS) /OUT:$@ /PDB:$(@D)\ $(LIBDIR) Wsetargv.obj "$(OX)\fossil.res" @"$(OX)\linkopts"
	if exist "$(APPMANIFEST)" \
		$(MTC) -nologo -manifest "$(APPMANIFEST)" -outputresource:$@;1

"$(OX)\linkopts": "$(B)\win\Makefile.msc"
	echo "$(OX)\add.obj" > $@

	echo "$(OX)\allrepo.obj" >> $@
	echo "$(OX)\attach.obj" >> $@
	echo "$(OX)\backoffice.obj" >> $@
	echo "$(OX)\bag.obj" >> $@
	echo "$(OX)\bisect.obj" >> $@
	echo "$(OX)\blob.obj" >> $@
	echo "$(OX)\branch.obj" >> $@
	echo "$(OX)\browse.obj" >> $@
	echo "$(OX)\builtin.obj" >> $@
	echo "$(OX)\bundle.obj" >> $@
	echo "$(OX)\cache.obj" >> $@

	echo "$(OX)\captcha.obj" >> $@
	echo "$(OX)\cgi.obj" >> $@
	echo "$(OX)\checkin.obj" >> $@
	echo "$(OX)\checkout.obj" >> $@
	echo "$(OX)\clearsign.obj" >> $@
	echo "$(OX)\clone.obj" >> $@
	echo "$(OX)\comformat.obj" >> $@
	echo "$(OX)\configure.obj" >> $@
	echo "$(OX)\content.obj" >> $@
	echo "$(OX)\cookies.obj" >> $@
	echo "$(OX)\cson_amalgamation.obj" >> $@
	echo "$(OX)\db.obj" >> $@
	echo "$(OX)\delta.obj" >> $@
	echo "$(OX)\deltacmd.obj" >> $@
	echo "$(OX)\descendants.obj" >> $@
	echo "$(OX)\diff.obj" >> $@
	echo "$(OX)\diffcmd.obj" >> $@
	echo "$(OX)\dispatch.obj" >> $@
	echo "$(OX)\doc.obj" >> $@
	echo "$(OX)\email.obj" >> $@
	echo "$(OX)\encode.obj" >> $@
	echo "$(OX)\etag.obj" >> $@
	echo "$(OX)\event.obj" >> $@
	echo "$(OX)\export.obj" >> $@
	echo "$(OX)\file.obj" >> $@
	echo "$(OX)\finfo.obj" >> $@
	echo "$(OX)\foci.obj" >> $@







>











>



















<







810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848

849
850
851
852
853
854
855
	"$(OBJDIR)\codecheck1$E" $(SRC)
	link $(LDFLAGS) /OUT:$@ /PDB:$(@D)\ $(LIBDIR) Wsetargv.obj "$(OX)\fossil.res" @"$(OX)\linkopts"
	if exist "$(APPMANIFEST)" \
		$(MTC) -nologo -manifest "$(APPMANIFEST)" -outputresource:$@;1

"$(OX)\linkopts": "$(B)\win\Makefile.msc"
	echo "$(OX)\add.obj" > $@
	echo "$(OX)\alerts.obj" >> $@
	echo "$(OX)\allrepo.obj" >> $@
	echo "$(OX)\attach.obj" >> $@
	echo "$(OX)\backoffice.obj" >> $@
	echo "$(OX)\bag.obj" >> $@
	echo "$(OX)\bisect.obj" >> $@
	echo "$(OX)\blob.obj" >> $@
	echo "$(OX)\branch.obj" >> $@
	echo "$(OX)\browse.obj" >> $@
	echo "$(OX)\builtin.obj" >> $@
	echo "$(OX)\bundle.obj" >> $@
	echo "$(OX)\cache.obj" >> $@
	echo "$(OX)\capabilities.obj" >> $@
	echo "$(OX)\captcha.obj" >> $@
	echo "$(OX)\cgi.obj" >> $@
	echo "$(OX)\checkin.obj" >> $@
	echo "$(OX)\checkout.obj" >> $@
	echo "$(OX)\clearsign.obj" >> $@
	echo "$(OX)\clone.obj" >> $@
	echo "$(OX)\comformat.obj" >> $@
	echo "$(OX)\configure.obj" >> $@
	echo "$(OX)\content.obj" >> $@
	echo "$(OX)\cookies.obj" >> $@
	echo "$(OX)\cson_amalgamation.obj" >> $@
	echo "$(OX)\db.obj" >> $@
	echo "$(OX)\delta.obj" >> $@
	echo "$(OX)\deltacmd.obj" >> $@
	echo "$(OX)\descendants.obj" >> $@
	echo "$(OX)\diff.obj" >> $@
	echo "$(OX)\diffcmd.obj" >> $@
	echo "$(OX)\dispatch.obj" >> $@
	echo "$(OX)\doc.obj" >> $@

	echo "$(OX)\encode.obj" >> $@
	echo "$(OX)\etag.obj" >> $@
	echo "$(OX)\event.obj" >> $@
	echo "$(OX)\export.obj" >> $@
	echo "$(OX)\file.obj" >> $@
	echo "$(OX)\finfo.obj" >> $@
	echo "$(OX)\foci.obj" >> $@
899
900
901
902
903
904
905

906
907
908
909
910
911
912
	echo "$(OX)\regexp.obj" >> $@
	echo "$(OX)\report.obj" >> $@
	echo "$(OX)\rss.obj" >> $@
	echo "$(OX)\schema.obj" >> $@
	echo "$(OX)\search.obj" >> $@
	echo "$(OX)\security_audit.obj" >> $@
	echo "$(OX)\setup.obj" >> $@

	echo "$(OX)\sha1.obj" >> $@
	echo "$(OX)\sha1hard.obj" >> $@
	echo "$(OX)\sha3.obj" >> $@
	echo "$(OX)\shell.obj" >> $@
	echo "$(OX)\shun.obj" >> $@
	echo "$(OX)\sitemap.obj" >> $@
	echo "$(OX)\skins.obj" >> $@







>







906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
	echo "$(OX)\regexp.obj" >> $@
	echo "$(OX)\report.obj" >> $@
	echo "$(OX)\rss.obj" >> $@
	echo "$(OX)\schema.obj" >> $@
	echo "$(OX)\search.obj" >> $@
	echo "$(OX)\security_audit.obj" >> $@
	echo "$(OX)\setup.obj" >> $@
	echo "$(OX)\setupuser.obj" >> $@
	echo "$(OX)\sha1.obj" >> $@
	echo "$(OX)\sha1hard.obj" >> $@
	echo "$(OX)\sha3.obj" >> $@
	echo "$(OX)\shell.obj" >> $@
	echo "$(OX)\shun.obj" >> $@
	echo "$(OX)\sitemap.obj" >> $@
	echo "$(OX)\skins.obj" >> $@
1089
1090
1091
1092
1093
1094
1095

1096
1097
1098
1099
1100
1101
1102
	echo "$(SRCDIR)\../skins/bootstrap/details.txt" >> $@
	echo "$(SRCDIR)\../skins/bootstrap/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/bootstrap/header.txt" >> $@
	echo "$(SRCDIR)\../skins/default/css.txt" >> $@
	echo "$(SRCDIR)\../skins/default/details.txt" >> $@
	echo "$(SRCDIR)\../skins/default/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/default/header.txt" >> $@

	echo "$(SRCDIR)\../skins/eagle/css.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/details.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/header.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/css.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/details.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/footer.txt" >> $@







>







1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
	echo "$(SRCDIR)\../skins/bootstrap/details.txt" >> $@
	echo "$(SRCDIR)\../skins/bootstrap/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/bootstrap/header.txt" >> $@
	echo "$(SRCDIR)\../skins/default/css.txt" >> $@
	echo "$(SRCDIR)\../skins/default/details.txt" >> $@
	echo "$(SRCDIR)\../skins/default/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/default/header.txt" >> $@
	echo "$(SRCDIR)\../skins/default/js.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/css.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/details.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/eagle/header.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/css.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/details.txt" >> $@
	echo "$(SRCDIR)\../skins/enhanced1/footer.txt" >> $@
1119
1120
1121
1122
1123
1124
1125

1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143






1144
1145
1146
1147
1148
1149
1150
	echo "$(SRCDIR)\../skins/rounded1/header.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\ci_edit.js" >> $@
	echo "$(SRCDIR)\diff.tcl" >> $@

	echo "$(SRCDIR)\graph.js" >> $@
	echo "$(SRCDIR)\href.js" >> $@
	echo "$(SRCDIR)\login.js" >> $@
	echo "$(SRCDIR)\markdown.md" >> $@
	echo "$(SRCDIR)\menu.js" >> $@
	echo "$(SRCDIR)\sbsdiff.js" >> $@
	echo "$(SRCDIR)\scroll.js" >> $@
	echo "$(SRCDIR)\skin.js" >> $@
	echo "$(SRCDIR)\sorttable.js" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@

"$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c"

"$(OX)\add_.c" : "$(SRCDIR)\add.c"
	"$(OBJDIR)\translate$E" $** > $@







"$(OX)\allrepo$O" : "$(OX)\allrepo_.c" "$(OX)\allrepo.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\allrepo_.c"

"$(OX)\allrepo_.c" : "$(SRCDIR)\allrepo.c"
	"$(OBJDIR)\translate$E" $** > $@








>


















>
>
>
>
>
>







1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
	echo "$(SRCDIR)\../skins/rounded1/header.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
	echo "$(SRCDIR)\ci_edit.js" >> $@
	echo "$(SRCDIR)\diff.tcl" >> $@
	echo "$(SRCDIR)\forum.js" >> $@
	echo "$(SRCDIR)\graph.js" >> $@
	echo "$(SRCDIR)\href.js" >> $@
	echo "$(SRCDIR)\login.js" >> $@
	echo "$(SRCDIR)\markdown.md" >> $@
	echo "$(SRCDIR)\menu.js" >> $@
	echo "$(SRCDIR)\sbsdiff.js" >> $@
	echo "$(SRCDIR)\scroll.js" >> $@
	echo "$(SRCDIR)\skin.js" >> $@
	echo "$(SRCDIR)\sorttable.js" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@

"$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c"

"$(OX)\add_.c" : "$(SRCDIR)\add.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\alerts$O" : "$(OX)\alerts_.c" "$(OX)\alerts.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\alerts_.c"

"$(OX)\alerts_.c" : "$(SRCDIR)\alerts.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\allrepo$O" : "$(OX)\allrepo_.c" "$(OX)\allrepo.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\allrepo_.c"

"$(OX)\allrepo_.c" : "$(SRCDIR)\allrepo.c"
	"$(OBJDIR)\translate$E" $** > $@

1203
1204
1205
1206
1207
1208
1209






1210
1211
1212
1213
1214
1215
1216
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\cache$O" : "$(OX)\cache_.c" "$(OX)\cache.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cache_.c"

"$(OX)\cache_.c" : "$(SRCDIR)\cache.c"
	"$(OBJDIR)\translate$E" $** > $@







"$(OX)\captcha$O" : "$(OX)\captcha_.c" "$(OX)\captcha.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\captcha_.c"

"$(OX)\captcha_.c" : "$(SRCDIR)\captcha.c"
	"$(OBJDIR)\translate$E" $** > $@








>
>
>
>
>
>







1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\cache$O" : "$(OX)\cache_.c" "$(OX)\cache.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cache_.c"

"$(OX)\cache_.c" : "$(SRCDIR)\cache.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\capabilities$O" : "$(OX)\capabilities_.c" "$(OX)\capabilities.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\capabilities_.c"

"$(OX)\capabilities_.c" : "$(SRCDIR)\capabilities.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\captcha$O" : "$(OX)\captcha_.c" "$(OX)\captcha.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\captcha_.c"

"$(OX)\captcha_.c" : "$(SRCDIR)\captcha.c"
	"$(OBJDIR)\translate$E" $** > $@

1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331

"$(OX)\doc$O" : "$(OX)\doc_.c" "$(OX)\doc.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\doc_.c"

"$(OX)\doc_.c" : "$(SRCDIR)\doc.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\email$O" : "$(OX)\email_.c" "$(OX)\email.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\email_.c"

"$(OX)\email_.c" : "$(SRCDIR)\email.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\encode$O" : "$(OX)\encode_.c" "$(OX)\encode.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\encode_.c"

"$(OX)\encode_.c" : "$(SRCDIR)\encode.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\etag$O" : "$(OX)\etag_.c" "$(OX)\etag.h"







<
<
<
<
<
<







1334
1335
1336
1337
1338
1339
1340






1341
1342
1343
1344
1345
1346
1347

"$(OX)\doc$O" : "$(OX)\doc_.c" "$(OX)\doc.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\doc_.c"

"$(OX)\doc_.c" : "$(SRCDIR)\doc.c"
	"$(OBJDIR)\translate$E" $** > $@







"$(OX)\encode$O" : "$(OX)\encode_.c" "$(OX)\encode.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\encode_.c"

"$(OX)\encode_.c" : "$(SRCDIR)\encode.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\etag$O" : "$(OX)\etag_.c" "$(OX)\etag.h"
1701
1702
1703
1704
1705
1706
1707






1708
1709
1710
1711
1712
1713
1714
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\setup$O" : "$(OX)\setup_.c" "$(OX)\setup.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\setup_.c"

"$(OX)\setup_.c" : "$(SRCDIR)\setup.c"
	"$(OBJDIR)\translate$E" $** > $@







"$(OX)\sha1$O" : "$(OX)\sha1_.c" "$(OX)\sha1.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sha1_.c"

"$(OX)\sha1_.c" : "$(SRCDIR)\sha1.c"
	"$(OBJDIR)\translate$E" $** > $@








>
>
>
>
>
>







1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\setup$O" : "$(OX)\setup_.c" "$(OX)\setup.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\setup_.c"

"$(OX)\setup_.c" : "$(SRCDIR)\setup.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\setupuser$O" : "$(OX)\setupuser_.c" "$(OX)\setupuser.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\setupuser_.c"

"$(OX)\setupuser_.c" : "$(SRCDIR)\setupuser.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\sha1$O" : "$(OX)\sha1_.c" "$(OX)\sha1.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sha1_.c"

"$(OX)\sha1_.c" : "$(SRCDIR)\sha1.c"
	"$(OBJDIR)\translate$E" $** > $@

1938
1939
1940
1941
1942
1943
1944

1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955

1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
	$(RCC)  /fo $@ $**

"$(OX)\fossil.exe.manifest" : "$(B)\win\fossil.exe.manifest"
	copy /Y $** $@ 

"$(OX)\headers": "$(OBJDIR)\makeheaders$E" "$(OX)\page_index.h" "$(OX)\builtin_data.h" "$(OX)\default_css.h" "$(OX)\VERSION.h"
	"$(OBJDIR)\makeheaders$E" "$(OX)\add_.c":"$(OX)\add.h" \

			"$(OX)\allrepo_.c":"$(OX)\allrepo.h" \
			"$(OX)\attach_.c":"$(OX)\attach.h" \
			"$(OX)\backoffice_.c":"$(OX)\backoffice.h" \
			"$(OX)\bag_.c":"$(OX)\bag.h" \
			"$(OX)\bisect_.c":"$(OX)\bisect.h" \
			"$(OX)\blob_.c":"$(OX)\blob.h" \
			"$(OX)\branch_.c":"$(OX)\branch.h" \
			"$(OX)\browse_.c":"$(OX)\browse.h" \
			"$(OX)\builtin_.c":"$(OX)\builtin.h" \
			"$(OX)\bundle_.c":"$(OX)\bundle.h" \
			"$(OX)\cache_.c":"$(OX)\cache.h" \

			"$(OX)\captcha_.c":"$(OX)\captcha.h" \
			"$(OX)\cgi_.c":"$(OX)\cgi.h" \
			"$(OX)\checkin_.c":"$(OX)\checkin.h" \
			"$(OX)\checkout_.c":"$(OX)\checkout.h" \
			"$(OX)\clearsign_.c":"$(OX)\clearsign.h" \
			"$(OX)\clone_.c":"$(OX)\clone.h" \
			"$(OX)\comformat_.c":"$(OX)\comformat.h" \
			"$(OX)\configure_.c":"$(OX)\configure.h" \
			"$(OX)\content_.c":"$(OX)\content.h" \
			"$(OX)\cookies_.c":"$(OX)\cookies.h" \
			"$(OX)\db_.c":"$(OX)\db.h" \
			"$(OX)\delta_.c":"$(OX)\delta.h" \
			"$(OX)\deltacmd_.c":"$(OX)\deltacmd.h" \
			"$(OX)\descendants_.c":"$(OX)\descendants.h" \
			"$(OX)\diff_.c":"$(OX)\diff.h" \
			"$(OX)\diffcmd_.c":"$(OX)\diffcmd.h" \
			"$(OX)\dispatch_.c":"$(OX)\dispatch.h" \
			"$(OX)\doc_.c":"$(OX)\doc.h" \
			"$(OX)\email_.c":"$(OX)\email.h" \
			"$(OX)\encode_.c":"$(OX)\encode.h" \
			"$(OX)\etag_.c":"$(OX)\etag.h" \
			"$(OX)\event_.c":"$(OX)\event.h" \
			"$(OX)\export_.c":"$(OX)\export.h" \
			"$(OX)\file_.c":"$(OX)\file.h" \
			"$(OX)\finfo_.c":"$(OX)\finfo.h" \
			"$(OX)\foci_.c":"$(OX)\foci.h" \







>











>


















<







1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997

1998
1999
2000
2001
2002
2003
2004
	$(RCC)  /fo $@ $**

"$(OX)\fossil.exe.manifest" : "$(B)\win\fossil.exe.manifest"
	copy /Y $** $@ 

"$(OX)\headers": "$(OBJDIR)\makeheaders$E" "$(OX)\page_index.h" "$(OX)\builtin_data.h" "$(OX)\default_css.h" "$(OX)\VERSION.h"
	"$(OBJDIR)\makeheaders$E" "$(OX)\add_.c":"$(OX)\add.h" \
			"$(OX)\alerts_.c":"$(OX)\alerts.h" \
			"$(OX)\allrepo_.c":"$(OX)\allrepo.h" \
			"$(OX)\attach_.c":"$(OX)\attach.h" \
			"$(OX)\backoffice_.c":"$(OX)\backoffice.h" \
			"$(OX)\bag_.c":"$(OX)\bag.h" \
			"$(OX)\bisect_.c":"$(OX)\bisect.h" \
			"$(OX)\blob_.c":"$(OX)\blob.h" \
			"$(OX)\branch_.c":"$(OX)\branch.h" \
			"$(OX)\browse_.c":"$(OX)\browse.h" \
			"$(OX)\builtin_.c":"$(OX)\builtin.h" \
			"$(OX)\bundle_.c":"$(OX)\bundle.h" \
			"$(OX)\cache_.c":"$(OX)\cache.h" \
			"$(OX)\capabilities_.c":"$(OX)\capabilities.h" \
			"$(OX)\captcha_.c":"$(OX)\captcha.h" \
			"$(OX)\cgi_.c":"$(OX)\cgi.h" \
			"$(OX)\checkin_.c":"$(OX)\checkin.h" \
			"$(OX)\checkout_.c":"$(OX)\checkout.h" \
			"$(OX)\clearsign_.c":"$(OX)\clearsign.h" \
			"$(OX)\clone_.c":"$(OX)\clone.h" \
			"$(OX)\comformat_.c":"$(OX)\comformat.h" \
			"$(OX)\configure_.c":"$(OX)\configure.h" \
			"$(OX)\content_.c":"$(OX)\content.h" \
			"$(OX)\cookies_.c":"$(OX)\cookies.h" \
			"$(OX)\db_.c":"$(OX)\db.h" \
			"$(OX)\delta_.c":"$(OX)\delta.h" \
			"$(OX)\deltacmd_.c":"$(OX)\deltacmd.h" \
			"$(OX)\descendants_.c":"$(OX)\descendants.h" \
			"$(OX)\diff_.c":"$(OX)\diff.h" \
			"$(OX)\diffcmd_.c":"$(OX)\diffcmd.h" \
			"$(OX)\dispatch_.c":"$(OX)\dispatch.h" \
			"$(OX)\doc_.c":"$(OX)\doc.h" \

			"$(OX)\encode_.c":"$(OX)\encode.h" \
			"$(OX)\etag_.c":"$(OX)\etag.h" \
			"$(OX)\event_.c":"$(OX)\event.h" \
			"$(OX)\export_.c":"$(OX)\export.h" \
			"$(OX)\file_.c":"$(OX)\file.h" \
			"$(OX)\finfo_.c":"$(OX)\finfo.h" \
			"$(OX)\foci_.c":"$(OX)\foci.h" \
2032
2033
2034
2035
2036
2037
2038

2039
2040
2041
2042
2043
2044
2045
			"$(OX)\regexp_.c":"$(OX)\regexp.h" \
			"$(OX)\report_.c":"$(OX)\report.h" \
			"$(OX)\rss_.c":"$(OX)\rss.h" \
			"$(OX)\schema_.c":"$(OX)\schema.h" \
			"$(OX)\search_.c":"$(OX)\search.h" \
			"$(OX)\security_audit_.c":"$(OX)\security_audit.h" \
			"$(OX)\setup_.c":"$(OX)\setup.h" \

			"$(OX)\sha1_.c":"$(OX)\sha1.h" \
			"$(OX)\sha1hard_.c":"$(OX)\sha1hard.h" \
			"$(OX)\sha3_.c":"$(OX)\sha3.h" \
			"$(OX)\shun_.c":"$(OX)\shun.h" \
			"$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
			"$(OX)\skins_.c":"$(OX)\skins.h" \
			"$(OX)\smtp_.c":"$(OX)\smtp.h" \







>







2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
			"$(OX)\regexp_.c":"$(OX)\regexp.h" \
			"$(OX)\report_.c":"$(OX)\report.h" \
			"$(OX)\rss_.c":"$(OX)\rss.h" \
			"$(OX)\schema_.c":"$(OX)\schema.h" \
			"$(OX)\search_.c":"$(OX)\search.h" \
			"$(OX)\security_audit_.c":"$(OX)\security_audit.h" \
			"$(OX)\setup_.c":"$(OX)\setup.h" \
			"$(OX)\setupuser_.c":"$(OX)\setupuser.h" \
			"$(OX)\sha1_.c":"$(OX)\sha1.h" \
			"$(OX)\sha1hard_.c":"$(OX)\sha1hard.h" \
			"$(OX)\sha3_.c":"$(OX)\sha3.h" \
			"$(OX)\shun_.c":"$(OX)\shun.h" \
			"$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
			"$(OX)\skins_.c":"$(OX)\skins.h" \
			"$(OX)\smtp_.c":"$(OX)\smtp.h" \

Changes to www/aboutdownload.wiki.

1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30


31
32


33


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<title>How The Fossil Download Page Works</title>
<h1 align="center">How The Download Page Works</h1>

<h2>1.0 Overview</h2>

The [/uv/download.html|Download] page for the Fossil self-hosting
repository is implemented using [./unvers.wiki|unversioned files].
The "download.html" screen itself, and the various build products
are all stored as unversioned content.  The download.html page
uses AJAX to retrieve the [/help?cmd=/juvlist|/juvlist] webpage
for a list of all unversioned files.  Javascript within the

download.html page then figures out which unversioned files are
build products and paints appropriate icons on the displayed
download page.

When a new version is generated, the developers use the
[/help?cmd=uv|fossil uv edit] command to make minor changes
to the "[/uv/download.html?mimetype=text/plain|download.html]"
file so that it knows about the
new version number.  Then the developers run
the [/help?cmd=uv|fossil uv add] command for each
build product.  Finally, the
[/help?cmd=uv|fossil uv sync] command is run to push all
the content up to servers.  All
[./selfhost.wiki|three self-hosting repositories] for Fossil
are updated automatically.

<h2>2.0 Details</h2>

The current text of the "download.html" file can be seen


[/uv/download.html?mimetype=text/plain|here].  (Mouse-over that
hyperlink to see how the "mimetype=text/plain" query parameter


is added in order to display the file as plain text instead of


the usual HTML.)  The default mimetype for "download.html" is
text/html.  But because the entire page is enclosed within


    <b>&lt;div class='fossil-doc' data-title='Download Page'&gt;...&lt;/div&gt;</b>

Fossil knows to add its standard header and footer information to the
document, making it look just like any other page.  See
"[./embeddeddoc.wiki|embedded documentation]" for further details on
how this works.

With each new release, the "releases" variable in the javascript on
the [/uv/download.html?mimetype=text/plain|download.html] page is
edited (using "[/help?cmd=uv|fossil uv edit download.html]") to add
details of the release.

When the javascript on the "download.html" page runs, it requests
a listing of all unversioned content using the /juvlist URL.
([/juvlist|sample /juvlist output]).  The content of the download page is
constructed by matching unversioned files against regular expressions
in the "releases" variable.

Build products need to be constructed on different machines.  The precompiled
binary for Linux is compiled on Linux, the precompiled binary for Windows
is compiled on Windows10, and so forth.  After a new release is tagged,
the release manager goes around to each of the target platforms, checks
out the release and compiles it, then runs
[/help?cmd=uv|fossil uv add] for the build product followed by
[/help?cmd=uv|fossil uv sync] to push the new build product to the
[./selfhost.wiki|various servers].  This process is repeated for
each build product.

When older builds are retired from the download page, the
[/uv/download.html?mimetype=text/plain|download.html] page is again
edited to remove the corresponding entry from the "release" variable
and the edit is synced using
[/help?cmd=uv|fossil uv sync].  This causes the build products to
disappear from the download page immediately.  But those build products
are still taking up space in the unversioned content table of the
server repository.  To purge the obsolete build products, one or
more [/help?cmd=uv|fossil uv rm] commands are run, followed by










|
>
|





|











|
>
>
|
|
>
>
|
>
>
|

<






|


|
|


|
















|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<title>How The Fossil Download Page Works</title>
<h1 align="center">How The Download Page Works</h1>

<h2>1.0 Overview</h2>

The [/uv/download.html|Download] page for the Fossil self-hosting
repository is implemented using [./unvers.wiki|unversioned files].
The "download.html" screen itself, and the various build products
are all stored as unversioned content.  The download.html page
uses AJAX to retrieve the [/help?cmd=/juvlist|/juvlist] webpage
for a list of all unversioned files.  Javascript in the
[/uv/download.js?mimetype=text/plain|download.js] file (which is
sourced by "download.html") then figures out which unversioned files are
build products and paints appropriate icons on the displayed
download page.

When a new version is generated, the developers use the
[/help?cmd=uv|fossil uv edit] command to make minor changes
to the "[/uv/download.js?mimetype=text/plain|download.js]"
file so that it knows about the
new version number.  Then the developers run
the [/help?cmd=uv|fossil uv add] command for each
build product.  Finally, the
[/help?cmd=uv|fossil uv sync] command is run to push all
the content up to servers.  All
[./selfhost.wiki|three self-hosting repositories] for Fossil
are updated automatically.

<h2>2.0 Details</h2>

The current text of the "download.html" and "download.js" files can
be seen at:

   *   [/uv/download.html?mimetype=text/plain]
   *   [/uv/download.js?mimetype=text/plain]

Notice how the hyperlinks above use the "mimetype=text/plain"
query parameter in order to display the file as plain text
instead of the usual HTML or Javascript.

The default mimetype for "download.html" is
text/html.  But because the entire page is enclosed within


    <b>&lt;div class='fossil-doc' data-title='Download Page'&gt;...&lt;/div&gt;</b>

Fossil knows to add its standard header and footer information to the
document, making it look just like any other page.  See
"[./embeddeddoc.wiki|embedded documentation]" for further details on
how &lt;div class='fossil-doc'&gt; this works.

With each new release, the "releases" variable in the javascript on
the [/uv/download.js?mimetype=text/plain|download.js] page is
edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add
details of the release.

When the javascript in the "download.js" file runs, it requests
a listing of all unversioned content using the /juvlist URL.
([/juvlist|sample /juvlist output]).  The content of the download page is
constructed by matching unversioned files against regular expressions
in the "releases" variable.

Build products need to be constructed on different machines.  The precompiled
binary for Linux is compiled on Linux, the precompiled binary for Windows
is compiled on Windows10, and so forth.  After a new release is tagged,
the release manager goes around to each of the target platforms, checks
out the release and compiles it, then runs
[/help?cmd=uv|fossil uv add] for the build product followed by
[/help?cmd=uv|fossil uv sync] to push the new build product to the
[./selfhost.wiki|various servers].  This process is repeated for
each build product.

When older builds are retired from the download page, the
[/uv/download.js?mimetype=text/plain|download.js] page is again
edited to remove the corresponding entry from the "release" variable
and the edit is synced using
[/help?cmd=uv|fossil uv sync].  This causes the build products to
disappear from the download page immediately.  But those build products
are still taking up space in the unversioned content table of the
server repository.  To purge the obsolete build products, one or
more [/help?cmd=uv|fossil uv rm] commands are run, followed by

Added www/alerts.md.

















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
Email Alerts
============

The email alert system is a work-in-progress.
This documentation was last updated on 2018-08-12.
Check back later for updates.

Email Alerts And Notifications
------------------------------

Beginning with version 2.7, Fossil supports the ability to send
email messages to subscribers alerting them to changes in the repository.
Subscribers can request an email notification of the following kinds
of changes:

  *  New check-ins
  *  Changes to any ticket
  *  Changes to any wiki page
  *  New forum posts
  *  Announcements

Subscribers can either elect to receive emails as soon as these events happen,
or they can receive a daily digest of the events instead.

Email alerts are sent by a [Fossil server](./server.wiki).  You must
have a server [set up to make use of email alerts](#setup).  Email
alerts do not currently work if you are only using Fossil from the
command line.

Users and Subscribers
---------------------

Fossil makes a distinction between "users" and "subscribers".  A
user is someone with a username and password — someone who can
log in.  A subscriber is someone who receives email alerts.  Users
can also be subscribers and subscribers can be users, but that does
not have to be the case.  It is possible to be a user without being
a subscriber and to be a subscriber without being a user.

In the repository database file, users are tracked with the USER table
and subscribers are tracked via the SUBSCRIBER table.

<a id="setup"></a>
Activating Email Alerts
-----------------------

Email alerts are turned off by default.  To activate them, log into
the Fossil server as an administrator and visit the 
[Admin/Notification](/setup_notification)
setup page. ([`/setup_notification`](/setup_notification))

Important:  Email alerts are configured using Admin/Notification, not
Admin/Email-Server.  The Email-Server setup screen is used to configure
a different subsystem within Fossil.

The Admin/Notification setup screen lets you configure how Fossil should
send email for alerts.  There are some required fields at the top of the
screen for elements such as the "From:" address for outgoing emails,
the URL for the Fossil server, and a nickname for the repository that
will appear in the "Subject:" line of outgoing emails.  But the key
setup parameter is the "Email Send Method".

Fossil supports multiple methods for sending email alerts:

  1.  Pipe the email message text into a command, such as `sendmail`.
  2.  Store email messages as individual files in a directory and let
      some other process set up by the administrator take care of
      reading and forwarding those files.
  3.  Store email messages as entries in an SQLite database where
      some external process and read and forward the emails.
  4.  Send emails to an SMTP Relay.
  5.  Send emails directly to the recipients via SMTP.

As of 2018-08-08, method (5) is not yet supported, but there are plans
to add support soon.

The self-hosting Fossil repository at <https://www.fossil-scm.org/> currently
uses method (3).  Outgoing email messages are added to an SQLite database
file.  A separate daemon process continously monitors that database file,
extracts email messages as they are added, and hands them off to 
"procmail" to be sent on to the final recipient.  The self-hosting
Fossil repository uses this technique rather than method (1) because
it is running inside of a restrictive chroot jail which is unable to
hand off messages to "procmail" directly.  The daemon that monitors the
email database is a [short TCL script](/file/tools/email-sender.tcl).
That daemon is started automatically by adding this line:

      /usr/bin/tclsh /home/www/fossil/email-sender.tcl &

To the `/etc/rc.local` file of the Ubuntu server that hosts the
repository.

After making necessary changes to the Admin/Notification page, test
those changes by clicking the "[Send Announcement](/announce)" link
at the top of the page.  Fill in your email address in the "To:"
line and a test message below, and press "Send Message" to verify that
outgoing email is working.

<a id="cap7"></a>
Once email notification is working, one must also adjust user permissions
to allow users to subscribe to email notification.  On the 
Setup/User page, under the permissions for each user, is a new capability
called "Email Alerts".  The corresponding capability letter is "7".
That new "7" capability must be enabled in order for
users to be able to become subscribers.  To allow anonymous passers-by
on the internet to subscribe, simply enable "Email Alerts" for the
"nobody" user category. To require that the user solve a simple CAPTCHA
first, add it to the "anonymous" user category instead.

Signing Up For Email Notification
---------------------------------

Users and/or anonymous passers-by can visit the 
[`/subscribe`](/subscribe) page to sign
up for email notification.

If your users are getting the following complaint from Fossil:

<blockquote>
  Use a different login with greater privilege than FOO to access
  /subscribe
</blockquote>

...then you forgot to [give capability 7](#cap7) to that user or to a
user category that the user is a member of.

After signing up, a single verification email
is sent.  The new subscriber must click a link on that email in order to
activate the subscription.

Subscription verification emails are only sent once.  This is a defense
against malicious robots that try to harass innocent internet users
by having subscription pages send multiple verification emails.
If the initial subscription verification does not go through correctly,
an administrator must intervene to reset the subscription.

Every subscriber has a long random hexadecimal security code that serves
as their password.  All email notifications contain a link back to the
Fossil server, incorporating this security code, which allows the 
subscriber to adjust their subscription options.

Administrator Activities
------------------------

The repository administrator has unlimited control over individual
subscriptions.  The "[List Subscribers](/subscribers)" button at the top
of the Setup/Notification screen gives a list of subscribers.  Clicking on
any subscriber link allows the administrator to adjust the subscription.

To unsubscribe, first select the "unsubscribe" checkbox, then press the
"Unsubscribe" button.

The "verified" checkbox determines whether or not an email address has
been verified.  This can be enabled or disabled manually by the
administrator.

Cloning, Syncing, and Backups
-----------------------------

The Setup/Notification settings are not replicated using clone or sync.
In a network of peer repositories, you only want one repository sending
email notifications.  If you were to replicate the email notification
settings to a separate repository, then subscribers would get multiple
notifications for each event, which would be bad.

However, the subscriber list can be synced for backup purposes.  Use the
[`fossil config pull subscriber`](/help?cmd=configuration) command to
pull the latest subscriber list from a server into a backup repository.

Added www/backoffice.md.















































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
Backoffice
==========

This is technical documentation about the internal workings of Fossil.
Ordinary Fossil users do not need to know about anything covered by this
document.  The information here is intended for people who want to enhance
or extend the Fossil code, or who just want a deeper understanding of 
the internal workings of Fossil.

What Is The Backoffice
----------------------

The backoffice is a mechanism used by a 
[Fossil server](/doc/trunk/www/server.wiki) to do low-priority
background work that is not directly related to the user interface.  Here
are some examples of the kinds of work that backoffice performs:

  1.  Sending email alerts and notifications
  2.  Sending out daily digests of email notifications
  3.  Other background email handling chores
  4.  Automatic syncing of peer repositories
  5.  Repository maintenance and optimization

(As of 2018-08-07, only items 1 and 2 have actually been implemented.)
The idea is that the backoffice handles behind-the-scenes work that does
not have tight latency requirements.

When Backoffice Runs
--------------------

A backoffice process is usually launched automatically by a webpage
request.  After each webpage is generated, Fossil checks to see if any
backoffice work needs to be done. If there is work to do, and no other
process is already assigned to do the work, then a new backoffice process
is started to do the work.

This happens for every webpage, regardless of how that webpage is launched,
and regardless of the purpose of the webpage.  This also happens on the
server for "[fossil sync](/help?cmd=sync)" and
[fossil clone](/help?cmd=clone)" commands which are implemented as
web requests - albeit requests that the human user never sees.
Web requests can arrive at the Fossil server via direct TCP/IP (for example
when Fossil is started using commands like "[fossil server](/help?cmd=server)")
or via [CGI](/doc/trunk/www/server.wiki) or
[SCGI](/doc/trunk/www/scgi.wiki) or via SSH.
A backoffice process might be started regardless of the origin of the
request.

The backoffice is not a daemon.  Each backoffice process runs for a short
while and then exits.  This helps keep Fossil easy to manage, since there
are no daemons to start and stop.  To upgrade Fossil to a new version,
you simply replace the older "fossil" executable with the newer one, and
the backoffice processes will (within a minute or so) start using the new
one.  (Upgrading the executable on Windows is more complicated, since on 
Windows it is not possible to replace an executable file that is in active 
use.  But Windows users probably already know this.)

The backoffice is serialized and rate limited.  No more than a single
backoffice process will be running at once, and backoffice runs will not
occur more frequently than once every 60 seconds.

If a Fossil server is idle, then no backoffice processes will be running.
That means there are no extra processes sitting around taking up memory 
and process table slots for seldom accessed repositories.
The backoffice is an on-demand system.
A busy repository will usually have a backoffice
running at all times.  But an infrequently accessed repository will only have
backoffice processes running for a minute or two following the most recent
access.

Manually Running The Backoffice
-------------------------------

The automatic backoffice runs are sufficient for most installations.
However, the daily digest of email notifications is handled by the
backoffice.  If a Fossil server can sometimes go more than a day without
being accessed, then the automatic backoffice will never run, and the
daily digest might not go out until somebody does visit a webpage.
If this is a problem, an adminstrator can set up a cron job to
periodically run:

>   fossil backoffice -R _REPOSITORY_

That command will cause backoffice processing to occur immediately.
Note that this is almost never necessary for an internet-facing
Fossil repository, since most repositories will get multiple accesses
per day from random robots, which will be sufficient to kick off the
daily digest emails.  And even for a private server, if there is very
little traffic, then the daily digests are probably a no-op anyhow
and won't be missed.

How Backoffice Is Implemented
-----------------------------

The backoffice is implemented by the "backoffice.c" source file.

Serialization and rate limiting is handled by a single entry in the
repository database CONFIG table named "backoffice".  This entry is
called "the lease".  The value of the lease
is a text string representing four integers, which
are respectively:

  1.  The process id of the "current" backoffice process
  2.  The lease expiration time of the current backoffice process
  3.  The process id of the "next" backoffice process
  4.  The lease expiration time for the next backoffice process

Times are expressed in seconds since 1970.  A process id of zero means
"no process".  Sometimes the process id will be non-zero even if there
is no corresponding process. Fossil knows how to figure out whether or
not a process still exists.

You can print out a decoded copy of the current backoffice lease using
this command:

>  fossil test-backoffice-lease -R _REPOSITORY_

If a system has been idle for a long time, then there will be no
backoffice processes.  (Either the process id entries in the lease
will be zero, or there will exist no process associated with the
process id.) When a new web request comes in, the system
sees that no backoffice process is active and so it kicks off a separate
process to run backoffice.  

The new backoffice process becomes the "current" process.  It sets a 
lease expiration time for itself to be 60 seconds in the future.
Then it does the backoffice processing and exits.  Note that usually
the backoffice process will exit long before its lease expires.  That
is ok.  The lease is there to limit the rate at which backoffice processes
run.

If a new backoffice process starts up and sees that the "current" lease has
yet to expire, the new process makes itself the "next" backoffice process
and sets its expiration time to be 60 seconds past the expiration time of
the "current" backoffice process.  The "next" process then puts itself to
sleep until the "current" lease expires.  After the "current"
lease expires and the "current" process has itself exited, then
the "next" process promotes itself to the new "current" process.  It
sets the current lease expiration to be 60 seconds in the future, runs
whatever backoffice work is needed, then exits.

If a new backoffice process starts up and finds that there is already
a "current" lease and a "next" process, it exits without doing anything.
This should happen only rarely, since the lease information is checked
prior to spawning the backoffice process, so a conflict will only happen
in a race.

Because the "backoffice" entry of the CONFIG table is in the repository
database, access to the lease is serialized.  The lease prevents more
than one backoffice process from running at a time.  It prevents
backoffice processes from running more frequently than once every 60 seconds.
And, it guarantees (assuming processes are not killed out-of-band) that
every web request will be followed within 60 seconds by a backoffice
run.

Debugging The Backoffice
------------------------

The backoffice should "just work".  It should not require administrator
attention.  However, if you suspect that something is not working right,
there are some debugging aids.

We have already mentioned the command that shows the backoffice lease
for a repository:

>  fossil test-backoffice-lease -R _REPOSITORY_

Running that command every few seconds should show what is going on with
backoffice processing in a particular repository.

There are also two settings that control backoffice behavior.  The
"backoffice-nodelay" setting prevents the "next" process from taking a
lease and sleeping.  If "backoffice-nodelay" is set, that causes all
backoffice processes to exit either immediately or after doing whatever
backoffice works needs to be done.  If something is going wrong and
backoffice leases are causing delays in webpage processing, then setting
"backoffice-nodelay" to true can work around the problem until the bug
can be fixed.  The "backoffice-logfile" setting is the name of a log
file onto which is appended a short message everything a backoffice
process actually starts to do the backoffice work.  This log file can
be used to verify that backoffice really is running, if there is any
doubt.  Most installations should leave "backoffice-nodelay" off and
leave "backoffice-logfile" unset or set to an empty string.

Changes to www/build.wiki.

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
file "<b>win\buildmsvc.bat</b>" may be used and it will attempt to
detect and use the latest installed version of MSVC.<br><br>To enable
the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
first <a href="https://www.openssl.org/source/">download the official
source code for OpenSSL</a> and extract it to an appropriately named
"<b>openssl-X.Y.ZA</b>" subdirectory within the local
[/tree?ci=trunk&name=compat | compat] directory (e.g.
"<b>compat/openssl-1.0.2o</b>"), then make sure that some recent
<a href="http://www.perl.org/">Perl</a> binaries are installed locally,
and finally run one of the following commands:
<blockquote><pre>
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
</pre></blockquote>
<blockquote><pre>
buildmsvc.bat FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin







|







134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
file "<b>win\buildmsvc.bat</b>" may be used and it will attempt to
detect and use the latest installed version of MSVC.<br><br>To enable
the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
first <a href="https://www.openssl.org/source/">download the official
source code for OpenSSL</a> and extract it to an appropriately named
"<b>openssl-X.Y.ZA</b>" subdirectory within the local
[/tree?ci=trunk&name=compat | compat] directory (e.g.
"<b>compat/openssl-1.0.2p</b>"), then make sure that some recent
<a href="http://www.perl.org/">Perl</a> binaries are installed locally,
and finally run one of the following commands:
<blockquote><pre>
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
</pre></blockquote>
<blockquote><pre>
buildmsvc.bat FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin

Changes to www/changes.wiki.

1
2






























3
4
5
6
7
8
9
<title>Change Log</title>































<a name='v2_6'></a>
<h2>Changes for Version 2.6 (2018-05-04)</h2>

  *  Fix a bug that was causing crashes while trying to clone the TCL
     repository.  This fix is the main reason for the current release.
  *  Added the new "Classic" timeline viewing mode.  "Classic" is the
     same as "Verbose" in the previous release.  The "Verbose" mode is


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<title>Change Log</title>

<a name='v2_7'></a>
<h2>Changes for Version 2.7 (2018-09-??)</h2>

  *  Add the [./alerts.md|email alerts] feature for commits, ticket
     changes, wiki changes, forum posts, and announcements.  This is
     still a work in progress.  It works, but it is not as easy to 
     setup and use as it ought to be.
  *  Add the [./forum.wiki|discussion forum] feature.
  *  Add new user capabilities letters needed to support alerts and forum.
     Formerly, user capabilities were letters from &#91;a-z&#93;, but with the
     enhancements, the supply of lower case letters was exhausted.
     User capabilities are now letters in &#91;a-zA-Z0-9&#93;.
  *  The default skin is now responsive, providing better layout on
     small screens, including mobile devices.
  *  The default skin now includes a hamburger menu that is generated
     by the [/sitemap] page.
  *  The [/sitemap] and other list pages show as multiple columns if
     the viewing window is wide enough.
  *  There is an optional "js" file for each skin that can be used to
     hold javascript.  This file can be loaded by reference or can be
     included in the header or footer.
  *  Add the [./backoffice.md|backoffice].
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 10.0 to 11.0.
  *  Improvements to the "Security Audit" administration page
  *  Update the built-in SQLite to version 3.25.0.
  *  Some code and interfaces are in place to support sending and
     receiving email directly via SMTP, but this feature is not yet
     complete or ready for production use.

<a name='v2_6'></a>
<h2>Changes for Version 2.6 (2018-05-04)</h2>

  *  Fix a bug that was causing crashes while trying to clone the TCL
     repository.  This fix is the main reason for the current release.
  *  Added the new "Classic" timeline viewing mode.  "Classic" is the
     same as "Verbose" in the previous release.  The "Verbose" mode is

Changes to www/emaildesign.md.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
are not created in new repositories by default.  The tables only
come into existance if email notification is configured and used.


  *  <b>SUBSCRIBER</b> &rarr;
     The subscriber table records the email address for people who
     want to receive email notifications.  Each subscriber has a
     "subscriberCode" which is a random 32-byte blob that uniquely
     identifies the subscriber.  There are also fields to indicate
     what kinds of notifications the subscriber wishes to receive,
     whether or not the email address of the subscriber has been
     verified, etc.

  *  <b>PENDING\_ALERT</b> &rarr;
     The PENDING\_ALERT table contains records that define events







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
are not created in new repositories by default.  The tables only
come into existance if email notification is configured and used.


  *  <b>SUBSCRIBER</b> &rarr;
     The subscriber table records the email address for people who
     want to receive email notifications.  Each subscriber has a
     `subscriberCode` which is a random 32-byte blob that uniquely
     identifies the subscriber.  There are also fields to indicate
     what kinds of notifications the subscriber wishes to receive,
     whether or not the email address of the subscriber has been
     verified, etc.

  *  <b>PENDING\_ALERT</b> &rarr;
     The PENDING\_ALERT table contains records that define events
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
the linkage between users and subscribers.  But it is also possible
to be a user without being a subscriber, or to be a subscriber without
being a user.

Sending Email Messages
----------------------

Fossil expects to interact with an external mail agent.
There are currently three different methods for sending outbound
email messages from Fossil to the external mail agent:

  1.  <b>"pipe"</b> &rarr; Invoke an external command that accepts
      the email message on standard input.  This is useful if the
      host computer has a command like /usr/sbin/sendmail that will
      accept well-formed email messages from standard input and forward







|







53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
the linkage between users and subscribers.  But it is also possible
to be a user without being a subscriber, or to be a subscriber without
being a user.

Sending Email Messages
----------------------

Fossil expects to interact with an external [mail transfer agent][MTA].
There are currently three different methods for sending outbound
email messages from Fossil to the external mail agent:

  1.  <b>"pipe"</b> &rarr; Invoke an external command that accepts
      the email message on standard input.  This is useful if the
      host computer has a command like /usr/sbin/sendmail that will
      accept well-formed email messages from standard input and forward
99
100
101
102
103
104
105



106
107
108
109
110
111
112
All emails are text/plain and use a transfer-encoding of base64.

There is a utility command-line program named 
["tools/decode-email.c"](/file/tools/decode-email.c) in
the Fossil source tree.  If you compile this program, you can use it
to convert the base64 transfer-encoding into human-readable output for
testing and debugging.




Receiving Email Messages
------------------------

Inbound email messages (for example bounces from failed notification
emails) should be relayed to the "fossil email inbound" command.  That
command is currently a no-op place-holder.  At some point, we will need







>
>
>







99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
All emails are text/plain and use a transfer-encoding of base64.

There is a utility command-line program named 
["tools/decode-email.c"](/file/tools/decode-email.c) in
the Fossil source tree.  If you compile this program, you can use it
to convert the base64 transfer-encoding into human-readable output for
testing and debugging.

[MTA]: https://en.wikipedia.org/wiki/Message_transfer_agent


Receiving Email Messages
------------------------

Inbound email messages (for example bounces from failed notification
emails) should be relayed to the "fossil email inbound" command.  That
command is currently a no-op place-holder.  At some point, we will need
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

Email Address Verification
--------------------------

When anonymous passers-by on the internet sign up for email notifications,
their email address must first be verified.  An email message is sent to
the address supplied inviting the user to click on a link.  The link includes
the random 32-byte subscriberCode in hex.  If anyone visits the link, the
email address is verified.

There is no password.  Knowledge of the subscriberCode is sufficient to
control the subscription.  This is not a secure as a separate password,
but on the other hand it is easier for the average subscriber to deal
with in that they don't have to come up with yet another password.  Also,
even if the subscriberCode is stolen, the worst that can happens is that
the thief can change your subscription settings.  No PII (other than
the subscriber's email address) is available to an attacker with the
subscriberCode.  Nor can knowledge of the subscriberCode lead to a
email flood or other annoyance attack, as far as I can see.

If subscriberCodes are ever compromised, new ones can be generated
as follows:

>   UPDATE subscriber SET subscriberCode=randomblob(32);

Perhaps the system be enhanced to randomize the
subscriberCodes periodically - say just before each daily digest
is sent out?

User Control Of Their Subscription
----------------------------------

If a user has a separate account with a login and password for
the repository, then their subscription is linked to their account.
On the /login page is a link to a page to control their subscription.

For users without logins, they can request a link to a page for
controling their subscription on the /alerts or /unsubscribe page.
The link is sent via email, and includes the subscriberCode.

Internal Processing Flow
------------------------

Almost all of the email notification code is found in the "src/email.c"
source file.

When email notifications are enabled, a trigger is created in the schema
(the "email_trigger1" trigger) that adds a new entry to the
PENDING_ALERT table every time a row is added to the EVENT table.
During a "rebuild", the EVENT table is rebuilt from scratch, and we
do not want users to get notifications for every historical check-in,
so the trigger is disabled during "rebuild".

Email notifications are sent out by the email_send_alerts() function.
This function is can be called by having a cron job invoke the
"fossil email exec" command.  Or, if the email-autoexec setting is
enabled, then email_send_alerts() is invoked automatically after each
successful webpage is generated.  The latter approach is used on the
Fossil self-hosting repository.  The email_send_alerts() function is
a no-op (obviously) if there are no pending events to be sent.

Digests are handled by recording the time of the last digest in the
"email-last-digest" setting, and only sending a new digest if the
current time is one day or later after the last digest.

Individual emails are sent to each subscriber.  I ran tests and found
that I could send about 1200 emails/second, which is fast enough so that
I do not need to resort to trying to notify multiple subscribers with
a single email.  Because each subscriber gets a separate email, the
system can include information in the email that is unique to the
subscriber, such as a link to the page to edit their subscription.  That
link includes the subscriberCode.

Other Notes
-----------

The "fossil configuration pull subscriber" command pulls down the content
of the SUBSCRIBER table.  This is intended to as a backup-only.  It
is not desirable to have two or more systems sending emails to the
same people for the same repository, as that would mean users would
receive duplicate emails.  Hence the settings that control email 
notifications are not transmitted with the pull.  The "push", "export",
and "import" commands all work similarly







|


|



|


|





|


|










|
|




|



|
|
|

|

|

|
|

|



|



|




|




|
|


|
|
|
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223

Email Address Verification
--------------------------

When anonymous passers-by on the internet sign up for email notifications,
their email address must first be verified.  An email message is sent to
the address supplied inviting the user to click on a link.  The link includes
the random 32-byte `subscriberCode` in hex.  If anyone visits the link, the
email address is verified.

There is no password.  Knowledge of the `subscriberCode` is sufficient to
control the subscription.  This is not a secure as a separate password,
but on the other hand it is easier for the average subscriber to deal
with in that they don't have to come up with yet another password.  Also,
even if the `subscriberCode` is stolen, the worst that can happens is that
the thief can change your subscription settings.  No PII (other than
the subscriber's email address) is available to an attacker with the
`subscriberCode`.  Nor can knowledge of the `subscriberCode` lead to a
email flood or other annoyance attack, as far as I can see.

If subscriberCodes are ever compromised, new ones can be generated
as follows:

        UPDATE subscriber SET subscriberCode=randomblob(32);

Perhaps the system be enhanced to randomize the
`subscriberCodes` periodically - say just before each daily digest
is sent out?

User Control Of Their Subscription
----------------------------------

If a user has a separate account with a login and password for
the repository, then their subscription is linked to their account.
On the /login page is a link to a page to control their subscription.

For users without logins, they can request a link to a page for
controling their subscription on the `/alerts` or `/unsubscribe` page.
The link is sent via email, and includes the `subscriberCode`.

Internal Processing Flow
------------------------

Almost all of the email notification code is found in the `src/email.c`
source file.

When email notifications are enabled, a trigger is created in the schema
(the `email_trigger1` trigger) that adds a new entry to the
`PENDING_ALERT` table every time a row is added to the `EVENT` table.
During a `fossil rebuild`, the `EVENT` table is rebuilt from scratch; since we
do not want users to get notifications for every historical check-in,
the trigger is disabled during `rebuild`.

Email notifications are sent out by the `email_send_alerts()` function.
This function is can be called by having a cron job invoke the
`fossil email exec` command.  Or, if the email-autoexec setting is
enabled, then `email_send_alerts()` is invoked automatically after each
successful webpage is generated.  The latter approach is used on the
Fossil self-hosting repository.  The `email_send_alerts()` function is
a no-op (obviously) if there are no pending events to be sent.

Digests are handled by recording the time of the last digest in the
`email-last-digest` setting, and only sending a new digest if the
current time is one day or later after the last digest.

Individual emails are sent to each subscriber.  I ran tests and found
that I could send about 1200 emails/second, which is fast enough that
I do not need to resort to trying to notify multiple subscribers with
a single email.  Because each subscriber gets a separate email, the
system can include information in the email that is unique to the
subscriber, such as a link to the page to edit their subscription.  That
link includes the `subscriberCode`., 

Other Notes
-----------

The `fossil configuration pull subscriber` command pulls down the content
of the `SUBSCRIBER` table.  This is intended to as a backup-only.  It
is not desirable to have two or more systems sending emails to the
same people for the same repository, as that would mean users would
receive duplicate emails.  Hence, the settings that control email 
notifications are not transmitted with the pull.  The `push`, `export`,
and `import` commands all work similarly.

Changes to www/env-opts.md.

117
118
119
120
121
122
123




124
125
126
127
128
129
130
`FOSSIL_HOME`, `LOCALAPPDATA` (Windows), `APPDATA` (Windows),
`HOMEDRIVE` and `HOMEPATH` (Windows, used together), and `HOME` is
used as the location of the `~/.fossil` file.

`EDITOR`: Name the editor to use for check-in and stash comments.
Overridden by the local or global `editor` setting or the `VISUAL`
environment variable.





`FOSSIL_FORCE_TICKET_MODERATION`: If set, *ALL* changes for tickets
will be required to go through moderation (even those performed by the
local interactive user via the command line).  This can be useful for
local (or remote) testing of the moderation subsystem and its impact
on the contents and status of tickets.








>
>
>
>







117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
`FOSSIL_HOME`, `LOCALAPPDATA` (Windows), `APPDATA` (Windows),
`HOMEDRIVE` and `HOMEPATH` (Windows, used together), and `HOME` is
used as the location of the `~/.fossil` file.

`EDITOR`: Name the editor to use for check-in and stash comments.
Overridden by the local or global `editor` setting or the `VISUAL`
environment variable.

`FOSSIL_BREAK`: If set, an opportunity will be created to attach a
debugger to the Fossil process prior to any significant work being
performed.

`FOSSIL_FORCE_TICKET_MODERATION`: If set, *ALL* changes for tickets
will be required to go through moderation (even those performed by the
local interactive user via the command line).  This can be useful for
local (or remote) testing of the moderation subsystem and its impact
on the contents and status of tickets.

Changes to www/fileformat.wiki.

69
70
71
72
73
74
75

76
77
78
79
80
81
82
83
84
85
<li> [#manifest | Manifests] </li>
<li> [#cluster | Clusters] </li>
<li> [#ctrl | Control Artifacts] </li>
<li> [#wikichng | Wiki Pages] </li>
<li> [#tktchng | Ticket Changes] </li>
<li> [#attachment | Attachments] </li>
<li> [#event | TechNotes] </li>

</ul>

These seven structural artifact types are described in subsections below.

Structural artifacts are ASCII text.  The artifact may be PGP clearsigned.
After removal of the PGP clearsign header and suffix (if any) a structural
artifact consists of one or more "cards" separated by a single newline
(ASCII: 0x0a) character. Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other







>


|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<li> [#manifest | Manifests] </li>
<li> [#cluster | Clusters] </li>
<li> [#ctrl | Control Artifacts] </li>
<li> [#wikichng | Wiki Pages] </li>
<li> [#tktchng | Ticket Changes] </li>
<li> [#attachment | Attachments] </li>
<li> [#event | TechNotes] </li>
<li> [#forum | Forum Posts] </li>
</ul>

These eight structural artifact types are described in subsections below.

Structural artifacts are ASCII text.  The artifact may be PGP clearsigned.
After removal of the PGP clearsign header and suffix (if any) a structural
artifact consists of one or more "cards" separated by a single newline
(ASCII: 0x0a) character. Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
where the technote is displayed) and a unique identifier for the technote.
When there are multiple artifacts with the same technote-id, the one with
the most recent D card is the only one used.  The technote-id must be a
40-character lower-case hexadecimal string.

The optional N card specifies the mimetype of the text of the technote
that is contained in the W card.  If the N card is omitted, then the
W card text mimetype is assumed to be text/x-fossil, which is the
Fossil wiki format.

The optional P card specifies a prior technote with the same technote-id
from which the current technote is an edit.  The P card is a hint to the
system that it might be space efficient to store one technote as a delta of
the other.








|







493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
where the technote is displayed) and a unique identifier for the technote.
When there are multiple artifacts with the same technote-id, the one with
the most recent D card is the only one used.  The technote-id must be a
40-character lower-case hexadecimal string.

The optional N card specifies the mimetype of the text of the technote
that is contained in the W card.  If the N card is omitted, then the
W card text mimetype is assumed to be text/x-fossil-wiki, which is the
Fossil wiki format.

The optional P card specifies a prior technote with the same technote-id
from which the current technote is an edit.  The P card is a hint to the
system that it might be space efficient to store one technote as a delta of
the other.

523
524
525
526
527
528
529










































































530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553

554
555
556
557
558
559
560
561
562
563

564
565
566
567

568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584

585
586
587
588
589

590
591
592
593
594
595
596
597
598
599
600
601
602
603
604

605
606
607
608
609
610
611
612
613
614


































615
616
617
618
619
620
621
622

623
624
625
626
627
628
629
630
631
632
633
634

635
636
637
638
639
640
641
642
643
644

645
646
647
648
649

650
651
652
653
654
655
656
657
658
659
660
661
662
663
664

665
666
667
668
669
670
671
672
673
674

675
676
677
678

679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694

695
696
697
698
699
700
701
702
703

704
705
706
707
708
709
710
711
712
713

714
715
716
717
718
719
720
721
722
723

724
725
726

727
728
729
730
731
732
733

A single W card provides wiki text for the document associated with the
technote.  The format of the W card is exactly the same as for a
[#wikichng | wiki artifact].

The Z card is the required checksum over the rest of the artifact.












































































<a name="summary"></a>
<h2>3.0 Card Summary</h2>

The following table summarizes the various kinds of cards that appear
on Fossil artifacts. A blank entry means that combination of card and
artifact is not legal. A number or range of numbers indicates the number
of times a card may (or must) appear in the corresponding artifact type.
e.g. a value of 1 indicates a required unique card and 1+ indicates that one
or more such cards are required.

<table border=1 width="100%">
<tr>
<th rowspan=2 valign=bottom>Card Format</th>
<th colspan=7>Used By</th>
</tr>
<tr>
<th>Manifest</th>
<th>Cluster</th>
<th>Control</th>
<th>Wiki</th>
<th>Ticket</th>
<th>Attachment</th>
<th>Technote</th>

</tr>
<tr>
<td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>

</tr>
<tr>
<td><b>B</b> <i>baseline</i></td>
<td align=center><b>0-1*</b></td>

<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr><td>&nbsp;</td><td colspan='7'>* = Required for delta manifests</td></tr>
<tr>
<td><b>C</b> <i>comment-text</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>

</tr>
<tr>
<td><b>D</b> <i>date-time-stamp</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>

<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
</tr>
<tr>
<td><b>E</b> <i>technote-time technote-id</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>

</tr>
<tr>
<td><b>F</b> <i>filename</i> ?<i>uuid</i>? ?<i>permissions</i>? ?<i>oldname</i>?</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>


































</tr>
<tr>
<td><b>J</b> <i>name</i> ?<i>value</i>?</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>

<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>K</b> <i>ticket-uuid</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>

</tr>
<tr>
<td><b>L</b> <i>wiki-title</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>

</tr>
<tr>
<td><b>M</b> <i>uuid</i></td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>

<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>N</b> <i>mimetype</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>

</tr>
<tr>
<td><b>P</b> <i>uuid ...</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>

</tr>
<tr>
<td><b>Q</b> (<b>+</b>|<b>-</b>)<i>uuid</i> ?<i>uuid</i>?</td>
<td align=center><b>0+</b></td>

<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>R</b> <i>md5sum</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>

<tr>
<td><b>T</b> (<b>+</b>|<b>*</b>|<b>-</b>)<i>tagname</i> <i>uuid</i> ?<i>value</i>?</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0+</b></td>

</tr>
<tr>
<td><b>U</b> <i>username</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>

</tr>
<tr>
<td><b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>

</tr>
<tr>
<td><b>Z</b> <i>md5sum</i></td>

<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>














|









>










>



|
>







<









>





>















>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








>












>










>





>















>










>




>
















>









>










>










>



>







524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859

A single W card provides wiki text for the document associated with the
technote.  The format of the W card is exactly the same as for a
[#wikichng | wiki artifact].

The Z card is the required checksum over the rest of the artifact.

<a name="forum"></a>
<h3>2.8 Forum Posts</h3>

Forum posts are intended as a mechanism for users and developers to 
discuss a project.  Forum posts are like messages on a mailing list.

The following cards are allowed on an forum post artifact:

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>G</b> <i>thread-root</i><br />
<b>H</b> <i>thread-title</i><br />
<b>I</b> <i>in-reply-to</i><br />
<b>N</b> <i>mimetype</i><br />
<b>P</b> <i>parent-artifact-id</i><br />
<b>U</b> <i>user-name</i><br />
<b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
<b>Z</b> <i>checksum</i>
</blockquote>

Every forum post must have either one I card and one G card
or one H card.  
Forum posts are organized into topic threads.  The initial
post for a thread (the root post) has an H card giving the title or
subject for that thread.  The argument to the H card is a string
in the same format as a comment string in a C card.
All follow-up posts have an I card that
indicates which prior post in the same thread the current forum
post is replying to, and a G card specifying the root post for
the entire thread.  The argument to G and I cards is the
artifact hash for the prior forum post to which the card refers.

In theory, it is sufficient for follow-up posts to have only an
I card, since the G card value could be computed by following a
chain of I cards.  However, the G card is required in order to
associate the artifact with a forum thread in the case where an
intermediate artifact in the I card chain is shunned or otherwise
becomes unreadable.

A single D card is required to give the date and time when the
forum post was created.

The optional N card specifies the mimetype of the text of the technote
that is contained in the W card.  If the N card is omitted, then the
W card text mimetype is assumed to be text/x-fossil-wiki, which is the
Fossil wiki format.

The optional P card specifies a prior forum post for which this
forum post is an edit.  For display purposes, only the child post
is shown, though the historical post is retained as a record.
If P cards are used and there exist multiple versions of the same
forum post, then I cards for other artifacts refer to whichever
version of the post was current at the time the reply was made,
but G cards refer to the initial, unedited root post for the thread.
Thus, following the chain of I cards back to the root of the thread
may land on a different post than the one given in the G card.
However, following the chain of I cards back to the thread root,
then following P cards back to the initial version of the thread
root must give the same artifact as is provided by the G card,
otherwise the artifact containing the G card is considered invalid
and should be ignored.

In general, P cards may contain multiple arguments, indicating a
merge.  But since forum posts cannot be merged, the
P card of a forum post may only contain a single argument.

The U card gives name of the user who entered the forum post.

A single W card provides wiki text for the forum post.
The format of the W card is exactly the same as for a
[#wikichng | wiki artifact].

The Z card is the required checksum over the rest of the artifact.


<a name="summary"></a>
<h2>3.0 Card Summary</h2>

The following table summarizes the various kinds of cards that appear
on Fossil artifacts. A blank entry means that combination of card and
artifact is not legal. A number or range of numbers indicates the number
of times a card may (or must) appear in the corresponding artifact type.
e.g. a value of 1 indicates a required unique card and 1+ indicates that one
or more such cards are required.

<table border=1 width="100%">
<tr>
<th rowspan=2 valign=bottom>Card Format</th>
<th colspan=8>Used By</th>
</tr>
<tr>
<th>Manifest</th>
<th>Cluster</th>
<th>Control</th>
<th>Wiki</th>
<th>Ticket</th>
<th>Attachment</th>
<th>Technote</th>
<th>Forum</th>
</tr>
<tr>
<td><b>A</b> <i>filename</i> <i>target</i> ?<i>source</i>?</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>B</b> <i>baseline</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>

<tr>
<td><b>C</b> <i>comment-text</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>D</b> <i>date-time-stamp</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
</tr>
<tr>
<td><b>E</b> <i>technote-time technote-id</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>F</b> <i>filename</i> ?<i>uuid</i>? ?<i>permissions</i>? ?<i>oldname</i>?</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>G</b> <i>thread-root</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
</tr>
<tr>
<td><b>H</b> <i>thread-title</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
</tr>
<tr>
<td><b>I</b> <i>in-reply-to</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
</tr>
<tr>
<td><b>J</b> <i>name</i> ?<i>value</i>?</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>K</b> <i>ticket-uuid</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>L</b> <i>wiki-title</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>M</b> <i>uuid</i></td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>N</b> <i>mimetype</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>
</tr>
<tr>
<td><b>P</b> <i>uuid ...</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>
</tr>
<tr>
<td><b>Q</b> (<b>+</b>|<b>-</b>)<i>uuid</i> ?<i>uuid</i>?</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>R</b> <i>md5sum</i></td>
<td align=center><b>0-1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<tr>
<td><b>T</b> (<b>+</b>|<b>*</b>|<b>-</b>)<i>tagname</i> <i>uuid</i> ?<i>value</i>?</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
<td align=center><b>1+</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>0+</b></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><b>U</b> <i>username</i></td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>0-1</b></td>
<td align=center><b>0-1</b></td>
<td align=center><b>1</b></td>
</tr>
<tr>
<td><b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
</tr>
<tr>
<td><b>Z</b> <i>md5sum</i></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>
<td align=center><b>1</b></td>

Added www/forum.wiki.







































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
<title>Fossil Forums</title>

<h2>Introduction</h2>

As of Fossil 2.7, Fossil includes a built-in discussion forum feature.

Any project complex enough to benefit from being managed by Fossil and
which has more than one user can probably also benefit from having a
discussion forum. Even if your project has a discussion forum already,
there are many benefits to using Fossil's built-in forum feature, some
of which you cannot get by using third-party alternatives:

  *  <b>Easy to Administer:</b> Third-party discussion forum and mailing
     list software tends to be difficult to install, set up, and
     administer. The Fossil forum feature aims to be as close to
     zero-configuration as is practical.

  *  <b>Malefactor Resistant:</b> Because Fossil accepts forum posts
     only via the web UI, it is inherently [./antibot.wiki | protected
     against bots].

  *  <b>Distributed and Tamper-Proof:</b> Posts are stored in the Fossil
     repository using the same [./fileformat.wiki | block chain technology]
     that Fossil uses to store your check-ins, wiki documents, etc.
     Posts sync to cloned repositories in a tamper-proof fashion.

  *  <b>Space Efficient:</b> Because of Fossil's [./delta_format.wiki |
     delta compression technology], discussions add little to the size
     of a cloned repository. Ten years of the SQLite project's
     discussions — averaging about 2 dozen posts per day — compress down
     to [https://fossil-scm.org/forum/forumpost/9b6f3f36bdb | just
     35&nbsp;MB of space] in a Fossil forum repository.

  *  <b>Built-in Full-Text Search:</b> Fossil forums use
     [https://sqlite.org/fts3.html | SQLite's powerful FTS4 engine] to
     handle searches. If your project currently uses a mailing list for
     discussions, this means you are no longer reliant upon third-party
     mailing list archive services to provide a useful search engine for
     your discussions. If you are running a private Fossil repository,
     you may not even have the <em>option</em> of delegating this useful
     service to a third-party; Fossil provides this service out of the
     box.

  *  <b>One Result Per Matching Post:</b> When you search the forum
     archives via the Fossil web interface, you get only one result for
     each matching post. When you search for project information via a
     standard web search engine, you might get a result from the project
     site's own mail archive plus one from Nabble, one from Gmane, one
     from The Mail Archive...

  *  <b>Search Off-Line:</b> Because Fossil is a [./concepts.wiki |
     distributed version control system], project members can search
     your forum archive while disconnected from the network where the
     central Fossil instance runs. Your past discussions are potentially
     just as valuable as a wiki document or checkin comment: there is no
     good reason why you should have to wait to get back on the Internet
     or back to the office before you can search for past posts.

  *  <b>Contribute Off-Line:</b> Fossil forum posts work like any other
     insertion into the repository, so a user can create new threads and
     reply to existing ones while off-line, then sync their
     contributions to the server they cloned from when back on-line.
     Yes, you can post to the forum from inside a tent, miles from the
     nearest wifi router or cellular data tower.

  *  <b>Interlink with Other Fossil-Managed Artifacts:</b> Because forum
     posts are normal Fossil artifacts, you can interlink them with
     other Fossil artifacts using short internal links: link to forum
     threads from a [./tickets.wiki | ticket], link to a wiki document
     from a forum post, etc.
     
  *  <b>Durable Links:</b> Once you create a valid internal artifact
     link in Fossil, it <em>remains</em> valid, durably. With
     third-party forum software and mailing list search engines, your
     links are only valid until the third-party component changes its
     URL scheme or disappears from the web.

  *  <b>Role-Based Access Control:</b> The forum uses the same
     [https://en.wikipedia.org/wiki/Role-based_access_control | RBAC
     system] that Fossil uses to control all other repository accesses.
     The Fossil forum feature simply adds several new fine-grained
     capability bits to the existing system.

  *  <b>Enduring, Open File Format:</b> Since Fossil has an
     [./fileformat.wiki | open and well-documented file format], your
     discussion archives are truly that: <em>archives</em>. You are no
     longer dependent on the lifetime and business model of a
     third-party piece of software or service. Should you choose to stop
     using Fossil, you can easily extract your discussion traffic for
     transfer to another system.

  *  <b>Lightweight Markup:</b> Posts can be marked up using Fossil's
     existing [/md_rules | Markdown] and [/wiki_rules | Wiki] markup
     processors. No longer must you choose between two bad options: to
     restrict posts to plain text only or to allow wild-west
     HTML-formatted MIME email. Fossil's lightweight markup language
     formatting features give you a middle path, providing your users
     enough formatting power to communicate complex ideas well without
     providing so much power as to risk
     [https://wonko.com/post/html-escaping | security problems].

  *  <b>Easy Notification Emails:</b> It is easy to configure Fossil to
     send email notifications of new posts to interested forum users via
     your Fossil server's existing
     [https://en.wikipedia.org/wiki/Message_transfer_agent | message
     transfer agent] (MTA): Postfix, Exim, Sendmail... Notification
     emails include the complete message content for the benefit of
     those that prefer to visit the forum only when they need to post
     something. Notifications are optional, and each user gets the
     choice of immediate or daily digest delivery.

  *  <b>Talks to Everyone:</b> Because Fossil delegates email handling
     to your existing MTA, it does not need to implement the
     [http://sqlite.1065341.n5.nabble.com/Many-ML-emails-going-to-GMail-s-SPAM-tp98685p98722.html
     | roughly two dozen]
     [https://en.wikipedia.org/wiki/Request_for_Comments | RFCs]
     required in order to properly support SMTP email in this complex
     world we've built.  As well, this design choice means you do not
     need to do duplicate configuration, such as to point Fossil at your
     server's TLS certificate private key in order to support users
     behind mail servers that require STARTTLS encryption.


<h2 id="setup">Setting up a Fossil Forum</h2>

<h3 id="caps">Capabilities</h3>

Fossil forums use the same role-based access control mechanism as
for normal Fossil repository logins.

There are several dedicated forum-related capability bits you can grant
a user:

  *  <b>Read Forum</b> (<tt>2</tt>): The user may read forum posts.

  *  <b>Write Forum</b> (<tt>3</tt>): The user may create new forum
     threads, reply to existing threads, and edit their own posts. New
     posts are held for moderation, and they are marked to prevent them
     from being included in clone and sync operations.

  *  <b>WriteTrusted Forum</b> (<tt>4</tt>): Same as <b>Write Forum</b>
     except that forum updates bypass the [#moderation | moderation and
     private artifact restrictions].

  *  <b>Moderate Forum</b> (<tt>5</tt>): User gets buttons on posts
     which allow them to either reject or approve posts held for
     moderation. User also gets access to a page (<tt>/modreq</tt>)
     showing the list of pending moderation tasks.

  *  <b>Supervise Forum</b> (<tt>6</tt>): User can grant or revoke
     <b>WriteTrusted</b> capability for other users. (Currently
     unimplemented.)

  *  <b>Email Alerts</b> (<tt>7</tt>): User can sign themselves up for
     email notifications, a.k.a. alerts.

By default, no Fossil user has permission to use the forums except for
users with Setup and Admin capabilities, which get these as part of the
large package of other capabilities they get.

For public Fossil repositories that wish to accept new users without
involving a human, go into Admin &rarr; Access and enable the "Allow
users to register themselves" setting. You may also wish to give users
in the <tt>anonymous</tt> category the Read Forum (2) and Write Forum
(3) capabilities: this allows people to post without creating an account
simply by solving [./antibot.wiki | a simple CAPTCHA].

For a private repository, you probably won't want to give the
<tt>anonymous</tt> user any forum access, but you may wish to give the
Read Forum capability (2) to users in the <tt>reader</tt> category.

For either type of repository, you are likely to want to give at least
the WriteTrusted capability (4) to users in the <tt>developer</tt>
category. If you did not give the Read Forum capability (2) to
<tt>anonymous</tt> above, you should give <tt>developer</tt> that
capability here if you choose to give it capability 3 or 4.

If you want to use the email notification feature, by default only those
users in the Setup and Admin user categories can make use of it. Grant
the Email Alerts capability (7) to give others access to this feature.
Alternately, you can handle notification signups outside of Fossil, with
a Setup or Admin users manually signing users up via Admin &rarr;
Notification. You'll want to grant this capability to the
<tt>nobody</tt> user category if you want anyone to sign up without any
restrictions.  Give it to <tt>anonymous</tt> instead if you want the
user to solve a simple CAPTCHA before signing up. Or, give it to
<tt>reader</tt> or <tt>developer</tt> if you want only users with Fossil
logins to have this ability. (That's assuming you give one or both of
these capabilities to every user on your Fossil repository.)

By following this advice, you should not need to tediously add
capabilities to individual accounts except in atypical cases, such as
to grant the Moderate Forum capability (5) to an uncommonly
highly-trusted user.


<h3 id="skin">Skin Setup</h3>

If you create a new Fossil repository with version 2.7 or newer, its
default skin is already set up correctly for typical forum
configurations.

If you have an existing repository, you have two choices if you want its
skin to be upgraded to support forums:

<ol>
  <li>Go into Admin &rarr; Skins and switch from your current skin to
  one of the stock skins.  If you were on a stock skin, just switch away
  from your current one to the actual stock skin, since they will be
  different after the upgrade.</li>

  <li>If you have local customization that you do not want to throw
  away, you can use the diff feature of Fossil's skin editor to show how
  the skins differ.</li>
</ol>

The remainder of this section summarizes the differences you're expected
to see when taking option #2.

The first thing is that you'll need to add something like the following
to the Header part of the skin to create the navbar link:

<verbatim>
  if {[anycap 234567] || [anoncap 2] || [anoncap 3]} {
    menulink /forum Forum
  }
</verbatim>

These rules say that any logged-in user with any forum-related
capability (2-7 inclusive, as of this writing) or an anonymous user with
read or write capability on the forum (2, 3) will see the "Forum" navbar
link, which just takes you to <tt>/forum</tt>.

The exact code you need here varies depending on which skin you're
using. Follow the style you see for the other navbar links.

The new forum feature also brings many new CSS styles to the table. If
you're using the stock skin or something sufficiently close, the changes
may work with your existing skin as-is. Otherwise, you might need to
adjust some things, such as the background color used for the selected
forum post:

<verbatim>
  div.forumSel {
    background-color: rgba(0, 0, 0, 0.05);
  }
</verbatim>

That overrides the default — a hard-coded light cyan — with a 95%
transparent black overlay instead, which simply darkens your skin's
normal background color underneath the selected post. That should work
with almost any background color except for very dark background colors.
For dark skins, an inverse of the above trick will work better:

<verbatim>
  div.forumSel {
    background-color: rgba(255, 255, 255, 0.05);
  }
</verbatim>

That overlays the background with 5% white to lighten it slightly.

Another new forum-related CSS style you might want to reflect into your
existing skin is:

<verbatim>
  div.forumPosts a:visited {
    color: #6A7F94;
  }
</verbatim>

This changes the clicked-hyperlink color for the forum post links on the
main <tt>/forum</tt> page only, which allows your browser's history
mechanism to show which threads a user has read and which not. The link
color will change back to the normal link color — indicating "unread" —
when a reply is added to an existing thread because that changes where
the link from the <tt>/forum</tt> page points, taking you to the newest
post in the thread.

The color given above is suitable for the stock skin.

Beware that when changing this example, there are some
[https://hacks.mozilla.org/2010/03/privacy-related-changes-coming-to-css-vistited/
| stringent restrictions] in modern browsers to prevent snoopy web sites
from brute-forcing your browsing history. (See the link for the method,
which explains the restrictions.)


<h3 id="search">Enable Forum Search</h3>

One of the underlying assumptions of the forum feature is that you will
want to be able to search the forum archives, so the <tt>/forum</tt>
page always includes a search box. Since that depends on search being
enabled on the Fossil repository, Fossil warns that search is disabled
until you go into Admin &rarr; Search and enable the "Search Forum"
setting.

You may want to enable some of the other Fossil search features while
you're in there. All of this does come at some CPU and I/O cost, which
is why it's disabled by default.


<h3 id="sso">Single Sign-On</h3>

If you choose to host your discussion forums within the same repository
as your project's other Fossil-managed content, you inherently have a
single sign-on system. Contrast third-party mailing list and forum
software where you either end up with two separate user tables and
permission sets, or you must go to significant effort to integrate the
two login systems.

You may instead choose to host your forums in a Fossil repository
separate from your project's main Fossil repository. A good reason to do
this is that you have a public project where very few of those
participating in the forum have special capability bits for project
assets managed by Fossil, so you wish to segregate the two user sets.

Yet, what of the users who will have logins on both repositories? Some
users will be trusted with access to the project's main Fossil
repository, and these users will probably also participate in the
project's Fossil-hosted forum. Fossil has a feature to solve this
problem which is probably less well known than it should be, and which
has been a feature of Fossil since April of 2011: Admin &rarr;
Login-Group. This allows one Fossil repository to recognize users
authorized on a different Fossil repository.


<h3 id="alerts">Email Notifications (Alerts)</h3>

The detailed information for this feature of Fossil is in two other
documents:

  *  [./alerts.md | Email Alerts]
  *  [./emaildesign.md | Email Notification Design]

You may want to study those in detail, since email is complicated. Some
of that complexity necessarily leaks out to the configuration of email
notification in Fossil.


<h4 id="quick">Quick Setup</h4>

However, if you've already got a working Postfix, Exim, or Sendmail
server on the machine running your Fossil instance(s), and you aren't
using Fossil's <tt>chroot</tt> feature to wall it off from the rest of
the machine, it's fairly simple to set up email notifications:

<ol>
    <li>Go to Admin &rarr; Notification and fill out all of the
    <b>Required</b> fields:</li>

    <ul>
        <li><b>Canonical server URL</b> — Use the suggested URL</li>
        <li><b>"From" email address</b> — <tt>forum-bounces@example.com</tt>
        is traditional, but do what you like</li>
        <li><b>Repository nickname</b> — See the suggested examples on
        the web page.</li>
    </ul>

    <li>Set "Email Send Method" to "Pipe to a command"</li>

    <li>Set the "Administrator email address" to a suitable email address.
    It could be the same value you used for the "From" address above,
    or it could be a different value like
    <tt>admin@example.com</tt>.</li>
</ol>

Save your changes.

<a id="cd"></a>For all of the following <tt>fossil</tt> CLI commands,
we're going to assume that you've <tt>cd</tt>'d into a checkout
directory for your repository on the central server.  Otherwise, we'd
need to add <tt>-R /path/to/repo.fossil</tt> to each of them.

Give this command:

<verbatim>
    $ fossil set email-send-command
</verbatim>

If that gives a blank value instead of <tt>sendmail -ti</tt>, say

<verbatim>
    $ fossil set email-send-command "sendmail -ti"
</verbatim>

to force the setting. That's a
[https://fossil-scm.org/forum/forumpost/840b676410 | known bug] which
should be squished soon.

This is the correct command even if you're running Postfix or Exim
rather than Sendmail. These mail servers (and perhaps others) provide a
<tt>sendmail</tt> command for this very sort of reason: it lets programs
send email without having to care exactly which server implementation is
running at a given site.

<a id="status"></a>If you reload the Email Notification Setup page, the
Status section at the top should show:

<verbatim>
    Outgoing Email: Piped to command "sendmail -ti"
    Pending Alerts: 0 normal, 0 digest
    Subscribers:    0 active, 0 total
</verbatim>


<h4 id="subscribe">Subscribe to Alerts</h4>

Above, we see that there are no subscribers, so the next step is to add
one.

Go to the <tt>/alerts</tt> page on your Fossil instance and sign your
regular Fossil user up for email alerts. (Alternate path: click the user
name or login/logout link in the upper right corner, depending on the
skin you're using, then go to "Email Alerts".) You will need "Forum
Posts" checked at the least for the testing steps below.

If you use the same user name and email address as you used for your
normal user login, Fossil will simply tie your alert preferences to your
login record, and the email address in your user's Contact Info field
will be considered already-verified. Otherwise, Fossil will create an
alert-only record, and you will have to verify the email address before
Fossil will send notifications to it.

This shows a key aspect of the way Fossil's email alerts system works,
by the way: a user can be signed up for email alerts without having a
full-fledged Fossil user account. Only when both user names are the same
are the two records tied together under the hood.


<h4 id="alert-test">Test the Email Subsystem</h4>

If you'd rather not create an inane "testing" post in your Fossil
instance just to force out an email alert, you can test the email
subsystem separately from the rest of the Fossil email alerts system
with the following command:

<verbatim>
    $ fossil alerts test-message you@example.com --body README.md --subject Test
</verbatim>

That should send you an email with "Test" in the subject line and the
contents of your project's <tt>README.md</tt> file in the body. If it
works, the file's contents may be truncated. I'm uncertain whether
that's due to an intentionally-small buffer size or if it's a bug.

That command assumes that your project [./foss-cklist.wiki | contains a
readme file]. Of course it does. Why would it not?


<h4 id="frist" name="first">First Post</h4>

Since you've already edited the skin per [#skin | the instructions
above], you can click the "Forum" link in the navbar and create a new
post. I suggest taking the time to compose a suitable introductory
message especially for your project's forum, one which a new user would
find helpful.

Wait a few seconds, and you should receive a notification email with the
post's subject and body text in the email.


<h4 id="trouble">Troubleshooting</h4>

If email alerts aren't working, there are several useful commands you
can give to figure out why.

(Be sure to [#cd | <tt>cd</tt> into a repo checkout directory] first!)

<verbatim>
    $ fossil alerts status
</verbatim>

This should give much the same information as you saw [#status | above].
One difference is that, since you've created a forum post, the
<tt>pending-alerts</tt> value should only be zero if you did in fact get
the requested email alert. If it's zero, check your mailer's spam
folder. If it's nonzero, continue with these troubleshooting steps.

<verbatim>
    $ fossil backoffice
</verbatim>

That forces Fossil to run its [./backoffice.md | "back office" process].
Its only purpose at the time of this writing is to push out alert
emails, but it might do other things later. Sometimes it can get stuck
and needs to be kicked. For that reason, you might want to set up a
crontab entry to make sure it runs occasionally.

<verbatim>
    $ fossil alerts send
</verbatim>

This should also kick off the backoffice processing, if there are any
pending alerts to send out.

<verbatim>
    $ fossil alert pending
</verbatim>

Show any pending alerts. The number of lines output here should equal
the [#status | status output above].

<verbatim>
    $ fossil test-add-alerts f5900
    $ fossil alert send
</verbatim>

Manually create an email alert and push it out immediately.

The <tt>f</tt> in the first command's final parameter means you're
scheduling a "forum" alert. The integer is the ID of a forum post, which
you can find by visiting <tt>/timeline?showid</tt> on your Fossil
instance.

The second command above is necessary because the
<tt>test-add-alerts</tt> command doesn't kick off a backoffice run.

<verbatim>
    $ fossil ale send
</verbatim>

This only does the same thing as the final command above, rather than
send you an ale, as you might be hoping. Sorry.


<h2 id="moderation">How Moderation Works</h2>

In this section, we're going to call all of the following a "forum
update:"

  *  create a new post
  *  reply to an existing post
  *  edit a post or reply

When a person with the normal <b>Write Forum</b> capability (<tt>3</tt>)
updates the forum, Fossil saves the update in its block chain, but this
update is impermanent because of two other table updates made at the
same time:

<ol>
    <li>Fossil saves the update artifact's ID in its <tt>private</tt>
    table, preventing Fossil from sending such artifacts to any of the
    repository's clones.  (This is the same mechanism behind
    [./private.wiki | private branches].)</li>

    <li>Fossil also adds a reference to that artifact in the
    <tt>modreq</tt> table, which backs the moderation feature.  This is
    what causes Fossil to leave out the Reply button when rendering that
    post's HTML in the forum's web interface.</li>
</ol>

When a moderator approves an update, Fossil deletes these table entries,
making the update semi-permanent. This changes how Fossil renders the
HTML for that update. It also means the artifact will now sync to
clones, if the sync is done by a user with <b>Check-Out</b> capability
(<tt>o</tt>).

When a forum user edits a moderator-approved artifact, what actually
happens under the hood is that Fossil writes another artifact to the
repository which refers to the original version as its parent, causing
Fossil UI to present the new version instead of the original. The
original version remains in the repository, just as with historical
checkins. The parent must remain in the repository for referential
integrity purposes.

When you "Delete" a moderator-approved post or reply through Fossil UI,
it's actually an edit with blank replacement content. The only way to
truly delete such artifacts is through [./shunning.wiki | shunning].

When a user with <b>WriteTrusted Forum</b> capability (<tt>4</tt>)
updates the forum, it happens in the same way except that Fossil skips
the <tt>private</tt> and <tt>modreq</tt> table insertions.

When a moderator rejects an update, that artifact is unceremoniously
removed from the tip of the block chain. This is safe because Fossil
prevents replies to a reply or post awaiting moderator approval, so
referential integrity cannot be harmed.  Rejecting an edit is even
safer, since the original post remains behind, so that replies continue
to refer to that original post.


<h2 id="mod-user">Using the Moderation Feature</h2>

Having described all of the work that Fossil performs under the hood on
behalf of its users, we can now give the short list of work left for the
repository's administrators and moderators:

<ol>
    <li>Add the <b>Moderate Forum</b> capability (<tt>5</tt>) to any of
    your users who should have this ability. You don't need to do this
    for any user with Setup (<tt>s</tt>) or Admin (<tt>a</tt>)
    capability; it's
    [http://fossil-scm.org/index.html/artifact/b16221ffb736caa2?ln=1246-1257
    | already included].</li>

    <li>When someone updates the forum, an entry will appear in the
    timeline if the type filter is set to "Forum" or "Any Type". If that
    user has only the <b>Write Forum</b> capability (<tt>3</tt>), any
    other user with the <b>Moderate Forum</b> capability (<tt>5</tt>)
    will see a conditional link appear at the top of the main forum
    page: "Moderation Requests".  Clicking this takes the moderator to
    the <tt>/modreq</tt> page. A moderator may wish to keep the main
    forum page open in a browser tab, reloading it occasionally to see
    when the "Moderation Requests" link reappears.</li>

    <li>A moderator viewing an update pending moderation sees two
    buttons at the bottom, "Approve" and "Reject" in place of the
    "Delete" button that the post's creator sees. Beware that both
    actions are durable and have no undo. Be careful!</li>
</ol>

Changes to www/hacker-howto.wiki.

9
10
11
12
13
14
15

  *  [./tech_overview.wiki | A Technical Overview of Fossil]
  *  [./adding_code.wiki | Adding Features To Fossil]
  *  [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
  *  [./fileformat.wiki|Fossil Artifact File Format]
  *  [./style.wiki | Coding Style Guidelines]
  *  [./checkin.wiki | Pre-checkin Checklist]
  *  [../test/release-checklist.wiki | Release Checklist]








>
9
10
11
12
13
14
15
16
  *  [./tech_overview.wiki | A Technical Overview of Fossil]
  *  [./adding_code.wiki | Adding Features To Fossil]
  *  [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project]
  *  [./fileformat.wiki|Fossil Artifact File Format]
  *  [./style.wiki | Coding Style Guidelines]
  *  [./checkin.wiki | Pre-checkin Checklist]
  *  [../test/release-checklist.wiki | Release Checklist]
  *  [./backoffice.md | The "backoffice" subsystem]

Changes to www/index.wiki.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
<li> [../COPYRIGHT-BSD2.txt | License]
<li> [./faq.wiki | FAQ]
<li> [./changes.wiki | Change Log]
<li> [./hacker-howto.wiki | Hacker How-To]
<li> [./fossil-v-git.wiki | Fossil vs. Git]
<li> [./hints.wiki | Tip &amp; Hints]
<li> [./permutedindex.html | Documentation Index]
<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
<li> Mailing list
     <ul>
     <li> [http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/fossil-users | sign-up]
     <li> [http://www.mail-archive.com/fossil-users@lists.fossil-scm.org | archives]
     </ul>
</ul>
<img src="fossil3.gif" align="center">
</div>

<p>Fossil is a simple, high-reliability, distributed software configuration
management system with these advanced features:

  1.  <b>Integrated Bug Tracking, Wiki, and Technotes</b> -
      In addition to doing [./concepts.wiki | distributed version control]
      like Git and Mercurial,
      Fossil also supports [./bugtheory.wiki | bug tracking],
      [./wikitheory.wiki | wiki], and [./event.wiki | technotes].


  2.  <b>Built-in Web Interface</b> -
      Fossil has a built-in,
      [https://fossil-scm.org/skins/index.html | themeable],
      and intuitive [./webui.wiki | web interface]
      with a rich variety of information pages
      ([./webpage-ex.md|examples]) promoting situational awareness.







|
<
<
<
|
<







|



|
>







10
11
12
13
14
15
16
17



18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<li> [../COPYRIGHT-BSD2.txt | License]
<li> [./faq.wiki | FAQ]
<li> [./changes.wiki | Change Log]
<li> [./hacker-howto.wiki | Hacker How-To]
<li> [./fossil-v-git.wiki | Fossil vs. Git]
<li> [./hints.wiki | Tip &amp; Hints]
<li> [./permutedindex.html | Documentation Index]
<li> [https://fossil-scm.org/forum | Forum ]



<li> [http://www.mail-archive.com/fossil-users@lists.fossil-scm.org | Mailing list archives]

</ul>
<img src="fossil3.gif" align="center">
</div>

<p>Fossil is a simple, high-reliability, distributed software configuration
management system with these advanced features:

  1.  <b>Integrated Bug Tracking, Wiki, Forum, and Technotes</b> -
      In addition to doing [./concepts.wiki | distributed version control]
      like Git and Mercurial,
      Fossil also supports [./bugtheory.wiki | bug tracking],
      [./wikitheory.wiki | wiki], [./forum.wiki | forum], and
      [./event.wiki | technotes].

  2.  <b>Built-in Web Interface</b> -
      Fossil has a built-in,
      [https://fossil-scm.org/skins/index.html | themeable],
      and intuitive [./webui.wiki | web interface]
      with a rich variety of information pages
      ([./webpage-ex.md|examples]) promoting situational awareness.

Changes to www/mkindex.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13

14

15
16
17
18
19
20
21
#!/usr/bin/env tclsh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
#    tclsh mkindex.tcl
#

set doclist {
  aboutcgi.wiki {How CGI Works In Fossil}
  aboutdownload.wiki {How The Download Page Works}
  adding_code.wiki {Adding New Features To Fossil}
  adding_code.wiki {Hacking Fossil}

  antibot.wiki {Defense against Spiders and Bots}

  blame.wiki {The Annotate/Blame Algorithm Of Fossil}
  branching.wiki {Branching, Forking, Merging, and Tagging}
  bugtheory.wiki {Bug Tracking In Fossil}
  build.wiki {Compiling and Installing Fossil}
  changes.wiki {Fossil Changelog}
  checkin_names.wiki {Check-in And Version Names}
  checkin.wiki {Check-in Checklist}













>

>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env tclsh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
#    tclsh mkindex.tcl
#

set doclist {
  aboutcgi.wiki {How CGI Works In Fossil}
  aboutdownload.wiki {How The Download Page Works}
  adding_code.wiki {Adding New Features To Fossil}
  adding_code.wiki {Hacking Fossil}
  alerts.md {Email Alerts And Notification}
  antibot.wiki {Defense against Spiders and Bots}
  backoffice.md {The "Backoffice" mechanism of Fossil}
  blame.wiki {The Annotate/Blame Algorithm Of Fossil}
  branching.wiki {Branching, Forking, Merging, and Tagging}
  bugtheory.wiki {Bug Tracking In Fossil}
  build.wiki {Compiling and Installing Fossil}
  changes.wiki {Fossil Changelog}
  checkin_names.wiki {Check-in And Version Names}
  checkin.wiki {Check-in Checklist}

Changes to www/permutedindex.html.

21
22
23
24
25
26
27

28
29
30
31
32
33

34
35
36
37
38
39
40
<h2>Permuted Index:</h2>
<ul>
<li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Up and Running in</a></li>
<li><a href="fossil-from-msvc.wiki">2010 IDE &mdash; Integrating Fossil in the Microsoft Express</a></li>
<li><a href="tech_overview.wiki"><b>A Technical Overview Of The Design And Implementation Of Fossil</b></a></li>
<li><a href="adding_code.wiki"><b>Adding New Features To Fossil</b></a></li>
<li><a href="copyright-release.html">Agreement &mdash; Contributor License</a></li>

<li><a href="delta_encoder_algorithm.wiki">Algorithm &mdash; Fossil Delta Encoding</a></li>
<li><a href="blame.wiki">Algorithm Of Fossil &mdash; The Annotate/Blame</a></li>
<li><a href="blame.wiki">Annotate/Blame Algorithm Of Fossil &mdash; The</a></li>
<li><a href="customskin.md">Appearance of Web Pages &mdash; Theming: Customizing The</a></li>
<li><a href="faq.wiki">Asked Questions &mdash; Frequently</a></li>
<li><a href="password.wiki">Authentication &mdash; Password Management And</a></li>

<li><a href="whyusefossil.wiki"><b>Benefits Of Version Control</b></a></li>
<li><a href="hashpolicy.wiki">Between SHA1 and SHA3-256 &mdash; Hash Policy: Choosing</a></li>
<li><a href="antibot.wiki">Bots &mdash; Defense against Spiders and</a></li>
<li><a href="private.wiki">Branches &mdash; Creating, Syncing, and Deleting Private</a></li>
<li><a href="branching.wiki"><b>Branching, Forking, Merging, and Tagging</b></a></li>
<li><a href="bugtheory.wiki"><b>Bug Tracking In Fossil</b></a></li>
<li><a href="makefile.wiki">Build Process &mdash; The Fossil</a></li>







>






>







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<h2>Permuted Index:</h2>
<ul>
<li><a href="fiveminutes.wiki">5 Minutes as a Single User &mdash; Up and Running in</a></li>
<li><a href="fossil-from-msvc.wiki">2010 IDE &mdash; Integrating Fossil in the Microsoft Express</a></li>
<li><a href="tech_overview.wiki"><b>A Technical Overview Of The Design And Implementation Of Fossil</b></a></li>
<li><a href="adding_code.wiki"><b>Adding New Features To Fossil</b></a></li>
<li><a href="copyright-release.html">Agreement &mdash; Contributor License</a></li>
<li><a href="alerts.md">Alerts And Notification &mdash; Email</a></li>
<li><a href="delta_encoder_algorithm.wiki">Algorithm &mdash; Fossil Delta Encoding</a></li>
<li><a href="blame.wiki">Algorithm Of Fossil &mdash; The Annotate/Blame</a></li>
<li><a href="blame.wiki">Annotate/Blame Algorithm Of Fossil &mdash; The</a></li>
<li><a href="customskin.md">Appearance of Web Pages &mdash; Theming: Customizing The</a></li>
<li><a href="faq.wiki">Asked Questions &mdash; Frequently</a></li>
<li><a href="password.wiki">Authentication &mdash; Password Management And</a></li>
<li><a href="backoffice.md">Backoffice mechanism of Fossil &mdash; The</a></li>
<li><a href="whyusefossil.wiki"><b>Benefits Of Version Control</b></a></li>
<li><a href="hashpolicy.wiki">Between SHA1 and SHA3-256 &mdash; Hash Policy: Choosing</a></li>
<li><a href="antibot.wiki">Bots &mdash; Defense against Spiders and</a></li>
<li><a href="private.wiki">Branches &mdash; Creating, Syncing, and Deleting Private</a></li>
<li><a href="branching.wiki"><b>Branching, Forking, Merging, and Tagging</b></a></li>
<li><a href="bugtheory.wiki"><b>Bug Tracking In Fossil</b></a></li>
<li><a href="makefile.wiki">Build Process &mdash; The Fossil</a></li>
74
75
76
77
78
79
80

81
82
83
84
85
86
87
<li><a href="tech_overview.wiki">Design And Implementation Of Fossil &mdash; A Technical Overview Of The</a></li>
<li><a href="theory1.wiki">Design Of The Fossil DVCS &mdash; Thoughts On The</a></li>
<li><a href="embeddeddoc.wiki">Documentation &mdash; Embedded Project</a></li>
<li><a href="contribute.wiki">Documentation To The Fossil Project &mdash; Contributing Code or</a></li>
<li><a href="aboutdownload.wiki">Download Page Works &mdash; How The</a></li>
<li><a href="theory1.wiki">DVCS &mdash; Thoughts On The Design Of The Fossil</a></li>
<li><a href="quotes.wiki">DVCSes in General &mdash; Quotes: What People Are Saying About Fossil, Git, and</a></li>

<li><a href="embeddeddoc.wiki"><b>Embedded Project Documentation</b></a></li>
<li><a href="delta_encoder_algorithm.wiki">Encoding Algorithm &mdash; Fossil Delta</a></li>
<li><a href="encryptedrepos.wiki">Encrypted Repositories &mdash; How To Use</a></li>
<li><a href="env-opts.md"><b>Environment Variables and Global Options</b></a></li>
<li><a href="event.wiki"><b>Events</b></a></li>
<li><a href="webpage-ex.md">Examples &mdash; Webpage</a></li>
<li><a href="inout.wiki">Export To And From Git &mdash; Import And</a></li>







>







76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<li><a href="tech_overview.wiki">Design And Implementation Of Fossil &mdash; A Technical Overview Of The</a></li>
<li><a href="theory1.wiki">Design Of The Fossil DVCS &mdash; Thoughts On The</a></li>
<li><a href="embeddeddoc.wiki">Documentation &mdash; Embedded Project</a></li>
<li><a href="contribute.wiki">Documentation To The Fossil Project &mdash; Contributing Code or</a></li>
<li><a href="aboutdownload.wiki">Download Page Works &mdash; How The</a></li>
<li><a href="theory1.wiki">DVCS &mdash; Thoughts On The Design Of The Fossil</a></li>
<li><a href="quotes.wiki">DVCSes in General &mdash; Quotes: What People Are Saying About Fossil, Git, and</a></li>
<li><a href="alerts.md"><b>Email Alerts And Notification</b></a></li>
<li><a href="embeddeddoc.wiki"><b>Embedded Project Documentation</b></a></li>
<li><a href="delta_encoder_algorithm.wiki">Encoding Algorithm &mdash; Fossil Delta</a></li>
<li><a href="encryptedrepos.wiki">Encrypted Repositories &mdash; How To Use</a></li>
<li><a href="env-opts.md"><b>Environment Variables and Global Options</b></a></li>
<li><a href="event.wiki"><b>Events</b></a></li>
<li><a href="webpage-ex.md">Examples &mdash; Webpage</a></li>
<li><a href="inout.wiki">Export To And From Git &mdash; Import And</a></li>
138
139
140
141
142
143
144

145
146
147
148
149
150
151

152
153
154
155
156
157
158
<li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
<li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
<li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
<li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li>
<li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
<li><a href="../../../sitemap">Map &mdash; Site</a></li>
<li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li>

<li><a href="branching.wiki">Merging, and Tagging &mdash; Branching, Forking,</a></li>
<li><a href="fossil-from-msvc.wiki">Microsoft Express 2010 IDE &mdash; Integrating Fossil in the</a></li>
<li><a href="fiveminutes.wiki">Minutes as a Single User &mdash; Up and Running in 5</a></li>
<li><a href="globs.md">Name Glob Patterns &mdash; File</a></li>
<li><a href="checkin_names.wiki">Names &mdash; Check-in And Version</a></li>
<li><a href="adding_code.wiki">New Features To Fossil &mdash; Adding</a></li>
<li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>

<li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
<li><a href="pop.wiki">Operation &mdash; Principles Of</a></li>
<li><a href="env-opts.md">Options &mdash; Environment Variables and Global</a></li>
<li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil &mdash; A Technical</a></li>
<li><a href="index.wiki">Page &mdash; Home</a></li>
<li><a href="aboutdownload.wiki">Page Works &mdash; How The Download</a></li>
<li><a href="customskin.md">Pages &mdash; Theming: Customizing The Appearance of Web</a></li>







>







>







141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<li><a href="webui.wiki">Interface &mdash; The Fossil Web</a></li>
<li><a href="th1.md">Language &mdash; The TH1 Scripting</a></li>
<li><a href="copyright-release.html">License Agreement &mdash; Contributor</a></li>
<li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li>
<li><a href="password.wiki">Management And Authentication &mdash; Password</a></li>
<li><a href="../../../sitemap">Map &mdash; Site</a></li>
<li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li>
<li><a href="backoffice.md">mechanism of Fossil &mdash; The Backoffice</a></li>
<li><a href="branching.wiki">Merging, and Tagging &mdash; Branching, Forking,</a></li>
<li><a href="fossil-from-msvc.wiki">Microsoft Express 2010 IDE &mdash; Integrating Fossil in the</a></li>
<li><a href="fiveminutes.wiki">Minutes as a Single User &mdash; Up and Running in 5</a></li>
<li><a href="globs.md">Name Glob Patterns &mdash; File</a></li>
<li><a href="checkin_names.wiki">Names &mdash; Check-in And Version</a></li>
<li><a href="adding_code.wiki">New Features To Fossil &mdash; Adding</a></li>
<li><a href="newrepo.wiki">New Fossil Repository &mdash; How To Create A</a></li>
<li><a href="alerts.md">Notification &mdash; Email Alerts And</a></li>
<li><a href="foss-cklist.wiki">Open-Source Projects &mdash; Checklist For Successful</a></li>
<li><a href="pop.wiki">Operation &mdash; Principles Of</a></li>
<li><a href="env-opts.md">Options &mdash; Environment Variables and Global</a></li>
<li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil &mdash; A Technical</a></li>
<li><a href="index.wiki">Page &mdash; Home</a></li>
<li><a href="aboutdownload.wiki">Page Works &mdash; How The Download</a></li>
<li><a href="customskin.md">Pages &mdash; Theming: Customizing The Appearance of Web</a></li>
205
206
207
208
209
210
211

212
213
214
215
216
217
218
<li><a href="private.wiki">Syncing, and Deleting Private Branches &mdash; Creating,</a></li>
<li><a href="custom_ticket.wiki">System &mdash; Customizing The Ticket</a></li>
<li><a href="tickets.wiki">System &mdash; The Fossil Ticket</a></li>
<li><a href="branching.wiki">Tagging &mdash; Branching, Forking, Merging, and</a></li>
<li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil &mdash; A</a></li>
<li><a href="../test/release-checklist.wiki">Testing Checklist &mdash; Pre-Release</a></li>
<li><a href="th1.md">TH1 Scripting Language &mdash; The</a></li>

<li><a href="blame.wiki"><b>The Annotate/Blame Algorithm Of Fossil</b></a></li>
<li><a href="makefile.wiki"><b>The Fossil Build Process</b></a></li>
<li><a href="sync.wiki"><b>The Fossil Sync Protocol</b></a></li>
<li><a href="tickets.wiki"><b>The Fossil Ticket System</b></a></li>
<li><a href="webui.wiki"><b>The Fossil Web Interface</b></a></li>
<li><a href="th1.md"><b>The TH1 Scripting Language</b></a></li>
<li><a href="customskin.md"><b>Theming: Customizing The Appearance of Web Pages</b></a></li>







>







210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<li><a href="private.wiki">Syncing, and Deleting Private Branches &mdash; Creating,</a></li>
<li><a href="custom_ticket.wiki">System &mdash; Customizing The Ticket</a></li>
<li><a href="tickets.wiki">System &mdash; The Fossil Ticket</a></li>
<li><a href="branching.wiki">Tagging &mdash; Branching, Forking, Merging, and</a></li>
<li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil &mdash; A</a></li>
<li><a href="../test/release-checklist.wiki">Testing Checklist &mdash; Pre-Release</a></li>
<li><a href="th1.md">TH1 Scripting Language &mdash; The</a></li>
<li><a href="backoffice.md"><b>The "Backoffice" mechanism of Fossil</b></a></li>
<li><a href="blame.wiki"><b>The Annotate/Blame Algorithm Of Fossil</b></a></li>
<li><a href="makefile.wiki"><b>The Fossil Build Process</b></a></li>
<li><a href="sync.wiki"><b>The Fossil Sync Protocol</b></a></li>
<li><a href="tickets.wiki"><b>The Fossil Ticket System</b></a></li>
<li><a href="webui.wiki"><b>The Fossil Web Interface</b></a></li>
<li><a href="th1.md"><b>The TH1 Scripting Language</b></a></li>
<li><a href="customskin.md"><b>Theming: Customizing The Appearance of Web Pages</b></a></li>

Changes to www/th1.md.

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
































79
80
81
82
83
84
85
operations are accomplished by converting from string to numeric, performing
the computation, then converting the result back into a string.  (This might
seem inefficient, but it is faster than people imagine, and numeric
computations do not come up very often for the kinds of work that TH1 does,
so it has never been a factor.)

A TH1 script consist of a sequence of commands.
Each command is terminated by the first (unescaped) newline or ";" character.
The text of the command (excluding the newline or semicolon terminator)
is broken into space-separated tokens.  The first token is the command
name and subsequent tokens are the arguments.  In this since, TH1 syntax
is similar to the familiar command-line shell syntax.

A token is any sequence of characters other than whitespace and semicolons.
Or, all text without double-quotes is a single token even if it includes
whitespace and semicolons.  Or, all text without nested {...} pairs is a
single token.

The nested {...} form of tokens is important because it allows TH1 commands
to have an appearance similar to C/C++.  It is important to remember, though,
that a TH1 script is really just a list of text commands, not a context-free
language with a grammar like C/C++.  This can be confusing to long-time
C/C++ programmers because TH1 does look a lot like C/C++.  But the semantics
of TH1 are closer to FORTH or Lisp than they are to C.

Consider the "if" command in TH1.

        if {$current eq "dev"} {
          puts "hello"
        } else {
          puts "world"
        }

The example above is a single command.  The first token, and the name
of the command, is "if".
The second token is '$current eq "dev"' - an expression.  (The outer {...}
are removed from each token by the command parser.)  The third token
is the 'puts "hello"', with its whitespace and newlines.  The fourth token
is "else".  And the fifth and last token is 'puts "world"'.

The "if" command word by evaluating its first argument (the second token)
as an expression, and if that expression is true, evaluating its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is "else" then
the fourth argument is evaluated as a TH1 expression.

So, you see, even though the example above spans five lines, it is really
just a single command.

































Summary of Core TH1 Commands
----------------------------

The original Tcl language after when TH1 is modeled has a very rich
repertoire of commands.  TH1, as it is designed to be minimalist and
embedded has a greatly reduced command set.  The following bullets







|


|











|


|








|
|

|
|

|
|

|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
operations are accomplished by converting from string to numeric, performing
the computation, then converting the result back into a string.  (This might
seem inefficient, but it is faster than people imagine, and numeric
computations do not come up very often for the kinds of work that TH1 does,
so it has never been a factor.)

A TH1 script consist of a sequence of commands.
Each command is terminated by the first *unescaped* newline or ";" character.
The text of the command (excluding the newline or semicolon terminator)
is broken into space-separated tokens.  The first token is the command
name and subsequent tokens are the arguments.  In this sense, TH1 syntax
is similar to the familiar command-line shell syntax.

A token is any sequence of characters other than whitespace and semicolons.
Or, all text without double-quotes is a single token even if it includes
whitespace and semicolons.  Or, all text without nested {...} pairs is a
single token.

The nested {...} form of tokens is important because it allows TH1 commands
to have an appearance similar to C/C++.  It is important to remember, though,
that a TH1 script is really just a list of text commands, not a context-free
language with a grammar like C/C++.  This can be confusing to long-time
C/C++ programmers because TH1 does look a lot like C/C++, but the semantics
of TH1 are closer to FORTH or Lisp than they are to C.

Consider the `if` command in TH1.

        if {$current eq "dev"} {
          puts "hello"
        } else {
          puts "world"
        }

The example above is a single command.  The first token, and the name
of the command, is `if`.
The second token is `$current eq "dev"` - an expression.  (The outer {...}
are removed from each token by the command parser.)  The third token
is the `puts "hello"`, with its whitespace and newlines.  The fourth token
is `else"`  And the fifth and last token is `puts "world"`.

The `if` command evaluates its first argument (the second token)
as an expression, and if that expression is true, evaluates its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is `else`, then
the fourth argument is evaluated as a TH1 expression.

So, you see, even though the example above spans five lines, it is really
just a single command.

All of this also explains the emphasis on *unescaped* characters above:
the curly braces `{ }` are string quoting characters in Tcl/TH1, not
block delimiters as in C. This is how we can have a command that extends
over multiple lines. It is also why the `else` keyword must be cuddled
up with the closing brace for the `if` clause's scriptlet. The following
is invalid Tcl/TH1:

        if {$current eq "dev"} {
          puts "hello"
        }
        else {
          puts "world"
        }

If you try to run this under either Tcl or TH1, the interpreter will
tell you that there is no `else` command, because with the newline on
the third line, you terminated the `if` command.

Occasionally in Tcl/TH1 scripts, you may need to use a backslash at the
end of a line to allow a command to extend over multiple lines without
being considered two separate commands. Here's an example from one of
Fossil's test scripts:

        return [lindex [regexp -line -inline -nocase -- \
            {^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
            $repository "" info trunk]]] end]

Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.


Summary of Core TH1 Commands
----------------------------

The original Tcl language after when TH1 is modeled has a very rich
repertoire of commands.  TH1, as it is designed to be minimalist and
embedded has a greatly reduced command set.  The following bullets
166
167
168
169
170
171
172

173
174
175
176
177
178
179
  *  repository
  *  searchable
  *  setParameter
  *  setting
  *  stime
  *  styleHeader
  *  styleFooter

  *  tclEval
  *  tclExpr
  *  tclInvoke
  *  tclIsSafe
  *  tclMakeSafe
  *  tclReady
  *  trace







>







198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
  *  repository
  *  searchable
  *  setParameter
  *  setting
  *  stime
  *  styleHeader
  *  styleFooter
  *  styleScript
  *  tclEval
  *  tclExpr
  *  tclInvoke
  *  tclIsSafe
  *  tclMakeSafe
  *  tclReady
  *  trace
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551







552
553
554
555
556
557
558
process in system space.

<a name="styleHeader"></a>TH1 styleHeader Command
-------------------------------------------------

  *  styleHeader TITLE

Render the configured style header.

<a name="styleFooter"></a>TH1 styleFooter Command
-------------------------------------------------

  *  styleFooter

Render the configured style footer.








<a name="tclEval"></a>TH1 tclEval Command
-----------------------------------------

**This command requires the Tcl integration feature.**

  *  tclEval arg ?arg ...?







|






|
>
>
>
>
>
>
>







570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
process in system space.

<a name="styleHeader"></a>TH1 styleHeader Command
-------------------------------------------------

  *  styleHeader TITLE

Render the configured style header for the selected skin.

<a name="styleFooter"></a>TH1 styleFooter Command
-------------------------------------------------

  *  styleFooter

Render the configured style footer for the selected skin.

<a name="styleScript"></a>TH1 styleScript Command
-------------------------------------------------

  *  styleScript

Render the configured JavaScript for the selected skin.

<a name="tclEval"></a>TH1 tclEval Command
-----------------------------------------

**This command requires the Tcl integration feature.**

  *  tclEval arg ?arg ...?