Fossil

Changes On Branch forum-v2
Login

Changes On Branch forum-v2

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

Changes In Branch forum-v2 Excluding Merge-Ins

This is equivalent to a diff from 030bf200 to 4b4e133a

2018-08-07
15:12
A new implementation for "Forum" in which each forum post is an artifact. This merge includes lots of enhancements to email notification, backoffice, configuration, and other subsystems, all in support of the new forum artifacts. The forum feature is not complete nor bug-free but at this point it seems good enough to continue development on trunk. ... (check-in: 99fcc43f user: drh tags: trunk)
13:52
Disable the backoffice for SSH clients. ... (Closed-Leaf check-in: 4b4e133a user: drh tags: forum-v2)
13:28
More conversions of fossil_panic() into fossil_fatal(). ... (check-in: 666b0ce6 user: drh tags: forum-v2)
2018-08-06
21:20
Merge the mkbuiltin.c fix from trunk. ... (check-in: 427e2575 user: drh tags: forum-v2)
21:19
Fix the mkbuiltin.c code generator so that it tolerates Windows-style backslash path separators on its arguments. This fixes the windows build that was busted by check-in [774bdc83556442c5]. ... (check-in: 030bf200 user: drh tags: trunk)
2018-08-03
21:06
Merged in ben-json-timeline (adds /json/timeline/event). ... (check-in: 74c908e7 user: stephan tags: trunk)

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.

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*
** 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;







<






<







76
77
78
79
80
81
82

83
84
85
86
87
88

89
90
91
92
93
94
95
/*
** 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;
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
      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







|







260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
      if( g.fAnyTrace ){
        fprintf(stderr, "/***** Begin Backoffice Processing %d *****/\n",
                        getpid());
      }
      backoffice_work();
      break;
    }
    if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",1) ){
      /* 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

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
/*
** 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;

  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( pIn->x['v'] ){
    if( zDev==0 ){
      zDev = db_text(0, "SELECT cap FROM user WHERE login='developer'");
    }
    pIn = capability_add(pIn, zDev);
  }
  if( pIn->x['u'] ){
    if( zReader==0 ){
      zReader = db_text(0, "SELECT cap FROM user WHERE login='reader'");
    }
    pIn = capability_add(pIn, zReader);
  }
  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'");
  }
  pIn = capability_add(pIn, zAnon);
  pIn = capability_add(pIn, zNobody);
}

/*
** 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);
}

/*
** Generate HTML that lists all of the capability letters together with
** a brief summary of what each letter means.
*/
void capabilities_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: Set or remove capability "4" for other users
     @ <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>
}

/*
** 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/cgi.c.

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  fflush(g.httpOut);
  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 ){







|







342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  fflush(g.httpOut);
  CGIDEBUG(("DONE\n"));

  /* After the webpage has been sent, do any useful background
  ** processing.
  */
  g.cgiOutput = 2;
  if( g.db!=0 && iReplyStatus==200 && !g.fSshClient ){
    fclose(g.httpOut);
#ifdef _WIN32
    g.httpOut = fossil_fopen("NUL", "wb");
#else
    g.httpOut = fossil_fopen("/dev/null", "wb");
#endif
    if( g.httpOut==0 ){
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".
*/







|



|
|





>
>
>
>
|
|







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
  }
  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/db.c.

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
/*
** 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 {







<













|


|

<
<
<
<
<
<
<
<
<
<
|







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
/*
** Call this routine when a database error occurs.
*/
static void db_err(const char *zFormat, ...){
  static int rcLooping = 0;
  va_list ap;
  char *z;

  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 && 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){







<
<
<
<
<










>
>
>
>
>







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
  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){
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;







>
>
>
>







999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
                          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);
}

#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);







|





|




|







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
    }
  }
  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
*/







|

|







1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
  }
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
*/
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__)







>
>
>
>
>
>







3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
*/
/*
** 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=on
** If backoffice-nodelay is true, then the backoffice processing
** will never invoke sleep().  If it has nothing useful to do,
** it simply exits.
*/
/*
** 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.

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 {







>
>
>
>







532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
  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;
}
















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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;
}

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++; }

Changes to src/email.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
/*
** 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

|
















>
>
>
>
>
|







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
/*
** 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 "email.h"
#include <assert.h>
#include <time.h>

/*
** Maximum size of the subscriberCode blob, in bytes
70
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
@ --
@ -- 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.
@ --







|
|
>







75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@ --
@ -- 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.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.
@ --
108
109
110
111
112
113
114





115
116
117
118
119
120
121
    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.
*/







>
>
>
>
>







114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    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();
  }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.
*/
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







<
<
<
<
<
<
<
<







298
299
300
301
302
303
304








305
306
307
308
309
310
311
  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
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
    }
  }
  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);
}








|









|





|













|


|







|







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
    }
  }
  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);
}

629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
  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;
}







|







632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
  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;
}
689
690
691
692
693
694
695

696
697
698
699
700
701

702
703
704
705
706
707
708
  }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");







>
|
|
|
|
|
|
>







692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
  }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)));
  if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==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");
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 







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







755
756
757
758
759
760
761














762
763
764
765
766
767
768
    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
** 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';







<
<
<
<
<
<










|

|



>
>
>
>
>
>
|






|

|
|
>
>
|

<
|
<

|

|
|






<
<
<
<
<









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

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

|







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
** 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 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, "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';
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
        "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();







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

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







893
894
895
896
897
898
899



900

901

902








903
904




905







906



907
908
909
910
911
912
913
        "DROP TABLE IF EXISTS email_pending;\n"
        "DROP TABLE IF EXISTS subscription;\n"
      );
      email_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();







    email_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,







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







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
    }
    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.
*/







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








|
>







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
        "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 = 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==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 = 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, "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.
*/
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

#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){







|
>
>












>










|

|





>
>

>
>


|
|







|
<
|
>
|
<
<
<

<

>
|
>









|
>



>



>








>
>
>
>
>
>



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







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

#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 */
  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);
    blob_reset(&p->hdr);
    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 *email_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 CROSS JOIN event CROSS JOIN 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->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 */
    " FROM temp.wantalert, event, forumpost"
    " WHERE event.objid=substr(wantalert.eventId,2)+0"
    "   AND eventId GLOB 'f*'"
    "   AND forumpost.fpid=event.objid"
  );
  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;
    if( pPost==0 ) continue;
    p = fossil_malloc( sizeof(EmailEvent) );
    pLast->pNext = p;
    pLast = p;
    p->type = 'f';
    p->needMod = db_column_int(&q, 5);
    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: <%s.%s>\r\n", zUuid, zFrom);
      zIrt = db_column_text(&q, 4);
      if( zIrt && zIrt[0] ){
        blob_appendf(&p->hdr, "In-Reply-To: <%s.%s>\r\n", zIrt, 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){
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
** 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);
}








>
>
>
>
>




>
>


>
>




|

|
>
>



|
>




|


>
>
>
>












|





|
|



>
>
|
|
|
>
>
>
>
>
>




>
>
>
>
>
>
>
>









|















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
















>













|


>

|
>
>

>



>
>

>
>
>
>
|
|
>


|







|
>









>



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














>
|
>
>
>

>
|
>
>
>
>
>

<







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
** 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();
  email_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 = email_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));
  }
  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 [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 email_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();
  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|mFlags);
  }
}

#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 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 email_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.
*/
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();
  email_schema(0);
  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, 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;"
    );
  }
  pEvents = email_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0);
  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 */
     " fullcap((SELECT cap FROM user WHERE login=suname))"  /* 3 */
     " 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);
    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);
        email_send(pSender,&fhdr,&fbody);
        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);
    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"
        " WHERE eventid IN (SELECT eventid FROM wantalert);"
        "DELETE FROM pending_alert WHERE sentDigest AND sentSep;"
      );
    }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);"
        "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);
}

Changes to src/forum.c.

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
**
** 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();
}







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

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

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

<
|
>
>
>
>

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

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

<
|
<
<
<
<

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

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

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




<
<
|

<
>
|
<
<
<
|
<
<

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

>
|
<
|
<
<
<

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

<
|
<


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
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
**
** This file contains code used to generate the user forum.
*/
#include "config.h"
#include <assert.h>
#include "forum.h"

#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, *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;
  for(p=pThread->pFirst; p; p=p->pNext){
    char *zDate;
    Manifest *pPost;

    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>
    }
    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;
      int isPrivate = content_is_private(p->fpid);
      @ <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;

  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){
    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>
    }
    forum_render(0, pPost->zMimetype, pPost->zWiki, 0);

    if( g.perm.WrForum ){
      int sameUser = login_is_individual()
                     && fossil_strcmp(pPost->zUser, g.zLogin)==0;
      int isPrivate = content_is_private(fpid);

      @ <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","text/x-fossil-wiki");
  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;
  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","text/x-fossil-wiki");
    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 Editted 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","text/x-fossil-wiki");
    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.


*/
void forum_main_page(void){
  Stmt q;
  int iLimit, iOfst;
















  login_check_credentials();





  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( search_screen(SRCH_FORUM, 0) ){

    style_submenu_element("Recent Threads","%R/forum");












    style_footer();
    return;



  }











  iLimit = 50;





  iOfst = 0;
  @ <h1>Recent Threads</h1>
  @ <div class='fileage'><table width="100%%">
  db_prepare(&q,
     "SELECT julianday('now') - max(fmtime),"
     "       (SELECT uuid FROM blob WHERE rid=fpid),"














     "       (SELECT substr(comment,instr(comment,':')+2)"

     "          FROM event WHERE objid=fpid)"
     "  FROM forumpost"


     " GROUP BY froot ORDER BY 1 LIMIT %d OFFSET %d",
     iLimit, iOfst



























  );


  while( db_step(&q)==SQLITE_ROW ){
    char *zAge = human_readable_age(db_column_double(&q,0));
    const char *zUuid = db_column_text(&q, 1);
    const char *zTitle = db_column_text(&q, 2);











    @ <tr><td>%h(zAge) ago</td>
    @ <td>%z(href("%R/forumpost/%S",zUuid))%h(zTitle)</a>
    @ </tr>


    fossil_free(zAge);
  }

  @ </table></div>

  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/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/login.c.

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.
**







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







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
  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>







>







514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
  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 ){







>
>
>
>
>
>







553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572

  /* 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 ){
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>.







|







654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
      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>.
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
    @   <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







|














|








|
>
>

>
|
<
<
>
>
>
|
|

>
|







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
    @   <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="2">
      @ <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"></td>
    @   <td colspan="2">&larr; Pressing this button grants\
    @   permission to store a cookie
    @ </tr>
    if( !noAnon && login_self_register_available(0) ){
      @ <tr>


      @   <td></td>
      @   <td><input type="submit" name="self" value="Create A New Account">
      @   <td colspan="2"> \
      @   &larr; Don't have a login?  Click this button to create one.
      @ </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
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
/*
** 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>







>
|



>
>









|
|
|
|
|
<


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












|



|
>
>
>


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

|
>
>
>



|
|
<
<
|
>



|
>
>
>







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
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
/*
** 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;
  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");
  zUserID = PDT("u","");
  zPasswd = PDT("p","");
  zConfirm = PDT("cp","");
  zEAddr = PDT("ea","");
  zDName = PDT("dn","");


  /* Try to make any sense from user input. */
  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( db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr) ){
    iErrLine = 3;
    zErr = "This address is already used.";

  }else{
    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, db_get("default-perms","u"), 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);
    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="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 ){
    @   <td><span class='loginError'>&larr; %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>
  if( iErrLine==2 ){
    @   <td><span class='loginError'>&larr; %h(zErr)</span></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="form_label" align="right">Email Address:</td>
  @   <td><input type="text" name="ea" value="%h(zEAddr)" size="30"></td>
  if( iErrLine==3 ){
    @   <td><span class='loginError'>&larr; %h(zErr)</span></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="form_label" align="right">Password:</td>
  @   <td><input type="password" name="p" value="%h(zPasswd)" size="30"></td>
  if( iErrLine==4 ){
    @   <td><span class='loginError'>&larr; %h(zErr)</span></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="form_label" align="right">Confirm password:</td>
  @   <td><input type="password" name="cp" value="%h(zConfirm)" size="30"></td>
  if( iErrLine==5 ){


    @   <td><span class='loginError'>&larr; %h(zErr)</span></td>
  }
  @ </tr>
  @ <tr>
  @   <td class="form_label" align="right">Captcha text (below):</td>
  @   <td><input type="text" name="captcha" value="" size="30"></td>
  if( iErrLine==6 ){
    @   <td><span class='loginError'>&larr; %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>
  @ %h(zCaptcha)
  @ </pre></td></tr></table></div>

Changes to src/main.c.

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  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
/*







|







88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  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 */
};

#ifdef FOSSIL_ENABLE_TCL
/*
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(







|







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_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;







|







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_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);
      }







|







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_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;







|







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_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]);
    }
  }
}

/*







|







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_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","/");







|












|







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_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;







|




|







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_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;
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*
**







|







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_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;







|







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_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");







>
>

















|







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
2836
2837
** 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();
}







>
>
>
>
>
>
>
>
>




2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
    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.

23
24
25
26
27
28
29

30
31
32
33
34
35
36
  $(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 \







>







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  $(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 \
203
204
205
206
207
208
209

210
211
212
213
214
215
216
  $(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 \







>







204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  $(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 \
229
230
231
232
233
234
235

236
237
238
239
240
241
242
  $(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 \







>







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
  $(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 \
364
365
366
367
368
369
370

371
372
373
374
375
376
377
 $(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 \







>







367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
 $(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 \
697
698
699
700
701
702
703

704
705
706
707
708
709
710
	$(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 \







>







701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
	$(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 \
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








>
>
>
>
>
>
>
>







925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
$(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

Changes to src/makemake.tcl.

35
36
37
38
39
40
41

42
43
44
45
46
47
48
  bisect
  blob
  branch
  browse
  builtin
  bundle
  cache

  captcha
  cgi
  checkin
  checkout
  clearsign
  clone
  comformat







>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  bisect
  blob
  branch
  browse
  builtin
  bundle
  cache
  capabilities
  captcha
  cgi
  checkin
  checkout
  clearsign
  clone
  comformat

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
  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",   "CDUZ"        },
  /* 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;







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







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
    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;







<








|
>
>


>
>
>







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
** 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







>
>







485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
  /* 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>
      **







>















>







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
        }
        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







>







587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
        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







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







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
        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







>

















>

















>







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
        }
        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.







>







773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
                                 , 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.







>














>







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
        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]) );







<


|
<
<
<







>







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
        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);







<









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








|







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
      ** 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 ){

    p->type = p->zComment!=0 ? CFTYPE_MANIFEST : 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
}

/*
** 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);







|
>







1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
}

/*
** 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;
  sqlite3_open(":memory:", &g.db);
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
    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.







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




















|







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
    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);
    }







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







2510
2511
2512
2513
2514
2515
2516
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
    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/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.

1083
1084
1085
1086
1087
1088
1089



1090
1091

1092
1093
1094
1095
1096
1097
1098
  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;







>
>
>


>







1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
  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/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);
;

/* 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
106
  @ <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 capunion(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, "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>







|
>










|
>







|

|
|
|
>
|
|







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
  /* 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") 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
  */
  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]*'"







|










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







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
  */
  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(login,' AND ') FROM user"
                 " WHERE cap GLOB '*[as]*'"
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







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







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
    }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( email_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.

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
  }
  @ </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.
*/







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







294
295
296
297
298
299
300

















































































301
302
303
304
305
306
307
  }
  @ </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.
*/
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
  @ 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:
**







|













|







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
  @ 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();
  @ </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");
  capabilities_table();
  style_footer();
}

/*
** Return true if zPw is a valid password string.  A valid
** password string is:
**
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







>
>







1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
  @ <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
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();







>
>







2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
  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();

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>
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);
  }







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







15848
15849
15850
15851
15852
15853
15854
15855
15856
15857
15858
15859
15860
15861
15862
15863
15864
15865
15866
15867
15868
15869
15870
15871
15872
15873
15874
15875
15876
15877
15878
  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/skins.c.

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));
}

/*







|







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_fatal("no such skin detail: %s", zName);
  return pDetail->zValue;
}
int skin_detail_boolean(const char *zName){
  return !is_false(skin_detail(zName));
}

/*

Changes to src/smtp.c.

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");







|












|







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");

Changes to src/sqlite3.c.

51
52
53
54
55
56
57






58
59
60
61
62
63
64
#endif

/* These macros are provided to "stringify" the value of the define
** for those options in which the value is meaningful. */
#define CTIMEOPT_VAL_(opt) #opt
#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)







/*
** An array of names of all compile-time options.  This array should 
** be sorted A-Z.
**
** This array looks large, but in a typical installation actually uses
** only a handful of compile-time options, so most times this array is usually
** rather short and uses little memory space.







>
>
>
>
>
>







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#endif

/* These macros are provided to "stringify" the value of the define
** for those options in which the value is meaningful. */
#define CTIMEOPT_VAL_(opt) #opt
#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)

/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This
** option requires a separate macro because legal values contain a single
** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */
#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2
#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt)

/*
** An array of names of all compile-time options.  This array should 
** be sorted A-Z.
**
** This array looks large, but in a typical installation actually uses
** only a handful of compile-time options, so most times this array is usually
** rather short and uses little memory space.
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
  "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT),
#endif
#ifdef SQLITE_DEFAULT_LOCKING_MODE
  "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE),
#endif
#ifdef SQLITE_DEFAULT_LOOKASIDE
  "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOOKASIDE),
#endif
#if SQLITE_DEFAULT_MEMSTATUS
  "DEFAULT_MEMSTATUS",
#endif
#ifdef SQLITE_DEFAULT_MMAP_SIZE
  "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
#endif







|







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
  "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT),
#endif
#ifdef SQLITE_DEFAULT_LOCKING_MODE
  "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE),
#endif
#ifdef SQLITE_DEFAULT_LOOKASIDE
  "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE),
#endif
#if SQLITE_DEFAULT_MEMSTATUS
  "DEFAULT_MEMSTATUS",
#endif
#ifdef SQLITE_DEFAULT_MMAP_SIZE
  "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
#endif
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
**
** 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







|







1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
**
** 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-24 22:02:12 2bd593332da0aade467e7a4ee89e966aa6302f37540a2c5e23671f98a6cb599c"

/*
** 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
1910
1911
1912
1913
1914
1915
1916

1917
1918
1919
1920
1921
1922
1923
1924
** 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.







>
|







1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
** 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.
9983
9984
9985
9986
9987
9988
9989
9990
9991
9992
9993
9994
9995
9996
9997
9998
9999
10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011
10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
** 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







<
















<
<
<
<
<







|







9990
9991
9992
9993
9994
9995
9996

9997
9998
9999
10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011
10012





10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
10027
** 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
10051
10052
10053
10054
10055
10056
10057
10058
10059
10060
10061
10062
10063
10064
10065
10066
10067
10068
10069
10070
10071
10072
10073
10074
10075
** 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







|









|







10052
10053
10054
10055
10056
10057
10058
10059
10060
10061
10062
10063
10064
10065
10066
10067
10068
10069
10070
10071
10072
10073
10074
10075
10076
** 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 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
10089
10090
10091
10092
10093
10094
10095
10096
10097
10098
10099
10100
10101
10102
10103
10104
10105
10106
10107
10108
10109
10110
10111
10112
10113
10114
10115
10116
10117
10118
10119
10120
10121
10122
10123
10124
10125
10126
10127
10128
10129
10130
10131
10132
10133
10134
10135
10136
10137



10138
10139
10140
10141
10142
10143
10144
10145
10146
10147
10148
10149
10150

10151
10152

10153
10154
10155
10156
10157
10158
10159
10160
10161
10162



10163
10164
10165
10166
10167
10168
10169
** 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







|









|






|





|


















>
>
>








|

|
|
|
>
|
<
>
|
<

|


|



>
>
>







10090
10091
10092
10093
10094
10095
10096
10097
10098
10099
10100
10101
10102
10103
10104
10105
10106
10107
10108
10109
10110
10111
10112
10113
10114
10115
10116
10117
10118
10119
10120
10121
10122
10123
10124
10125
10126
10127
10128
10129
10130
10131
10132
10133
10134
10135
10136
10137
10138
10139
10140
10141
10142
10143
10144
10145
10146
10147
10148
10149
10150
10151
10152
10153
10154
10155
10156

10157
10158

10159
10160
10161
10162
10163
10164
10165
10166
10167
10168
10169
10170
10171
10172
10173
10174
10175
10176
** 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
18935
18936
18937
18938
18939
18940
18941
18942
18943
18944
18945
18946
18947
18948
18949
#endif
SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, int, int);
SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*);
SQLITE_PRIVATE void sqlite3AlterFunctions(void);
SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *);
SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...);
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*);
SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr *, int, int);
SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*);
SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
SQLITE_PRIVATE int sqlite3MatchSpanName(const char*, const char*, const char*, const char*);
SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);







|







18942
18943
18944
18945
18946
18947
18948
18949
18950
18951
18952
18953
18954
18955
18956
#endif
SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, int, int);
SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*);
SQLITE_PRIVATE void sqlite3AlterFunctions(void);
SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *);
SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...);
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int);
SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr *, int, int);
SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*);
SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
SQLITE_PRIVATE int sqlite3MatchSpanName(const char*, const char*, const char*, const char*);
SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
20023
20024
20025
20026
20027
20028
20029

20030
20031
20032
20033
20034
20035
20036
20037
20038
20039
  int rcApp;              /* errcode set by sqlite3_result_error_code() */
  u32 nWrite;             /* Number of write operations that have occurred */
#endif
  u16 nResColumn;         /* Number of columns in one row of the result set */
  u8 errorAction;         /* Recovery action to do in case of an error */
  u8 minWriteFileFormat;  /* Minimum file format for writable database files */
  u8 prepFlags;           /* SQLITE_PREPARE_* flags */

  bft expired:1;          /* True if the VM needs to be recompiled */
  bft doingRerun:1;       /* True if rerunning after an auto-reprepare */
  bft explain:2;          /* True if EXPLAIN present on SQL command */
  bft changeCntOn:1;      /* True to update the change-counter */
  bft runOnlyOnce:1;      /* Automatically expire on reset */
  bft usesStmtJournal:1;  /* True if uses a statement journal */
  bft readOnly:1;         /* True for statements that do not write */
  bft bIsReader:1;        /* True for statements that read */
  yDbMask btreeMask;      /* Bitmask of db->aDb[] entries referenced */
  yDbMask lockMask;       /* Subset of btreeMask that requires a lock */







>
|

<







20030
20031
20032
20033
20034
20035
20036
20037
20038
20039

20040
20041
20042
20043
20044
20045
20046
  int rcApp;              /* errcode set by sqlite3_result_error_code() */
  u32 nWrite;             /* Number of write operations that have occurred */
#endif
  u16 nResColumn;         /* Number of columns in one row of the result set */
  u8 errorAction;         /* Recovery action to do in case of an error */
  u8 minWriteFileFormat;  /* Minimum file format for writable database files */
  u8 prepFlags;           /* SQLITE_PREPARE_* flags */
  bft expired:2;          /* 1: recompile VM immediately  2: when convenient */
  bft explain:2;          /* True if EXPLAIN present on SQL command */
  bft doingRerun:1;       /* True if rerunning after an auto-reprepare */

  bft changeCntOn:1;      /* True to update the change-counter */
  bft runOnlyOnce:1;      /* Automatically expire on reset */
  bft usesStmtJournal:1;  /* True if uses a statement journal */
  bft readOnly:1;         /* True for statements that do not write */
  bft bIsReader:1;        /* True for statements that read */
  yDbMask btreeMask;      /* Bitmask of db->aDb[] entries referenced */
  yDbMask lockMask;       /* Subset of btreeMask that requires a lock */
28638
28639
28640
28641
28642
28643
28644
28645
28646
28647
28648
28649
28650
28651
28652
                             pExpr->op2, pExpr->u.zToken);
      }else{
        sqlite3TreeViewLine(pView, "FUNCTION %Q", pExpr->u.zToken);
      }
      if( pFarg ){
        sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0);
      }
#ifndef SQLITe_OMIT_WINDOWFUNC
      if( pWin ){
        sqlite3TreeViewWindow(pView, pWin, 0);
      }
#endif
      break;
    }
#ifndef SQLITE_OMIT_SUBQUERY







|







28645
28646
28647
28648
28649
28650
28651
28652
28653
28654
28655
28656
28657
28658
28659
                             pExpr->op2, pExpr->u.zToken);
      }else{
        sqlite3TreeViewLine(pView, "FUNCTION %Q", pExpr->u.zToken);
      }
      if( pFarg ){
        sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0);
      }
#ifndef SQLITE_OMIT_WINDOWFUNC
      if( pWin ){
        sqlite3TreeViewWindow(pView, pWin, 0);
      }
#endif
      break;
    }
#ifndef SQLITE_OMIT_SUBQUERY
42946
42947
42948
42949
42950
42951
42952



42953
42954
42955
42956
42957
42958
42959
42960
42961
42962
42963
42964
42965
42966









42967
42968
42969
42970
42971
42972
42973
42974
42975
42976
42977
42978
42979
42980
42981
42982
42983
42984

42985

42986
42987
42988
42989
42990
42991
42992
/*
** Truncate an open file to a specified size
*/
static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
  winFile *pFile = (winFile*)id;  /* File handle object */
  int rc = SQLITE_OK;             /* Return code for this function */
  DWORD lastErrno;




  assert( pFile );
  SimulateIOError(return SQLITE_IOERR_TRUNCATE);
  OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, size=%lld, lock=%d\n",
           osGetCurrentProcessId(), pFile, pFile->h, nByte, pFile->locktype));

  /* If the user has configured a chunk-size for this file, truncate the
  ** file so that it consists of an integer number of chunks (i.e. the
  ** actual file size after the operation may be larger than the requested
  ** size).
  */
  if( pFile->szChunk>0 ){
    nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
  }










  /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */
  if( winSeekFile(pFile, nByte) ){
    rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno,
                     "winTruncate1", pFile->zPath);
  }else if( 0==osSetEndOfFile(pFile->h) &&
            ((lastErrno = osGetLastError())!=ERROR_USER_MAPPED_FILE) ){
    pFile->lastErrno = lastErrno;
    rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno,
                     "winTruncate2", pFile->zPath);
  }

#if SQLITE_MAX_MMAP_SIZE>0
  /* If the file was truncated to a size smaller than the currently
  ** mapped region, reduce the effective mapping size as well. SQLite will
  ** use read() and write() to access data beyond this point from now on.
  */
  if( pFile->pMapRegion && nByte<pFile->mmapSize ){

    pFile->mmapSize = nByte;

  }
#endif

  OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, rc=%s\n",
           osGetCurrentProcessId(), pFile, pFile->h, sqlite3ErrName(rc)));
  return rc;
}







>
>
>














>
>
>
>
>
>
>
>
>













|
|
<
<
|
>
|
>







42953
42954
42955
42956
42957
42958
42959
42960
42961
42962
42963
42964
42965
42966
42967
42968
42969
42970
42971
42972
42973
42974
42975
42976
42977
42978
42979
42980
42981
42982
42983
42984
42985
42986
42987
42988
42989
42990
42991
42992
42993
42994
42995
42996
42997
42998
42999
43000


43001
43002
43003
43004
43005
43006
43007
43008
43009
43010
43011
/*
** Truncate an open file to a specified size
*/
static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
  winFile *pFile = (winFile*)id;  /* File handle object */
  int rc = SQLITE_OK;             /* Return code for this function */
  DWORD lastErrno;
#if SQLITE_MAX_MMAP_SIZE>0
  sqlite3_int64 oldMmapSize;
#endif

  assert( pFile );
  SimulateIOError(return SQLITE_IOERR_TRUNCATE);
  OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, size=%lld, lock=%d\n",
           osGetCurrentProcessId(), pFile, pFile->h, nByte, pFile->locktype));

  /* If the user has configured a chunk-size for this file, truncate the
  ** file so that it consists of an integer number of chunks (i.e. the
  ** actual file size after the operation may be larger than the requested
  ** size).
  */
  if( pFile->szChunk>0 ){
    nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
  }

#if SQLITE_MAX_MMAP_SIZE>0
  if( pFile->pMapRegion ){
    oldMmapSize = pFile->mmapSize;
  }else{
    oldMmapSize = 0;
  }
  winUnmapfile(pFile);
#endif

  /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */
  if( winSeekFile(pFile, nByte) ){
    rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno,
                     "winTruncate1", pFile->zPath);
  }else if( 0==osSetEndOfFile(pFile->h) &&
            ((lastErrno = osGetLastError())!=ERROR_USER_MAPPED_FILE) ){
    pFile->lastErrno = lastErrno;
    rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno,
                     "winTruncate2", pFile->zPath);
  }

#if SQLITE_MAX_MMAP_SIZE>0
  if( rc==SQLITE_OK && oldMmapSize>0 ){
    if( oldMmapSize>nByte ){


      winMapfile(pFile, -1);
    }else{
      winMapfile(pFile, oldMmapSize);
    }
  }
#endif

  OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, rc=%s\n",
           osGetCurrentProcessId(), pFile, pFile->h, sqlite3ErrName(rc)));
  return rc;
}
56459
56460
56461
56462
56463
56464
56465

56466
56467
56468
56469
56470
56471
56472
56473
56474
56475
56476
56477
56478
56479
56480
56481
56482
56483
56484
          }
        }

        if( (rc&0xFF)==SQLITE_IOERR && rc!=SQLITE_IOERR_NOMEM ){
          rc = sqlite3JournalCreate(pPager->jfd);
          if( rc!=SQLITE_OK ){
            sqlite3OsClose(pPager->jfd);

          }
          bBatch = 0;
        }else{
          sqlite3OsClose(pPager->jfd);
        }
      }
#endif /* SQLITE_ENABLE_BATCH_ATOMIC_WRITE */

      if( bBatch==0 && rc==SQLITE_OK ){
        rc = pager_write_pagelist(pPager, pList);
      }

      if( rc!=SQLITE_OK ){
        assert( rc!=SQLITE_IOERR_BLOCKED );
        goto commit_phase_one_exit;
      }
      sqlite3PcacheCleanAll(pPager->pPCache);

      /* If the file on disk is smaller than the database image, use 







>








|


<







56478
56479
56480
56481
56482
56483
56484
56485
56486
56487
56488
56489
56490
56491
56492
56493
56494
56495
56496

56497
56498
56499
56500
56501
56502
56503
          }
        }

        if( (rc&0xFF)==SQLITE_IOERR && rc!=SQLITE_IOERR_NOMEM ){
          rc = sqlite3JournalCreate(pPager->jfd);
          if( rc!=SQLITE_OK ){
            sqlite3OsClose(pPager->jfd);
            goto commit_phase_one_exit;
          }
          bBatch = 0;
        }else{
          sqlite3OsClose(pPager->jfd);
        }
      }
#endif /* SQLITE_ENABLE_BATCH_ATOMIC_WRITE */

      if( bBatch==0 ){
        rc = pager_write_pagelist(pPager, pList);
      }

      if( rc!=SQLITE_OK ){
        assert( rc!=SQLITE_IOERR_BLOCKED );
        goto commit_phase_one_exit;
      }
      sqlite3PcacheCleanAll(pPager->pPCache);

      /* If the file on disk is smaller than the database image, use 
65749
65750
65751
65752
65753
65754
65755






65756
65757
65758
65759
65760
65761
65762
  ** is already in a read-transaction and a read-transaction
  ** is requested, this is a no-op.
  */
  if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){
    goto trans_begun;
  }
  assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 );







  /* Write transactions are not possible on a read-only database */
  if( (pBt->btsFlags & BTS_READ_ONLY)!=0 && wrflag ){
    rc = SQLITE_READONLY;
    goto trans_begun;
  }








>
>
>
>
>
>







65768
65769
65770
65771
65772
65773
65774
65775
65776
65777
65778
65779
65780
65781
65782
65783
65784
65785
65786
65787
  ** is already in a read-transaction and a read-transaction
  ** is requested, this is a no-op.
  */
  if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){
    goto trans_begun;
  }
  assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 );

  if( (p->db->flags & SQLITE_ResetDatabase) 
   && sqlite3PagerIsreadonly(pBt->pPager)==0 
  ){
    pBt->btsFlags &= ~BTS_READ_ONLY;
  }

  /* Write transactions are not possible on a read-only database */
  if( (pBt->btsFlags & BTS_READ_ONLY)!=0 && wrflag ){
    rc = SQLITE_READONLY;
    goto trans_begun;
  }

71765
71766
71767
71768
71769
71770
71771
71772
71773
71774
71775
71776
71777
71778
71779
71780
** reference to the page, add an error message to pCheck->zErrMsg.
** Return 1 if there are 2 or more references to the page and 0 if
** if this is the first reference to the page.
**
** Also check that the page number is in bounds.
*/
static int checkRef(IntegrityCk *pCheck, Pgno iPage){
  if( iPage==0 ) return 1;
  if( iPage>pCheck->nPage ){
    checkAppendMsg(pCheck, "invalid page number %d", iPage);
    return 1;
  }
  if( getPageReferenced(pCheck, iPage) ){
    checkAppendMsg(pCheck, "2nd reference to page %d", iPage);
    return 1;
  }







<
|







71790
71791
71792
71793
71794
71795
71796

71797
71798
71799
71800
71801
71802
71803
71804
** reference to the page, add an error message to pCheck->zErrMsg.
** Return 1 if there are 2 or more references to the page and 0 if
** if this is the first reference to the page.
**
** Also check that the page number is in bounds.
*/
static int checkRef(IntegrityCk *pCheck, Pgno iPage){

  if( iPage>pCheck->nPage || iPage==0 ){
    checkAppendMsg(pCheck, "invalid page number %d", iPage);
    return 1;
  }
  if( getPageReferenced(pCheck, iPage) ){
    checkAppendMsg(pCheck, "2nd reference to page %d", iPage);
    return 1;
  }
71821
71822
71823
71824
71825
71826
71827
71828
71829
71830
71831
71832
71833
71834
71835
71836
71837
71838

71839
71840
71841
71842
71843
71844
71845
  IntegrityCk *pCheck,  /* Integrity checking context */
  int isFreeList,       /* True for a freelist.  False for overflow page list */
  int iPage,            /* Page number for first page in the list */
  int N                 /* Expected number of pages in the list */
){
  int i;
  int expected = N;
  int iFirst = iPage;
  while( N-- > 0 && pCheck->mxErr ){
    DbPage *pOvflPage;
    unsigned char *pOvflData;
    if( iPage<1 ){
      checkAppendMsg(pCheck,
         "%d of %d pages missing from overflow list starting at %d",
          N+1, expected, iFirst);
      break;
    }
    if( checkRef(pCheck, iPage) ) break;

    if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){
      checkAppendMsg(pCheck, "failed to get page %d", iPage);
      break;
    }
    pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage);
    if( isFreeList ){
      int n = get4byte(&pOvflData[4]);







|
|


<
<
<
<
<
<

>







71845
71846
71847
71848
71849
71850
71851
71852
71853
71854
71855






71856
71857
71858
71859
71860
71861
71862
71863
71864
  IntegrityCk *pCheck,  /* Integrity checking context */
  int isFreeList,       /* True for a freelist.  False for overflow page list */
  int iPage,            /* Page number for first page in the list */
  int N                 /* Expected number of pages in the list */
){
  int i;
  int expected = N;
  int nErrAtStart = pCheck->nErr;
  while( iPage!=0 && pCheck->mxErr ){
    DbPage *pOvflPage;
    unsigned char *pOvflData;






    if( checkRef(pCheck, iPage) ) break;
    N--;
    if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){
      checkAppendMsg(pCheck, "failed to get page %d", iPage);
      break;
    }
    pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage);
    if( isFreeList ){
      int n = get4byte(&pOvflData[4]);
71875
71876
71877
71878
71879
71880
71881
71882
71883
71884
71885



71886
71887
71888
71889
71890
71891
71892
        i = get4byte(pOvflData);
        checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage);
      }
    }
#endif
    iPage = get4byte(pOvflData);
    sqlite3PagerUnref(pOvflPage);

    if( isFreeList && N<(iPage!=0) ){
      checkAppendMsg(pCheck, "free-page count in header is too small");
    }



  }
}
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */

/*
** An implementation of a min-heap.
**







|
|
|
<
>
>
>







71894
71895
71896
71897
71898
71899
71900
71901
71902
71903

71904
71905
71906
71907
71908
71909
71910
71911
71912
71913
        i = get4byte(pOvflData);
        checkPtrmap(pCheck, i, PTRMAP_OVERFLOW2, iPage);
      }
    }
#endif
    iPage = get4byte(pOvflData);
    sqlite3PagerUnref(pOvflPage);
  }
  if( N && nErrAtStart==pCheck->nErr ){
    checkAppendMsg(pCheck,

      "%s is %d but should be %d",
      isFreeList ? "size" : "overflow list length",
      expected-N, expected);
  }
}
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */

/*
** An implementation of a min-heap.
**
72272
72273
72274
72275
72276
72277
72278


















72279
72280
72281
72282
72283
72284
72285
  sCheck.zPfx = "Main freelist: ";
  checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
            get4byte(&pBt->pPage1->aData[36]));
  sCheck.zPfx = 0;

  /* Check all the tables.
  */


















  testcase( pBt->db->flags & SQLITE_CellSizeCk );
  pBt->db->flags &= ~SQLITE_CellSizeCk;
  for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
    i64 notUsed;
    if( aRoot[i]==0 ) continue;
#ifndef SQLITE_OMIT_AUTOVACUUM
    if( pBt->autoVacuum && aRoot[i]>1 ){







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







72293
72294
72295
72296
72297
72298
72299
72300
72301
72302
72303
72304
72305
72306
72307
72308
72309
72310
72311
72312
72313
72314
72315
72316
72317
72318
72319
72320
72321
72322
72323
72324
  sCheck.zPfx = "Main freelist: ";
  checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
            get4byte(&pBt->pPage1->aData[36]));
  sCheck.zPfx = 0;

  /* Check all the tables.
  */
#ifndef SQLITE_OMIT_AUTOVACUUM
  if( pBt->autoVacuum ){
    int mx = 0;
    int mxInHdr;
    for(i=0; (int)i<nRoot; i++) if( mx<aRoot[i] ) mx = aRoot[i];
    mxInHdr = get4byte(&pBt->pPage1->aData[52]);
    if( mx!=mxInHdr ){
      checkAppendMsg(&sCheck,
        "max rootpage (%d) disagrees with header (%d)",
        mx, mxInHdr
      );
    }
  }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){
    checkAppendMsg(&sCheck,
      "incremental_vacuum enabled with a max rootpage of zero"
    );
  }
#endif
  testcase( pBt->db->flags & SQLITE_CellSizeCk );
  pBt->db->flags &= ~SQLITE_CellSizeCk;
  for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
    i64 notUsed;
    if( aRoot[i]==0 ) continue;
#ifndef SQLITE_OMIT_AUTOVACUUM
    if( pBt->autoVacuum && aRoot[i]>1 ){
79972
79973
79974
79975
79976
79977
79978








79979
79980
79981
79982
79983
79984
79985
79986
79987
79988
79989
79990
** as expired.
**
** An expired statement means that recompilation of the statement is
** recommend.  Statements expire when things happen that make their
** programs obsolete.  Removing user-defined functions or collating
** sequences, or changing an authorization function are the types of
** things that make prepared statements obsolete.








*/
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db){
  Vdbe *p;
  for(p = db->pVdbe; p; p=p->pNext){
    p->expired = 1;
  }
}

/*
** Return the database associated with the Vdbe.
*/
SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){







>
>
>
>
>
>
>
>

|


|







80011
80012
80013
80014
80015
80016
80017
80018
80019
80020
80021
80022
80023
80024
80025
80026
80027
80028
80029
80030
80031
80032
80033
80034
80035
80036
80037
** as expired.
**
** An expired statement means that recompilation of the statement is
** recommend.  Statements expire when things happen that make their
** programs obsolete.  Removing user-defined functions or collating
** sequences, or changing an authorization function are the types of
** things that make prepared statements obsolete.
**
** If iCode is 1, then expiration is advisory.  The statement should
** be reprepared before being restarted, but if it is already running
** it is allowed to run to completion.
**
** Internally, this function just sets the Vdbe.expired flag on all
** prepared statements.  The flag is set to 1 for an immediate expiration
** and set to 2 for an advisory expiration.
*/
SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){
  Vdbe *p;
  for(p = db->pVdbe; p; p=p->pNext){
    p->expired = iCode+1;
  }
}

/*
** Return the database associated with the Vdbe.
*/
SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){
85518
85519
85520
85521
85522
85523
85524
85525
85526
85527
85528
85529
85530
85531
85532
        for(ii=0; ii<db->nDb; ii++){
          rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
          if( rc!=SQLITE_OK ){
            goto abort_due_to_error;
          }
        }
        if( isSchemaChange ){
          sqlite3ExpirePreparedStatements(db);
          sqlite3ResetAllSchemasOfConnection(db);
          db->mDbFlags |= DBFLAG_SchemaChange;
        }
      }
  
      /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all 
      ** savepoints nested inside of the savepoint being operated on. */







|







85565
85566
85567
85568
85569
85570
85571
85572
85573
85574
85575
85576
85577
85578
85579
        for(ii=0; ii<db->nDb; ii++){
          rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
          if( rc!=SQLITE_OK ){
            goto abort_due_to_error;
          }
        }
        if( isSchemaChange ){
          sqlite3ExpirePreparedStatements(db, 0);
          sqlite3ResetAllSchemasOfConnection(db);
          db->mDbFlags |= DBFLAG_SchemaChange;
        }
      }
  
      /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all 
      ** savepoints nested inside of the savepoint being operated on. */
85807
85808
85809
85810
85811
85812
85813
85814
85815
85816
85817
85818
85819
85820
85821
  }else if( pOp->p2==BTREE_FILE_FORMAT ){
    /* Record changes in the file format */
    pDb->pSchema->file_format = pOp->p3;
  }
  if( pOp->p1==1 ){
    /* Invalidate all prepared statements whenever the TEMP database
    ** schema is changed.  Ticket #1644 */
    sqlite3ExpirePreparedStatements(db);
    p->expired = 0;
  }
  if( rc ) goto abort_due_to_error;
  break;
}

/* Opcode: OpenRead P1 P2 P3 P4 P5







|







85854
85855
85856
85857
85858
85859
85860
85861
85862
85863
85864
85865
85866
85867
85868
  }else if( pOp->p2==BTREE_FILE_FORMAT ){
    /* Record changes in the file format */
    pDb->pSchema->file_format = pOp->p3;
  }
  if( pOp->p1==1 ){
    /* Invalidate all prepared statements whenever the TEMP database
    ** schema is changed.  Ticket #1644 */
    sqlite3ExpirePreparedStatements(db, 0);
    p->expired = 0;
  }
  if( rc ) goto abort_due_to_error;
  break;
}

/* Opcode: OpenRead P1 P2 P3 P4 P5
85925
85926
85927
85928
85929
85930
85931
85932
85933
85934
85935
85936
85937
85938
85939
case OP_OpenWrite:

  assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ );
  assert( p->bIsReader );
  assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx
          || p->readOnly==0 );

  if( p->expired ){
    rc = SQLITE_ABORT_ROLLBACK;
    goto abort_due_to_error;
  }

  nField = 0;
  pKeyInfo = 0;
  p2 = pOp->p2;







|







85972
85973
85974
85975
85976
85977
85978
85979
85980
85981
85982
85983
85984
85985
85986
case OP_OpenWrite:

  assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ );
  assert( p->bIsReader );
  assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx
          || p->readOnly==0 );

  if( p->expired==1 ){
    rc = SQLITE_ABORT_ROLLBACK;
    goto abort_due_to_error;
  }

  nField = 0;
  pKeyInfo = 0;
  p2 = pOp->p2;
89106
89107
89108
89109
89110
89111
89112
89113
89114
89115
89116
89117
89118
89119
89120
89121






89122
89123

89124
89125
89126
89127
89128
89129
89130
89131
89132
89133
89134
    rc = SQLITE_OK;
    goto jump_to_p2;
  }
  break;
}
#endif

/* Opcode: Expire P1 * * * *
**
** Cause precompiled statements to expire.  When an expired statement
** is executed using sqlite3_step() it will either automatically
** reprepare itself (if it was originally created using sqlite3_prepare_v2())
** or it will fail with SQLITE_SCHEMA.
** 
** If P1 is 0, then all SQL statements become expired. If P1 is non-zero,
** then only the currently executing statement is expired.






*/
case OP_Expire: {

  if( !pOp->p1 ){
    sqlite3ExpirePreparedStatements(db);
  }else{
    p->expired = 1;
  }
  break;
}

#ifndef SQLITE_OMIT_SHARED_CACHE
/* Opcode: TableLock P1 P2 P3 P4 *
** Synopsis: iDb=P1 root=P2 write=P3







|








>
>
>
>
>
>


>

|

|







89153
89154
89155
89156
89157
89158
89159
89160
89161
89162
89163
89164
89165
89166
89167
89168
89169
89170
89171
89172
89173
89174
89175
89176
89177
89178
89179
89180
89181
89182
89183
89184
89185
89186
89187
89188
    rc = SQLITE_OK;
    goto jump_to_p2;
  }
  break;
}
#endif

/* Opcode: Expire P1 P2 * * *
**
** Cause precompiled statements to expire.  When an expired statement
** is executed using sqlite3_step() it will either automatically
** reprepare itself (if it was originally created using sqlite3_prepare_v2())
** or it will fail with SQLITE_SCHEMA.
** 
** If P1 is 0, then all SQL statements become expired. If P1 is non-zero,
** then only the currently executing statement is expired.
**
** If P2 is 0, then SQL statements are expired immediately.  If P2 is 1,
** then running SQL statements are allowed to continue to run to completion.
** The P2==1 case occurs when a CREATE INDEX or similar schema change happens
** that might help the statement run faster but which does not affect the
** correctness of operation.
*/
case OP_Expire: {
  assert( pOp->p2==0 || pOp->p2==1 );
  if( !pOp->p1 ){
    sqlite3ExpirePreparedStatements(db, pOp->p2);
  }else{
    p->expired = pOp->p2+1;
  }
  break;
}

#ifndef SQLITE_OMIT_SHARED_CACHE
/* Opcode: TableLock P1 P2 P3 P4 *
** Synopsis: iDb=P1 root=P2 write=P3
103811
103812
103813
103814
103815
103816
103817
103818
103819
103820
103821
103822
103823
103824
103825
  for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
    Index *pIdx = sqliteHashData(i);
    if( !pIdx->hasStat1 ) sqlite3DefaultRowEst(pIdx);
  }

  /* Load the statistics from the sqlite_stat4 table. */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
  if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){
    db->lookaside.bDisable++;
    rc = loadStat4(db, sInfo.zDatabase);
    db->lookaside.bDisable--;
  }
  for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
    Index *pIdx = sqliteHashData(i);
    sqlite3_free(pIdx->aiRowEst);







|







103865
103866
103867
103868
103869
103870
103871
103872
103873
103874
103875
103876
103877
103878
103879
  for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
    Index *pIdx = sqliteHashData(i);
    if( !pIdx->hasStat1 ) sqlite3DefaultRowEst(pIdx);
  }

  /* Load the statistics from the sqlite_stat4 table. */
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
  if( rc==SQLITE_OK ){
    db->lookaside.bDisable++;
    rc = loadStat4(db, sInfo.zDatabase);
    db->lookaside.bDisable--;
  }
  for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
    Index *pIdx = sqliteHashData(i);
    sqlite3_free(pIdx->aiRowEst);
104543
104544
104545
104546
104547
104548
104549
104550
104551
104552
104553
104554
104555
104556
104557
){
#ifdef SQLITE_ENABLE_API_ARMOR
  if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
  sqlite3_mutex_enter(db->mutex);
  db->xAuth = (sqlite3_xauth)xAuth;
  db->pAuthArg = pArg;
  sqlite3ExpirePreparedStatements(db);
  sqlite3_mutex_leave(db->mutex);
  return SQLITE_OK;
}

/*
** Write an error message into pParse->zErrMsg that explains that the
** user-supplied authorization function returned an illegal value.







|







104597
104598
104599
104600
104601
104602
104603
104604
104605
104606
104607
104608
104609
104610
104611
){
#ifdef SQLITE_ENABLE_API_ARMOR
  if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
  sqlite3_mutex_enter(db->mutex);
  db->xAuth = (sqlite3_xauth)xAuth;
  db->pAuthArg = pArg;
  sqlite3ExpirePreparedStatements(db, 0);
  sqlite3_mutex_leave(db->mutex);
  return SQLITE_OK;
}

/*
** Write an error message into pParse->zErrMsg that explains that the
** user-supplied authorization function returned an illegal value.
108207
108208
108209
108210
108211
108212
108213
108214
108215
108216
108217
108218
108219
108220
108221
    ** to invalidate all pre-compiled statements.
    */
    if( pTblName ){
      sqlite3RefillIndex(pParse, pIndex, iMem);
      sqlite3ChangeCookie(pParse, iDb);
      sqlite3VdbeAddParseSchemaOp(v, iDb,
         sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
      sqlite3VdbeAddOp0(v, OP_Expire);
    }

    sqlite3VdbeJumpHere(v, pIndex->tnum);
  }

  /* When adding an index to the list of indices for a table, make
  ** sure all indices labeled OE_Replace come after all those labeled







|







108261
108262
108263
108264
108265
108266
108267
108268
108269
108270
108271
108272
108273
108274
108275
    ** to invalidate all pre-compiled statements.
    */
    if( pTblName ){
      sqlite3RefillIndex(pParse, pIndex, iMem);
      sqlite3ChangeCookie(pParse, iDb);
      sqlite3VdbeAddParseSchemaOp(v, iDb,
         sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
      sqlite3VdbeAddOp2(v, OP_Expire, 0, 1);
    }

    sqlite3VdbeJumpHere(v, pIndex->tnum);
  }

  /* When adding an index to the list of indices for a table, make
  ** sure all indices labeled OE_Replace come after all those labeled
131892
131893
131894
131895
131896
131897
131898
131899
131900
131901
131902
131903
131904
131905
131906
  VTable *p = db->pDisconnect;
  db->pDisconnect = 0;

  assert( sqlite3BtreeHoldsAllMutexes(db) );
  assert( sqlite3_mutex_held(db->mutex) );

  if( p ){
    sqlite3ExpirePreparedStatements(db);
    do {
      VTable *pNext = p->pNext;
      sqlite3VtabUnlock(p);
      p = pNext;
    }while( p );
  }
}







|







131946
131947
131948
131949
131950
131951
131952
131953
131954
131955
131956
131957
131958
131959
131960
  VTable *p = db->pDisconnect;
  db->pDisconnect = 0;

  assert( sqlite3BtreeHoldsAllMutexes(db) );
  assert( sqlite3_mutex_held(db->mutex) );

  if( p ){
    sqlite3ExpirePreparedStatements(db, 0);
    do {
      VTable *pNext = p->pNext;
      sqlite3VtabUnlock(p);
      p = pNext;
    }while( p );
  }
}
138678
138679
138680
138681
138682
138683
138684
138685


138686
138687
138688
138689
138690
138691
138692
  int nOut = pLoop->nOut;
  LogEst nNew;

#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
  Index *p = pLoop->u.btree.pIndex;
  int nEq = pLoop->u.btree.nEq;

  if( p->nSample>0 && nEq<p->nSampleCol ){


    if( nEq==pBuilder->nRecValid ){
      UnpackedRecord *pRec = pBuilder->pRec;
      tRowcnt a[2];
      int nBtm = pLoop->u.btree.nBtm;
      int nTop = pLoop->u.btree.nTop;

      /* Variable iLower will be set to the estimate of the number of rows in 







|
>
>







138732
138733
138734
138735
138736
138737
138738
138739
138740
138741
138742
138743
138744
138745
138746
138747
138748
  int nOut = pLoop->nOut;
  LogEst nNew;

#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
  Index *p = pLoop->u.btree.pIndex;
  int nEq = pLoop->u.btree.nEq;

  if( p->nSample>0 && nEq<p->nSampleCol
   && OptimizationEnabled(pParse->db, SQLITE_Stat34)
  ){
    if( nEq==pBuilder->nRecValid ){
      UnpackedRecord *pRec = pBuilder->pRec;
      tRowcnt a[2];
      int nBtm = pLoop->u.btree.nBtm;
      int nTop = pLoop->u.btree.nTop;

      /* Variable iLower will be set to the estimate of the number of rows in 
139826
139827
139828
139829
139830
139831
139832

139833
139834
139835
139836
139837
139838
139839
      }else{
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
        tRowcnt nOut = 0;
        if( nInMul==0 
         && pProbe->nSample 
         && pNew->u.btree.nEq<=pProbe->nSampleCol
         && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))

        ){
          Expr *pExpr = pTerm->pExpr;
          if( (eOp & (WO_EQ|WO_ISNULL|WO_IS))!=0 ){
            testcase( eOp & WO_EQ );
            testcase( eOp & WO_IS );
            testcase( eOp & WO_ISNULL );
            rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut);







>







139882
139883
139884
139885
139886
139887
139888
139889
139890
139891
139892
139893
139894
139895
139896
      }else{
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
        tRowcnt nOut = 0;
        if( nInMul==0 
         && pProbe->nSample 
         && pNew->u.btree.nEq<=pProbe->nSampleCol
         && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))
         && OptimizationEnabled(db, SQLITE_Stat34)
        ){
          Expr *pExpr = pTerm->pExpr;
          if( (eOp & (WO_EQ|WO_ISNULL|WO_IS))!=0 ){
            testcase( eOp & WO_EQ );
            testcase( eOp & WO_IS );
            testcase( eOp & WO_ISNULL );
            rc = whereEqualScanEst(pParse, pBuilder, pExpr->pRight, &nOut);
146914
146915
146916
146917
146918
146919
146920
146921
146922
146923
146924
146925
146926
146927
146928
  }while(1);
}

/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
static int yy_find_reduce_action(
  YYACTIONTYPE stateno,     /* Current state number */
  YYCODETYPE iLookAhead     /* The look-ahead token */
){
  int i;
#ifdef YYERRORSYMBOL
  if( stateno>YY_REDUCE_COUNT ){
    return yy_default[stateno];







|







146971
146972
146973
146974
146975
146976
146977
146978
146979
146980
146981
146982
146983
146984
146985
  }while(1);
}

/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
static YYACTIONTYPE yy_find_reduce_action(
  YYACTIONTYPE stateno,     /* Current state number */
  YYCODETYPE iLookAhead     /* The look-ahead token */
){
  int i;
#ifdef YYERRORSYMBOL
  if( stateno>YY_REDUCE_COUNT ){
    return yy_default[stateno];
147419
147420
147421
147422
147423
147424
147425
147426
147427
147428
147429
147430
147431
147432
147433
  yyParser *yypParser,         /* The parser */
  unsigned int yyruleno,       /* Number of the rule by which to reduce */
  int yyLookahead,             /* Lookahead token, or YYNOCODE if none */
  sqlite3ParserTOKENTYPE yyLookaheadToken  /* Value of the lookahead token */
  sqlite3ParserCTX_PDECL                   /* %extra_context */
){
  int yygoto;                     /* The next state */
  int yyact;                      /* The next action */
  yyStackEntry *yymsp;            /* The top of the parser's stack */
  int yysize;                     /* Amount to pop the stack */
  sqlite3ParserARG_FETCH
  (void)yyLookahead;
  (void)yyLookaheadToken;
  yymsp = yypParser->yytos;
#ifndef NDEBUG







|







147476
147477
147478
147479
147480
147481
147482
147483
147484
147485
147486
147487
147488
147489
147490
  yyParser *yypParser,         /* The parser */
  unsigned int yyruleno,       /* Number of the rule by which to reduce */
  int yyLookahead,             /* Lookahead token, or YYNOCODE if none */
  sqlite3ParserTOKENTYPE yyLookaheadToken  /* Value of the lookahead token */
  sqlite3ParserCTX_PDECL                   /* %extra_context */
){
  int yygoto;                     /* The next state */
  YYACTIONTYPE yyact;             /* The next action */
  yyStackEntry *yymsp;            /* The top of the parser's stack */
  int yysize;                     /* Amount to pop the stack */
  sqlite3ParserARG_FETCH
  (void)yyLookahead;
  (void)yyLookaheadToken;
  yymsp = yypParser->yytos;
#ifndef NDEBUG
148978
148979
148980
148981
148982
148983
148984
148985
148986
148987
148988
148989
148990
148991
148992
148993
148994
148995
148996
148997
              yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
    }
  }
#endif

  do{
    assert( yyact==yypParser->yytos->stateno );
    yyact = yy_find_shift_action(yymajor,yyact);
    if( yyact >= YY_MIN_REDUCE ){
      yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,
                        yyminor sqlite3ParserCTX_PARAM);
    }else if( yyact <= YY_MAX_SHIFTREDUCE ){
      yy_shift(yypParser,yyact,yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
      yypParser->yyerrcnt--;
#endif
      break;
    }else if( yyact==YY_ACCEPT_ACTION ){
      yypParser->yytos--;
      yy_accept(yypParser);







|




|







149035
149036
149037
149038
149039
149040
149041
149042
149043
149044
149045
149046
149047
149048
149049
149050
149051
149052
149053
149054
              yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
    }
  }
#endif

  do{
    assert( yyact==yypParser->yytos->stateno );
    yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
    if( yyact >= YY_MIN_REDUCE ){
      yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,
                        yyminor sqlite3ParserCTX_PARAM);
    }else if( yyact <= YY_MAX_SHIFTREDUCE ){
      yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
      yypParser->yyerrcnt--;
#endif
      break;
    }else if( yyact==YY_ACCEPT_ACTION ){
      yypParser->yytos--;
      yy_accept(yypParser);
151419
151420
151421
151422
151423
151424
151425
151426
151427
151428
151429
151430
151431
151432
151433
          u32 oldFlags = db->flags;
          if( onoff>0 ){
            db->flags |= aFlagOp[i].mask;
          }else if( onoff==0 ){
            db->flags &= ~aFlagOp[i].mask;
          }
          if( oldFlags!=db->flags ){
            sqlite3ExpirePreparedStatements(db);
          }
          if( pRes ){
            *pRes = (db->flags & aFlagOp[i].mask)!=0;
          }
          rc = SQLITE_OK;
          break;
        }







|







151476
151477
151478
151479
151480
151481
151482
151483
151484
151485
151486
151487
151488
151489
151490
          u32 oldFlags = db->flags;
          if( onoff>0 ){
            db->flags |= aFlagOp[i].mask;
          }else if( onoff==0 ){
            db->flags &= ~aFlagOp[i].mask;
          }
          if( oldFlags!=db->flags ){
            sqlite3ExpirePreparedStatements(db, 0);
          }
          if( pRes ){
            *pRes = (db->flags & aFlagOp[i].mask)!=0;
          }
          rc = SQLITE_OK;
          break;
        }
151862
151863
151864
151865
151866
151867
151868
151869
151870
151871
151872
151873
151874
151875
151876
      sqlite3BtreeRollback(p, tripCode, !schemaChange);
    }
  }
  sqlite3VtabRollback(db);
  sqlite3EndBenignMalloc();

  if( schemaChange ){
    sqlite3ExpirePreparedStatements(db);
    sqlite3ResetAllSchemasOfConnection(db);
  }
  sqlite3BtreeLeaveAll(db);

  /* Any deferred constraint violations have now been resolved. */
  db->nDeferredCons = 0;
  db->nDeferredImmCons = 0;







|







151919
151920
151921
151922
151923
151924
151925
151926
151927
151928
151929
151930
151931
151932
151933
      sqlite3BtreeRollback(p, tripCode, !schemaChange);
    }
  }
  sqlite3VtabRollback(db);
  sqlite3EndBenignMalloc();

  if( schemaChange ){
    sqlite3ExpirePreparedStatements(db, 0);
    sqlite3ResetAllSchemasOfConnection(db);
  }
  sqlite3BtreeLeaveAll(db);

  /* Any deferred constraint violations have now been resolved. */
  db->nDeferredCons = 0;
  db->nDeferredImmCons = 0;
152317
152318
152319
152320
152321
152322
152323
152324
152325
152326
152327
152328
152329
152330
152331
  if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){
    if( db->nVdbeActive ){
      sqlite3ErrorWithMsg(db, SQLITE_BUSY, 
        "unable to delete/modify user-function due to active statements");
      assert( !db->mallocFailed );
      return SQLITE_BUSY;
    }else{
      sqlite3ExpirePreparedStatements(db);
    }
  }

  p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1);
  assert(p || db->mallocFailed);
  if( !p ){
    return SQLITE_NOMEM_BKPT;







|







152374
152375
152376
152377
152378
152379
152380
152381
152382
152383
152384
152385
152386
152387
152388
  if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){
    if( db->nVdbeActive ){
      sqlite3ErrorWithMsg(db, SQLITE_BUSY, 
        "unable to delete/modify user-function due to active statements");
      assert( !db->mallocFailed );
      return SQLITE_BUSY;
    }else{
      sqlite3ExpirePreparedStatements(db, 0);
    }
  }

  p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1);
  assert(p || db->mallocFailed);
  if( !p ){
    return SQLITE_NOMEM_BKPT;
153092
153093
153094
153095
153096
153097
153098
153099
153100
153101
153102
153103
153104
153105
153106
  pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0);
  if( pColl && pColl->xCmp ){
    if( db->nVdbeActive ){
      sqlite3ErrorWithMsg(db, SQLITE_BUSY, 
        "unable to delete/modify collation sequence due to active statements");
      return SQLITE_BUSY;
    }
    sqlite3ExpirePreparedStatements(db);

    /* If collation sequence pColl was created directly by a call to
    ** sqlite3_create_collation, and not generated by synthCollSeq(),
    ** then any copies made by synthCollSeq() need to be invalidated.
    ** Also, collation destructor - CollSeq.xDel() - function may need
    ** to be called.
    */ 







|







153149
153150
153151
153152
153153
153154
153155
153156
153157
153158
153159
153160
153161
153162
153163
  pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0);
  if( pColl && pColl->xCmp ){
    if( db->nVdbeActive ){
      sqlite3ErrorWithMsg(db, SQLITE_BUSY, 
        "unable to delete/modify collation sequence due to active statements");
      return SQLITE_BUSY;
    }
    sqlite3ExpirePreparedStatements(db, 0);

    /* If collation sequence pColl was created directly by a call to
    ** sqlite3_create_collation, and not generated by synthCollSeq(),
    ** then any copies made by synthCollSeq() need to be invalidated.
    ** Also, collation destructor - CollSeq.xDel() - function may need
    ** to be called.
    */ 
194029
194030
194031
194032
194033
194034
194035

194036
194037
194038
194039
194040
194041

194042
194043
194044
194045
194046
194047
194048
#endif
  for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, 
                                 (void*)&aFunc[i].flag,
                                 aFunc[i].xFunc, 0, 0);
  }

  for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_window_function(db, aAgg[i].zName, aAgg[i].nArg,
                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0,
                                 aAgg[i].xStep, aAgg[i].xFinal,
                                 aAgg[i].xValue, jsonGroupInverse, 0);
  }

#ifndef SQLITE_OMIT_VIRTUALTABLE
  for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0);
  }
#endif
  return rc;
}







>






>







194086
194087
194088
194089
194090
194091
194092
194093
194094
194095
194096
194097
194098
194099
194100
194101
194102
194103
194104
194105
194106
194107
#endif
  for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, 
                                 (void*)&aFunc[i].flag,
                                 aFunc[i].xFunc, 0, 0);
  }
#ifndef SQLITE_OMIT_WINDOWFUNC
  for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_window_function(db, aAgg[i].zName, aAgg[i].nArg,
                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0,
                                 aAgg[i].xStep, aAgg[i].xFinal,
                                 aAgg[i].xValue, jsonGroupInverse, 0);
  }
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
  for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){
    rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0);
  }
#endif
  return rc;
}
196235
196236
196237
196238
196239
196240
196241
196242
196243
196244
196245
196246
196247
196248
196249
  }while(1);
}

/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
static int fts5yy_find_reduce_action(
  fts5YYACTIONTYPE stateno,     /* Current state number */
  fts5YYCODETYPE iLookAhead     /* The look-ahead token */
){
  int i;
#ifdef fts5YYERRORSYMBOL
  if( stateno>fts5YY_REDUCE_COUNT ){
    return fts5yy_default[stateno];







|







196294
196295
196296
196297
196298
196299
196300
196301
196302
196303
196304
196305
196306
196307
196308
  }while(1);
}

/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
static fts5YYACTIONTYPE fts5yy_find_reduce_action(
  fts5YYACTIONTYPE stateno,     /* Current state number */
  fts5YYCODETYPE iLookAhead     /* The look-ahead token */
){
  int i;
#ifdef fts5YYERRORSYMBOL
  if( stateno>fts5YY_REDUCE_COUNT ){
    return fts5yy_default[stateno];
196403
196404
196405
196406
196407
196408
196409
196410
196411
196412
196413
196414
196415
196416
196417
  fts5yyParser *fts5yypParser,         /* The parser */
  unsigned int fts5yyruleno,       /* Number of the rule by which to reduce */
  int fts5yyLookahead,             /* Lookahead token, or fts5YYNOCODE if none */
  sqlite3Fts5ParserFTS5TOKENTYPE fts5yyLookaheadToken  /* Value of the lookahead token */
  sqlite3Fts5ParserCTX_PDECL                   /* %extra_context */
){
  int fts5yygoto;                     /* The next state */
  int fts5yyact;                      /* The next action */
  fts5yyStackEntry *fts5yymsp;            /* The top of the parser's stack */
  int fts5yysize;                     /* Amount to pop the stack */
  sqlite3Fts5ParserARG_FETCH
  (void)fts5yyLookahead;
  (void)fts5yyLookaheadToken;
  fts5yymsp = fts5yypParser->fts5yytos;
#ifndef NDEBUG







|







196462
196463
196464
196465
196466
196467
196468
196469
196470
196471
196472
196473
196474
196475
196476
  fts5yyParser *fts5yypParser,         /* The parser */
  unsigned int fts5yyruleno,       /* Number of the rule by which to reduce */
  int fts5yyLookahead,             /* Lookahead token, or fts5YYNOCODE if none */
  sqlite3Fts5ParserFTS5TOKENTYPE fts5yyLookaheadToken  /* Value of the lookahead token */
  sqlite3Fts5ParserCTX_PDECL                   /* %extra_context */
){
  int fts5yygoto;                     /* The next state */
  fts5YYACTIONTYPE fts5yyact;             /* The next action */
  fts5yyStackEntry *fts5yymsp;            /* The top of the parser's stack */
  int fts5yysize;                     /* Amount to pop the stack */
  sqlite3Fts5ParserARG_FETCH
  (void)fts5yyLookahead;
  (void)fts5yyLookaheadToken;
  fts5yymsp = fts5yypParser->fts5yytos;
#ifndef NDEBUG
196759
196760
196761
196762
196763
196764
196765
196766
196767
196768
196769
196770
196771
196772
196773
196774
196775
196776
196777
196778
              fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact-fts5YY_MIN_REDUCE);
    }
  }
#endif

  do{
    assert( fts5yyact==fts5yypParser->fts5yytos->stateno );
    fts5yyact = fts5yy_find_shift_action(fts5yymajor,fts5yyact);
    if( fts5yyact >= fts5YY_MIN_REDUCE ){
      fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE,fts5yymajor,
                        fts5yyminor sqlite3Fts5ParserCTX_PARAM);
    }else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
      fts5yy_shift(fts5yypParser,fts5yyact,fts5yymajor,fts5yyminor);
#ifndef fts5YYNOERRORRECOVERY
      fts5yypParser->fts5yyerrcnt--;
#endif
      break;
    }else if( fts5yyact==fts5YY_ACCEPT_ACTION ){
      fts5yypParser->fts5yytos--;
      fts5yy_accept(fts5yypParser);







|




|







196818
196819
196820
196821
196822
196823
196824
196825
196826
196827
196828
196829
196830
196831
196832
196833
196834
196835
196836
196837
              fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact-fts5YY_MIN_REDUCE);
    }
  }
#endif

  do{
    assert( fts5yyact==fts5yypParser->fts5yytos->stateno );
    fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact);
    if( fts5yyact >= fts5YY_MIN_REDUCE ){
      fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE,fts5yymajor,
                        fts5yyminor sqlite3Fts5ParserCTX_PARAM);
    }else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
      fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor);
#ifndef fts5YYNOERRORRECOVERY
      fts5yypParser->fts5yyerrcnt--;
#endif
      break;
    }else if( fts5yyact==fts5YY_ACCEPT_ACTION ){
      fts5yypParser->fts5yytos--;
      fts5yy_accept(fts5yypParser);
211511
211512
211513
211514
211515
211516
211517
211518
211519
211520
211521
211522
211523
211524
211525
static void fts5SourceIdFunc(
  sqlite3_context *pCtx,          /* Function call context */
  int nArg,                       /* Number of args */
  sqlite3_value **apUnused        /* Function arguments */
){
  assert( nArg==0 );
  UNUSED_PARAM2(nArg, apUnused);
  sqlite3_result_text(pCtx, "fts5: 2018-07-18 19:09:07 a5087c5c87ad65f92e3bc96bbc84afb43faf10ab6b9ed3ba16304b5c60ad069f", -1, SQLITE_TRANSIENT);
}

static int fts5Init(sqlite3 *db){
  static const sqlite3_module fts5Mod = {
    /* iVersion      */ 2,
    /* xCreate       */ fts5CreateMethod,
    /* xConnect      */ fts5ConnectMethod,







|







211570
211571
211572
211573
211574
211575
211576
211577
211578
211579
211580
211581
211582
211583
211584
static void fts5SourceIdFunc(
  sqlite3_context *pCtx,          /* Function call context */
  int nArg,                       /* Number of args */
  sqlite3_value **apUnused        /* Function arguments */
){
  assert( nArg==0 );
  UNUSED_PARAM2(nArg, apUnused);
  sqlite3_result_text(pCtx, "fts5: 2018-07-24 22:02:12 2bd593332da0aade467e7a4ee89e966aa6302f37540a2c5e23671f98a6cb599c", -1, SQLITE_TRANSIENT);
}

static int fts5Init(sqlite3 *db){
  static const sqlite3_module fts5Mod = {
    /* iVersion      */ 2,
    /* xCreate       */ fts5CreateMethod,
    /* xConnect      */ fts5ConnectMethod,
214797
214798
214799
214800
214801
214802
214803
214804
214805
214806
214807
214808
214809
214810
214811
static void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){
  int i = 0;
  int iTbl = 0;
  while( i<128 ){
    int bToken = aArray[ aFts5UnicodeData[iTbl] & 0x1F ];
    int n = (aFts5UnicodeData[iTbl] >> 5) + i;
    for(; i<128 && i<n; i++){
      aAscii[i] = bToken;
    }
    iTbl++;
  }
}


/*







|







214856
214857
214858
214859
214860
214861
214862
214863
214864
214865
214866
214867
214868
214869
214870
static void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){
  int i = 0;
  int iTbl = 0;
  while( i<128 ){
    int bToken = aArray[ aFts5UnicodeData[iTbl] & 0x1F ];
    int n = (aFts5UnicodeData[iTbl] >> 5) + i;
    for(; i<128 && i<n; i++){
      aAscii[i] = (u8)bToken;
    }
    iTbl++;
  }
}


/*
216221
216222
216223
216224
216225
216226
216227
216228
216229
216230
216231
216232
216233
216234
#endif
  return rc;
}
#endif /* SQLITE_CORE */
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */

/************** End of stmt.c ************************************************/
#if __LINE__!=216228
#undef SQLITE_SOURCE_ID
#define SQLITE_SOURCE_ID      "2018-07-18 19:09:07 a5087c5c87ad65f92e3bc96bbc84afb43faf10ab6b9ed3ba16304b5c60adalt2"
#endif
/* Return the source-id for this library */
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
/************************** End of sqlite3.c ******************************/







|

|




216280
216281
216282
216283
216284
216285
216286
216287
216288
216289
216290
216291
216292
216293
#endif
  return rc;
}
#endif /* SQLITE_CORE */
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */

/************** End of stmt.c ************************************************/
#if __LINE__!=216287
#undef SQLITE_SOURCE_ID
#define SQLITE_SOURCE_ID      "2018-07-24 22:02:12 2bd593332da0aade467e7a4ee89e966aa6302f37540a2c5e23671f98a6cbalt2"
#endif
/* Return the source-id for this library */
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
/************************** End of sqlite3.c ******************************/

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-07-24 22:02:12 2bd593332da0aade467e7a4ee89e966aa6302f37540a2c5e23671f98a6cb599c"

/*
** 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
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.







>
|







883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
** 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.
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
** 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







<
















<
<
<
<
<







|







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
** 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
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
** 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







|









|







9019
9020
9021
9022
9023
9024
9025
9026
9027
9028
9029
9030
9031
9032
9033
9034
9035
9036
9037
9038
9039
9040
9041
9042
9043
** 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 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
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
** 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







|









|






|





|


















>
>
>








|

|
|
|
>
|
<
>
|
<

|


|



>
>
>







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
** 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/style.c.

431
432
433
434
435
436
437





438
439
440
441
442
443
444
  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







>
>
>
>
>







431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
  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;







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





>
















>
>
>







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

/*
** 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 man 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>
    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");
  }
  for(i=0; i<nJsToLoad; i++){
    style_load_one_js_file(azJsToLoad[i]);
  }
}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;
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>
}











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








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


>
>
|



















|
<
|


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


>
>
>
>
>
|
>
>
>
>
>



<
|

|
>
|
|

>
>
>
>
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
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
  }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/timeline.c.

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{







|







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' || 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";







|







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[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);
  }
}







>
>
>
>







1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
    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...







|







1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
**    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");







|







1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
  /* 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;







>


















>
>
>
>















>
>







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
      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/webmail.c.

706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
  @ <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>







|







706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
  @ <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>
730
731
732
733
734
735
736

737
738
739
740
741
742
743
  @ </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);







>







730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
  @ </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="%s(url_render(&url,"id",zId,0,0))">%h(zSubject)</a> \
    @ %s(zDate)</td>
    if( showAll ){
      const char *zTo = db_column_text(&q,5);

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/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;
      }

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 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 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)\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)\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 capabilities 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
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 $** > $@








>
>
>
>
>
>







198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
	+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 $** > $@

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







|

932
933
934
935
936
937
938
939
940
$(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 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 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

Changes to win/Makefile.mingw.

446
447
448
449
450
451
452

453
454
455
456
457
458
459
  $(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 \







>







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
  $(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 \
626
627
628
629
630
631
632

633
634
635
636
637
638
639
  $(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 \







>







627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
  $(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 \
652
653
654
655
656
657
658

659
660
661
662
663
664
665
  $(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 \







>







654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
  $(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 \
787
788
789
790
791
792
793

794
795
796
797
798
799
800
 $(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 \







>







790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
 $(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 \
1141
1142
1143
1144
1145
1146
1147

1148
1149
1150
1151
1152
1153
1154
		$(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 \







>







1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
		$(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 \
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








>
>
>
>
>
>
>
>







1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
$(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

Changes to win/Makefile.msc.

388
389
390
391
392
393
394

395
396
397
398
399
400
401
        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 \







>







388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
        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 \
567
568
569
570
571
572
573

574
575
576
577
578
579
580
        $(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 \







>







568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
        $(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 \
592
593
594
595
596
597
598

599
600
601
602
603
604
605
        $(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 \







>







594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
        $(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 \
786
787
788
789
790
791
792

793
794
795
796
797
798
799
	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 >> $@







>







789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
	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 >> $@
1102
1103
1104
1105
1106
1107
1108






1109
1110
1111
1112
1113
1114
1115
	translate$E $** > $@

$(OX)\cache$O : cache_.c cache.h
	$(TCC) /Fo$@ -c cache_.c

cache_.c : $(SRCDIR)\cache.c
	translate$E $** > $@







$(OX)\captcha$O : captcha_.c captcha.h
	$(TCC) /Fo$@ -c captcha_.c

captcha_.c : $(SRCDIR)\captcha.c
	translate$E $** > $@








>
>
>
>
>
>







1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
	translate$E $** > $@

$(OX)\cache$O : cache_.c cache.h
	$(TCC) /Fo$@ -c cache_.c

cache_.c : $(SRCDIR)\cache.c
	translate$E $** > $@

$(OX)\capabilities$O : capabilities_.c capabilities.h
	$(TCC) /Fo$@ -c capabilities_.c

capabilities_.c : $(SRCDIR)\capabilities.c
	translate$E $** > $@

$(OX)\captcha$O : captcha_.c captcha.h
	$(TCC) /Fo$@ -c captcha_.c

captcha_.c : $(SRCDIR)\captcha.c
	translate$E $** > $@

1845
1846
1847
1848
1849
1850
1851

1852
1853
1854
1855
1856
1857
1858
			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 \







>







1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
			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 \

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
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 mosts 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, 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>