Fossil SCM

Merge from trunk.

brickviking 2025-05-04 02:35 UTC bv-infotool merge
Commit fd1a5afe1e98ffbfda574712c44756bd3bf13d3e873a58ac09601c4d4fbb5030
+1 -1
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1
-2.26
1
+2.27
22
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.26
2
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.27
2
+37 -25
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1622,34 +1622,10 @@
16221622
if( n>350 ) n = 350;
16231623
sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r);
16241624
sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
16251625
}
16261626
1627
-
1628
-/*
1629
-** SQL function: shell_module_schema(X)
1630
-**
1631
-** Return a fake schema for the table-valued function or eponymous virtual
1632
-** table X.
1633
-*/
1634
-static void shellModuleSchema(
1635
- sqlite3_context *pCtx,
1636
- int nVal,
1637
- sqlite3_value **apVal
1638
-){
1639
- const char *zName;
1640
- char *zFake;
1641
- UNUSED_PARAMETER(nVal);
1642
- zName = (const char*)sqlite3_value_text(apVal[0]);
1643
- zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
1644
- if( zFake ){
1645
- sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
1646
- -1, sqlite3_free);
1647
- free(zFake);
1648
- }
1649
-}
1650
-
16511627
/*
16521628
** SQL function: shell_add_schema(S,X)
16531629
**
16541630
** Add the schema name X to the CREATE statement in S and return the result.
16551631
** Examples:
@@ -18709,10 +18685,13 @@
1870918685
rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1);
1871018686
}
1871118687
return rc;
1871218688
}
1871318689
18690
+#ifdef _WIN32
18691
+
18692
+#endif
1871418693
int sqlite3_dbdata_init(
1871518694
sqlite3 *db,
1871618695
char **pzErrMsg,
1871718696
const sqlite3_api_routines *pApi
1871818697
){
@@ -25937,10 +25916,43 @@
2593725916
int sleep = sqlite3_value_int(argv[0]);
2593825917
(void)argcUnused;
2593925918
sqlite3_sleep(sleep/1000);
2594025919
sqlite3_result_int(context, sleep);
2594125920
}
25921
+
25922
+/*
25923
+** SQL function: shell_module_schema(X)
25924
+**
25925
+** Return a fake schema for the table-valued function or eponymous virtual
25926
+** table X.
25927
+*/
25928
+static void shellModuleSchema(
25929
+ sqlite3_context *pCtx,
25930
+ int nVal,
25931
+ sqlite3_value **apVal
25932
+){
25933
+ const char *zName;
25934
+ char *zFake;
25935
+ ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
25936
+ FILE *pSavedLog = p->pLog;
25937
+ UNUSED_PARAMETER(nVal);
25938
+ zName = (const char*)sqlite3_value_text(apVal[0]);
25939
+
25940
+ /* Temporarily disable the ".log" when calling shellFakeSchema() because
25941
+ ** shellFakeSchema() might generate failures for some ephemeral virtual
25942
+ ** tables due to missing arguments. Example: fts4aux.
25943
+ ** https://sqlite.org/forum/forumpost/42fe6520b803be51 */
25944
+ p->pLog = 0;
25945
+ zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
25946
+ p->pLog = pSavedLog;
25947
+
25948
+ if( zFake ){
25949
+ sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
25950
+ -1, sqlite3_free);
25951
+ free(zFake);
25952
+ }
25953
+}
2594225954
2594325955
/* Flags for open_db().
2594425956
**
2594525957
** The default behavior of open_db() is to exit(1) if the database fails to
2594625958
** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
@@ -26081,11 +26093,11 @@
2608126093
shellDtostr, 0, 0);
2608226094
sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0,
2608326095
shellDtostr, 0, 0);
2608426096
sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
2608526097
shellAddSchemaName, 0, 0);
26086
- sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
26098
+ sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, p,
2608726099
shellModuleSchema, 0, 0);
2608826100
sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
2608926101
shellPutsFunc, 0, 0);
2609026102
sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
2609126103
shellUSleepFunc, 0, 0);
2609226104
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1622,34 +1622,10 @@
1622 if( n>350 ) n = 350;
1623 sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r);
1624 sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
1625 }
1626
1627
1628 /*
1629 ** SQL function: shell_module_schema(X)
1630 **
1631 ** Return a fake schema for the table-valued function or eponymous virtual
1632 ** table X.
1633 */
1634 static void shellModuleSchema(
1635 sqlite3_context *pCtx,
1636 int nVal,
1637 sqlite3_value **apVal
1638 ){
1639 const char *zName;
1640 char *zFake;
1641 UNUSED_PARAMETER(nVal);
1642 zName = (const char*)sqlite3_value_text(apVal[0]);
1643 zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
1644 if( zFake ){
1645 sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
1646 -1, sqlite3_free);
1647 free(zFake);
1648 }
1649 }
1650
1651 /*
1652 ** SQL function: shell_add_schema(S,X)
1653 **
1654 ** Add the schema name X to the CREATE statement in S and return the result.
1655 ** Examples:
@@ -18709,10 +18685,13 @@
18709 rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1);
18710 }
18711 return rc;
18712 }
18713
 
 
 
18714 int sqlite3_dbdata_init(
18715 sqlite3 *db,
18716 char **pzErrMsg,
18717 const sqlite3_api_routines *pApi
18718 ){
@@ -25937,10 +25916,43 @@
25937 int sleep = sqlite3_value_int(argv[0]);
25938 (void)argcUnused;
25939 sqlite3_sleep(sleep/1000);
25940 sqlite3_result_int(context, sleep);
25941 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25942
25943 /* Flags for open_db().
25944 **
25945 ** The default behavior of open_db() is to exit(1) if the database fails to
25946 ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
@@ -26081,11 +26093,11 @@
26081 shellDtostr, 0, 0);
26082 sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0,
26083 shellDtostr, 0, 0);
26084 sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
26085 shellAddSchemaName, 0, 0);
26086 sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
26087 shellModuleSchema, 0, 0);
26088 sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
26089 shellPutsFunc, 0, 0);
26090 sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
26091 shellUSleepFunc, 0, 0);
26092
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -1622,34 +1622,10 @@
1622 if( n>350 ) n = 350;
1623 sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r);
1624 sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
1625 }
1626
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1627 /*
1628 ** SQL function: shell_add_schema(S,X)
1629 **
1630 ** Add the schema name X to the CREATE statement in S and return the result.
1631 ** Examples:
@@ -18709,10 +18685,13 @@
18685 rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1);
18686 }
18687 return rc;
18688 }
18689
18690 #ifdef _WIN32
18691
18692 #endif
18693 int sqlite3_dbdata_init(
18694 sqlite3 *db,
18695 char **pzErrMsg,
18696 const sqlite3_api_routines *pApi
18697 ){
@@ -25937,10 +25916,43 @@
25916 int sleep = sqlite3_value_int(argv[0]);
25917 (void)argcUnused;
25918 sqlite3_sleep(sleep/1000);
25919 sqlite3_result_int(context, sleep);
25920 }
25921
25922 /*
25923 ** SQL function: shell_module_schema(X)
25924 **
25925 ** Return a fake schema for the table-valued function or eponymous virtual
25926 ** table X.
25927 */
25928 static void shellModuleSchema(
25929 sqlite3_context *pCtx,
25930 int nVal,
25931 sqlite3_value **apVal
25932 ){
25933 const char *zName;
25934 char *zFake;
25935 ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
25936 FILE *pSavedLog = p->pLog;
25937 UNUSED_PARAMETER(nVal);
25938 zName = (const char*)sqlite3_value_text(apVal[0]);
25939
25940 /* Temporarily disable the ".log" when calling shellFakeSchema() because
25941 ** shellFakeSchema() might generate failures for some ephemeral virtual
25942 ** tables due to missing arguments. Example: fts4aux.
25943 ** https://sqlite.org/forum/forumpost/42fe6520b803be51 */
25944 p->pLog = 0;
25945 zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
25946 p->pLog = pSavedLog;
25947
25948 if( zFake ){
25949 sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
25950 -1, sqlite3_free);
25951 free(zFake);
25952 }
25953 }
25954
25955 /* Flags for open_db().
25956 **
25957 ** The default behavior of open_db() is to exit(1) if the database fails to
25958 ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
@@ -26081,11 +26093,11 @@
26093 shellDtostr, 0, 0);
26094 sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0,
26095 shellDtostr, 0, 0);
26096 sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
26097 shellAddSchemaName, 0, 0);
26098 sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, p,
26099 shellModuleSchema, 0, 0);
26100 sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
26101 shellPutsFunc, 0, 0);
26102 sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
26103 shellUSleepFunc, 0, 0);
26104
+222 -132
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
1616
** if you want a wrapper to interface SQLite with your choice of programming
1717
** language. The code for the "sqlite3" command-line shell is also in a
1818
** separate file. This file contains only code for the core SQLite library.
1919
**
2020
** The content in this amalgamation comes from Fossil check-in
21
-** 20acd630b91609725794ce84f9eda01d5f3c with changes in files:
21
+** 20abf1ec107f942e4527901685d61283c9c2 with changes in files:
2222
**
2323
**
2424
*/
2525
#ifndef SQLITE_AMALGAMATION
2626
#define SQLITE_CORE 1
@@ -465,11 +465,11 @@
465465
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
466466
** [sqlite_version()] and [sqlite_source_id()].
467467
*/
468468
#define SQLITE_VERSION "3.50.0"
469469
#define SQLITE_VERSION_NUMBER 3050000
470
-#define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6"
470
+#define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849"
471471
472472
/*
473473
** CAPI3REF: Run-Time Library Version Numbers
474474
** KEYWORDS: sqlite3_version sqlite3_sourceid
475475
**
@@ -11872,13 +11872,14 @@
1187211872
** This may appear to have some counter-intuitive effects if a single row
1187311873
** is written to more than once during a session. For example, if a row
1187411874
** is inserted while a session object is enabled, then later deleted while
1187511875
** the same session object is disabled, no INSERT record will appear in the
1187611876
** changeset, even though the delete took place while the session was disabled.
11877
-** Or, if one field of a row is updated while a session is disabled, and
11878
-** another field of the same row is updated while the session is enabled, the
11879
-** resulting changeset will contain an UPDATE change that updates both fields.
11877
+** Or, if one field of a row is updated while a session is enabled, and
11878
+** then another field of the same row is updated while the session is disabled,
11879
+** the resulting changeset will contain an UPDATE change that updates both
11880
+** fields.
1188011881
*/
1188111882
SQLITE_API int sqlite3session_changeset(
1188211883
sqlite3_session *pSession, /* Session object */
1188311884
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
1188411885
void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11946,12 +11947,13 @@
1194611947
** To clarify, if this function is called and then a changeset constructed
1194711948
** using [sqlite3session_changeset()], then after applying that changeset to
1194811949
** database zFrom the contents of the two compatible tables would be
1194911950
** identical.
1195011951
**
11951
-** It an error if database zFrom does not exist or does not contain the
11952
-** required compatible table.
11952
+** Unless the call to this function is a no-op as described above, it is an
11953
+** error if database zFrom does not exist or does not contain the required
11954
+** compatible table.
1195311955
**
1195411956
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
1195511957
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
1195611958
** may be set to point to a buffer containing an English language error
1195711959
** message. It is the responsibility of the caller to free this buffer using
@@ -19162,10 +19164,11 @@
1916219164
unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
1916319165
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
1916419166
unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
1916519167
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
1916619168
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
19169
+ unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
1916719170
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
1916819171
unsigned bHasExpr:1; /* Index contains an expression, either a literal
1916919172
** expression, or a reference to a VIRTUAL column */
1917019173
#ifdef SQLITE_ENABLE_STAT4
1917119174
int nSample; /* Number of elements in aSample[] */
@@ -32985,10 +32988,19 @@
3298532988
va_end(ap);
3298632989
zBuf[acc.nChar] = 0;
3298732990
return zBuf;
3298832991
}
3298932992
32993
+/* Maximum size of an sqlite3_log() message. */
32994
+#if defined(SQLITE_MAX_LOG_MESSAGE)
32995
+ /* Leave the definition as supplied */
32996
+#elif SQLITE_PRINT_BUF_SIZE*10>10000
32997
+# define SQLITE_MAX_LOG_MESSAGE 10000
32998
+#else
32999
+# define SQLITE_MAX_LOG_MESSAGE (SQLITE_PRINT_BUF_SIZE*10)
33000
+#endif
33001
+
3299033002
/*
3299133003
** This is the routine that actually formats the sqlite3_log() message.
3299233004
** We house it in a separate routine from sqlite3_log() to avoid using
3299333005
** stack space on small-stack systems when logging is disabled.
3299433006
**
@@ -33001,11 +33013,11 @@
3300133013
** Care must be taken that any sqlite3_log() calls that occur while the
3300233014
** memory mutex is held do not use these mechanisms.
3300333015
*/
3300433016
static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
3300533017
StrAccum acc; /* String accumulator */
33006
- char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */
33018
+ char zMsg[SQLITE_MAX_LOG_MESSAGE]; /* Complete log message */
3300733019
3300833020
sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
3300933021
sqlite3_str_vappendf(&acc, zFormat, ap);
3301033022
sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
3301133023
sqlite3StrAccumFinish(&acc));
@@ -95714,11 +95726,11 @@
9571495726
}
9571595727
}else{
9571695728
sqlite3VdbeError(p, "%s", pOp->p4.z);
9571795729
}
9571895730
pcx = (int)(pOp - aOp);
95719
- sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg);
95731
+ sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql);
9572095732
}
9572195733
rc = sqlite3VdbeHalt(p);
9572295734
assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR );
9572395735
if( rc==SQLITE_BUSY ){
9572495736
p->rc = SQLITE_BUSY;
@@ -97040,11 +97052,11 @@
9704097052
pOut->u.i = ~sqlite3VdbeIntValue(pIn1);
9704197053
}
9704297054
break;
9704397055
}
9704497056
97045
-/* Opcode: Once P1 P2 * * *
97057
+/* Opcode: Once P1 P2 P3 * *
9704697058
**
9704797059
** Fall through to the next instruction the first time this opcode is
9704897060
** encountered on each invocation of the byte-code program. Jump to P2
9704997061
** on the second and all subsequent encounters during the same invocation.
9705097062
**
@@ -97056,10 +97068,16 @@
9705697068
**
9705797069
** For subprograms, there is a bitmask in the VdbeFrame that determines
9705897070
** whether or not the jump should be taken. The bitmask is necessary
9705997071
** because the self-altering code trick does not work for recursive
9706097072
** triggers.
97073
+**
97074
+** The P3 operand is not used directly by this opcode. However P3 is
97075
+** used by the code generator as follows: If this opcode is the start
97076
+** of a subroutine and that subroutine uses a Bloom filter, then P3 will
97077
+** be the register that holds that Bloom filter. See tag-202407032019
97078
+** in the source code for implementation details.
9706197079
*/
9706297080
case OP_Once: { /* jump */
9706397081
u32 iAddr; /* Address of this instruction */
9706497082
assert( p->aOp[0].opcode==OP_Init );
9706597083
if( p->pFrame ){
@@ -103549,12 +103567,12 @@
103549103567
sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
103550103568
}
103551103569
p->rc = rc;
103552103570
sqlite3SystemError(db, rc);
103553103571
testcase( sqlite3GlobalConfig.xLog!=0 );
103554
- sqlite3_log(rc, "statement aborts at %d: [%s] %s",
103555
- (int)(pOp - aOp), p->zSql, p->zErrMsg);
103572
+ sqlite3_log(rc, "statement aborts at %d: %s; [%s]",
103573
+ (int)(pOp - aOp), p->zErrMsg, p->zSql);
103556103574
if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
103557103575
if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
103558103576
if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){
103559103577
db->flags |= SQLITE_CorruptRdOnly;
103560103578
}
@@ -114020,15 +114038,16 @@
114020114038
pCopy = sqlite3SelectDup(pParse->db, pSelect, 0);
114021114039
rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest);
114022114040
sqlite3SelectDelete(pParse->db, pCopy);
114023114041
sqlite3DbFree(pParse->db, dest.zAffSdst);
114024114042
if( addrBloom ){
114043
+ /* Remember that location of the Bloom filter in the P3 operand
114044
+ ** of the OP_Once that began this subroutine. tag-202407032019 */
114025114045
sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2;
114026114046
if( dest.iSDParm2==0 ){
114027
- sqlite3VdbeChangeToNoop(v, addrBloom);
114028
- }else{
114029
- sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2;
114047
+ /* If the Bloom filter won't actually be used, keep it small */
114048
+ sqlite3VdbeGetOp(v, addrBloom)->p1 = 10;
114030114049
}
114031114050
}
114032114051
if( rc ){
114033114052
sqlite3KeyInfoUnref(pKeyInfo);
114034114053
return;
@@ -114471,11 +114490,11 @@
114471114490
if( destIfFalse==destIfNull ){
114472114491
/* Combine Step 3 and Step 5 into a single opcode */
114473114492
if( ExprHasProperty(pExpr, EP_Subrtn) ){
114474114493
const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr);
114475114494
assert( pOp->opcode==OP_Once || pParse->nErr );
114476
- if( pOp->opcode==OP_Once && pOp->p3>0 ){
114495
+ if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */
114477114496
assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) );
114478114497
sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse,
114479114498
rLhs, nVector); VdbeCoverage(v);
114480114499
}
114481114500
}
@@ -124089,11 +124108,11 @@
124089124108
*/
124090124109
SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){
124091124110
int i;
124092124111
i16 iCol16;
124093124112
assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN );
124094
- assert( pIdx->nColumn<=SQLITE_MAX_COLUMN );
124113
+ assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 );
124095124114
iCol16 = iCol;
124096124115
for(i=0; i<pIdx->nColumn; i++){
124097124116
if( iCol16==pIdx->aiColumn[i] ){
124098124117
return i;
124099124118
}
@@ -127237,10 +127256,11 @@
127237127256
}else{
127238127257
j = pCExpr->iColumn;
127239127258
assert( j<=0x7fff );
127240127259
if( j<0 ){
127241127260
j = pTab->iPKey;
127261
+ pIndex->bIdxRowid = 1;
127242127262
}else{
127243127263
if( pTab->aCol[j].notNull==0 ){
127244127264
pIndex->uniqNotNull = 0;
127245127265
}
127246127266
if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
@@ -149706,11 +149726,12 @@
149706149726
&& pE2->iColumn==pColumn->iColumn
149707149727
){
149708149728
return; /* Already present. Return without doing anything. */
149709149729
}
149710149730
}
149711
- if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
149731
+ assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
149732
+ if( sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
149712149733
pConst->bHasAffBlob = 1;
149713149734
}
149714149735
149715149736
pConst->nConst++;
149716149737
pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
@@ -149781,11 +149802,12 @@
149781149802
for(i=0; i<pConst->nConst; i++){
149782149803
Expr *pColumn = pConst->apExpr[i*2];
149783149804
if( pColumn==pExpr ) continue;
149784149805
if( pColumn->iTable!=pExpr->iTable ) continue;
149785149806
if( pColumn->iColumn!=pExpr->iColumn ) continue;
149786
- if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
149807
+ assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
149808
+ if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
149787149809
break;
149788149810
}
149789149811
/* A match is found. Add the EP_FixedCol property */
149790149812
pConst->nChng++;
149791149813
ExprClearProperty(pExpr, EP_Leaf);
@@ -152965,10 +152987,16 @@
152965152987
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
152966152988
p->pEList, p, wctrlFlags, p->nSelectRow);
152967152989
if( pWInfo==0 ) goto select_end;
152968152990
if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
152969152991
p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
152992
+ if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){
152993
+ /* TUNING: For a UNION CTE, because UNION is implies DISTINCT,
152994
+ ** reduce the estimated output row count by 8 (LogEst 30).
152995
+ ** Search for tag-20250414a to see other cases */
152996
+ p->nSelectRow -= 30;
152997
+ }
152970152998
}
152971152999
if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
152972153000
sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
152973153001
}
152974153002
if( sSort.pOrderBy ){
@@ -160131,11 +160159,11 @@
160131160159
160132160160
160133160161
/*
160134160162
** pX is an expression of the form: (vector) IN (SELECT ...)
160135160163
** In other words, it is a vector IN operator with a SELECT clause on the
160136
-** LHS. But not all terms in the vector are indexable and the terms might
160164
+** RHS. But not all terms in the vector are indexable and the terms might
160137160165
** not be in the correct order for indexing.
160138160166
**
160139160167
** This routine makes a copy of the input pX expression and then adjusts
160140160168
** the vector on the LHS with corresponding changes to the SELECT so that
160141160169
** the vector contains only index terms and those terms are in the correct
@@ -167926,11 +167954,11 @@
167926167954
}
167927167955
167928167956
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
167929167957
&& pNew->u.btree.nEq<pProbe->nColumn
167930167958
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
167931
- pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
167959
+ (pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
167932167960
){
167933167961
if( pNew->u.btree.nEq>3 ){
167934167962
sqlite3ProgressCheck(pParse);
167935167963
}
167936167964
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
@@ -171384,11 +171412,12 @@
171384171412
wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1);
171385171413
if( db->mallocFailed ) goto whereBeginError;
171386171414
}
171387171415
171388171416
/* TUNING: Assume that a DISTINCT clause on a subquery reduces
171389
- ** the output size by a factor of 8 (LogEst -30).
171417
+ ** the output size by a factor of 8 (LogEst -30). Search for
171418
+ ** tag-20250414a to see other cases.
171390171419
*/
171391171420
if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){
171392171421
WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n",
171393171422
pWInfo->nRowOut, pWInfo->nRowOut-30));
171394171423
pWInfo->nRowOut -= 30;
@@ -206261,20 +206290,20 @@
206261206290
case FTS3_MATCHINFO_LCS:
206262206291
nVal = pInfo->nCol;
206263206292
break;
206264206293
206265206294
case FTS3_MATCHINFO_LHITS:
206266
- nVal = pInfo->nCol * pInfo->nPhrase;
206295
+ nVal = (size_t)pInfo->nCol * pInfo->nPhrase;
206267206296
break;
206268206297
206269206298
case FTS3_MATCHINFO_LHITS_BM:
206270
- nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
206299
+ nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
206271206300
break;
206272206301
206273206302
default:
206274206303
assert( cArg==FTS3_MATCHINFO_HITS );
206275
- nVal = pInfo->nCol * pInfo->nPhrase * 3;
206304
+ nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3;
206276206305
break;
206277206306
}
206278206307
206279206308
return nVal;
206280206309
}
@@ -207956,60 +207985,113 @@
207956207985
** Growing our own isspace() routine this way is twice as fast as
207957207986
** the library isspace() function, resulting in a 7% overall performance
207958207987
** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os).
207959207988
*/
207960207989
static const char jsonIsSpace[] = {
207961
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
207962
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207963
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207964
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207965
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207966
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207967
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207968
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207969
-
207970
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207971
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207972
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207973
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207974
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207975
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207976
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207977
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207990
+#ifdef SQLITE_ASCII
207991
+/*0 1 2 3 4 5 6 7 8 9 a b c d e f */
207992
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 */
207993
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
207994
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
207995
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */
207996
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */
207997
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */
207998
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */
207999
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */
208000
+
208001
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */
208002
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */
208003
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */
208004
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */
208005
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */
208006
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
208007
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */
208008
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */
208009
+#endif
208010
+#ifdef SQLITE_EBCDIC
208011
+/*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208012
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0 */
208013
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208014
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
208015
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */
208016
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */
208017
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */
208018
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */
208019
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */
208020
+
208021
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */
208022
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */
208023
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */
208024
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */
208025
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */
208026
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
208027
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */
208028
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */
208029
+#endif
208030
+
207978208031
};
207979208032
#define jsonIsspace(x) (jsonIsSpace[(unsigned char)x])
207980208033
207981208034
/*
207982208035
** The set of all space characters recognized by jsonIsspace().
207983208036
** Useful as the second argument to strspn().
207984208037
*/
208038
+#ifdef SQLITE_ASCII
207985208039
static const char jsonSpaces[] = "\011\012\015\040";
208040
+#endif
208041
+#ifdef SQLITE_EBCDIC
208042
+static const char jsonSpaces[] = "\005\045\015\100";
208043
+#endif
208044
+
207986208045
207987208046
/*
207988208047
** Characters that are special to JSON. Control characters,
207989208048
** '"' and '\\' and '\''. Actually, '\'' is not special to
207990208049
** canonical JSON, but it is special in JSON-5, so we include
207991208050
** it in the set of special characters.
207992208051
*/
207993208052
static const char jsonIsOk[256] = {
207994
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207995
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207996
- 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
207997
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
207998
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
207999
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
208000
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208001
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208002
-
208003
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208004
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208005
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208006
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208007
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208008
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208009
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208010
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
208053
+#ifdef SQLITE_ASCII
208054
+/*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208055
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
208056
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208057
+ 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 2 */
208058
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 3 */
208059
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */
208060
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 5 */
208061
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
208062
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */
208063
+
208064
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */
208065
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */
208066
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */
208067
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */
208068
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */
208069
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */
208070
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
208071
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */
208072
+#endif
208073
+#ifdef SQLITE_EBCDIC
208074
+/*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208075
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
208076
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208077
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
208078
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 3 */
208079
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */
208080
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5 */
208081
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
208082
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, /* 7 */
208083
+
208084
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */
208085
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */
208086
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */
208087
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */
208088
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */
208089
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */
208090
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
208091
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */
208092
+#endif
208011208093
};
208012208094
208013208095
/* Objects */
208014208096
typedef struct JsonCache JsonCache;
208015208097
typedef struct JsonString JsonString;
@@ -208150,11 +208232,11 @@
208150208232
208151208233
/**************************************************************************
208152208234
** Forward references
208153208235
**************************************************************************/
208154208236
static void jsonReturnStringAsBlob(JsonString*);
208155
-static int jsonFuncArgMightBeBinary(sqlite3_value *pJson);
208237
+static int jsonArgIsJsonb(sqlite3_value *pJson, JsonParse *p);
208156208238
static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*);
208157208239
static void jsonReturnParse(sqlite3_context*,JsonParse*);
208158208240
static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32);
208159208241
static void jsonParseFree(JsonParse*);
208160208242
static u32 jsonbPayloadSize(const JsonParse*, u32, u32*);
@@ -208568,15 +208650,13 @@
208568208650
jsonAppendString(p, z, n);
208569208651
}
208570208652
break;
208571208653
}
208572208654
default: {
208573
- if( jsonFuncArgMightBeBinary(pValue) ){
208574
- JsonParse px;
208575
- memset(&px, 0, sizeof(px));
208576
- px.aBlob = (u8*)sqlite3_value_blob(pValue);
208577
- px.nBlob = sqlite3_value_bytes(pValue);
208655
+ JsonParse px;
208656
+ memset(&px, 0, sizeof(px));
208657
+ if( jsonArgIsJsonb(pValue, &px) ){
208578208658
jsonTranslateBlobToText(&px, 0, p);
208579208659
}else if( p->eErr==0 ){
208580208660
sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
208581208661
p->eErr = JSTRING_ERR;
208582208662
jsonStringReset(p);
@@ -210246,37 +210326,10 @@
210246210326
}
210247210327
}
210248210328
return i;
210249210329
}
210250210330
210251
-
210252
-/* Return true if the input pJson
210253
-**
210254
-** For performance reasons, this routine does not do a detailed check of the
210255
-** input BLOB to ensure that it is well-formed. Hence, false positives are
210256
-** possible. False negatives should never occur, however.
210257
-*/
210258
-static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){
210259
- u32 sz, n;
210260
- const u8 *aBlob;
210261
- int nBlob;
210262
- JsonParse s;
210263
- if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0;
210264
- aBlob = sqlite3_value_blob(pJson);
210265
- nBlob = sqlite3_value_bytes(pJson);
210266
- if( nBlob<1 ) return 0;
210267
- if( NEVER(aBlob==0) || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0;
210268
- memset(&s, 0, sizeof(s));
210269
- s.aBlob = (u8*)aBlob;
210270
- s.nBlob = nBlob;
210271
- n = jsonbPayloadSize(&s, 0, &sz);
210272
- if( n==0 ) return 0;
210273
- if( sz+n!=(u32)nBlob ) return 0;
210274
- if( (aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0 ) return 0;
210275
- return sz+n==(u32)nBlob;
210276
-}
210277
-
210278210331
/*
210279210332
** Given that a JSONB_ARRAY object starts at offset i, return
210280210333
** the number of entries in that array.
210281210334
*/
210282210335
static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){
@@ -211100,14 +211153,11 @@
211100211153
pParse->aBlob = aNull;
211101211154
pParse->nBlob = 1;
211102211155
return 0;
211103211156
}
211104211157
case SQLITE_BLOB: {
211105
- if( jsonFuncArgMightBeBinary(pArg) ){
211106
- pParse->aBlob = (u8*)sqlite3_value_blob(pArg);
211107
- pParse->nBlob = sqlite3_value_bytes(pArg);
211108
- }else{
211158
+ if( !jsonArgIsJsonb(pArg, pParse) ){
211109211159
sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1);
211110211160
return 1;
211111211161
}
211112211162
break;
211113211163
}
@@ -211254,31 +211304,50 @@
211254211304
}
211255211305
211256211306
/*
211257211307
** If pArg is a blob that seems like a JSONB blob, then initialize
211258211308
** p to point to that JSONB and return TRUE. If pArg does not seem like
211259
-** a JSONB blob, then return FALSE;
211309
+** a JSONB blob, then return FALSE.
211260211310
**
211261
-** This routine is only called if it is already known that pArg is a
211262
-** blob. The only open question is whether or not the blob appears
211263
-** to be a JSONB blob.
211311
+** For small BLOBs (having no more than 7 bytes of payload) a full
211312
+** validity check is done. So for small BLOBs this routine only returns
211313
+** true if the value is guaranteed to be a valid JSONB. For larger BLOBs
211314
+** (8 byte or more of payload) only the size of the outermost element is
211315
+** checked to verify that the BLOB is superficially valid JSONB.
211316
+**
211317
+** A full JSONB validation is done on smaller BLOBs because those BLOBs might
211318
+** also be text JSON that has been incorrectly cast into a BLOB.
211319
+** (See tag-20240123-a and https://sqlite.org/forum/forumpost/012136abd5)
211320
+** If the BLOB is 9 bytes are larger, then it is not possible for the
211321
+** superficial size check done here to pass if the input is really text
211322
+** JSON so we do not need to look deeper in that case.
211323
+**
211324
+** Why we only need to do full JSONB validation for smaller BLOBs:
211325
+**
211326
+** The first byte of valid JSON text must be one of: '{', '[', '"', ' ', '\n',
211327
+** '\r', '\t', '-', or a digit '0' through '9'. Of these, only a subset
211328
+** can also be the first byte of JSONB: '{', '[', and digits '3'
211329
+** through '9'. In every one of those cases, the payload size is 7 bytes
211330
+** or less. So if we do full JSONB validation for every BLOB where the
211331
+** payload is less than 7 bytes, we will never get a false positive for
211332
+** JSONB on an input that is really text JSON.
211264211333
*/
211265211334
static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){
211266211335
u32 n, sz = 0;
211336
+ u8 c;
211337
+ if( sqlite3_value_type(pArg)!=SQLITE_BLOB ) return 0;
211267211338
p->aBlob = (u8*)sqlite3_value_blob(pArg);
211268211339
p->nBlob = (u32)sqlite3_value_bytes(pArg);
211269
- if( p->nBlob==0 ){
211270
- p->aBlob = 0;
211271
- return 0;
211272
- }
211273
- if( NEVER(p->aBlob==0) ){
211274
- return 0;
211275
- }
211276
- if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT
211340
+ if( p->nBlob>0
211341
+ && ALWAYS(p->aBlob!=0)
211342
+ && ((c = p->aBlob[0]) & 0x0f)<=JSONB_OBJECT
211277211343
&& (n = jsonbPayloadSize(p, 0, &sz))>0
211278211344
&& sz+n==p->nBlob
211279
- && ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0)
211345
+ && ((c & 0x0f)>JSONB_FALSE || sz==0)
211346
+ && (sz>7
211347
+ || (c!=0x7b && c!=0x5b && !sqlite3Isdigit(c))
211348
+ || jsonbValidityCheck(p, 0, p->nBlob, 1)==0)
211280211349
){
211281211350
return 1;
211282211351
}
211283211352
p->aBlob = 0;
211284211353
p->nBlob = 0;
@@ -212367,25 +212436,21 @@
212367212436
sqlite3_result_int(ctx, 0);
212368212437
#endif
212369212438
return;
212370212439
}
212371212440
case SQLITE_BLOB: {
212372
- if( jsonFuncArgMightBeBinary(argv[0]) ){
212441
+ JsonParse py;
212442
+ memset(&py, 0, sizeof(py));
212443
+ if( jsonArgIsJsonb(argv[0], &py) ){
212373212444
if( flags & 0x04 ){
212374212445
/* Superficial checking only - accomplished by the
212375
- ** jsonFuncArgMightBeBinary() call above. */
212446
+ ** jsonArgIsJsonb() call above. */
212376212447
res = 1;
212377212448
}else if( flags & 0x08 ){
212378212449
/* Strict checking. Check by translating BLOB->TEXT->BLOB. If
212379212450
** no errors occur, call that a "strict check". */
212380
- JsonParse px;
212381
- u32 iErr;
212382
- memset(&px, 0, sizeof(px));
212383
- px.aBlob = (u8*)sqlite3_value_blob(argv[0]);
212384
- px.nBlob = sqlite3_value_bytes(argv[0]);
212385
- iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1);
212386
- res = iErr==0;
212451
+ res = 0==jsonbValidityCheck(&py, 0, py.nBlob, 1);
212387212452
}
212388212453
break;
212389212454
}
212390212455
/* Fall through into interpreting the input as text. See note
212391212456
** above at tag-20240123-a. */
@@ -212439,13 +212504,11 @@
212439212504
212440212505
assert( argc==1 );
212441212506
UNUSED_PARAMETER(argc);
212442212507
memset(&s, 0, sizeof(s));
212443212508
s.db = sqlite3_context_db_handle(ctx);
212444
- if( jsonFuncArgMightBeBinary(argv[0]) ){
212445
- s.aBlob = (u8*)sqlite3_value_blob(argv[0]);
212446
- s.nBlob = sqlite3_value_bytes(argv[0]);
212509
+ if( jsonArgIsJsonb(argv[0], &s) ){
212447212510
iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1);
212448212511
}else{
212449212512
s.zJson = (char*)sqlite3_value_text(argv[0]);
212450212513
if( s.zJson==0 ) return; /* NULL input or OOM */
212451212514
s.nJson = sqlite3_value_bytes(argv[0]);
@@ -213126,13 +213189,12 @@
213126213189
jsonEachCursorReset(p);
213127213190
if( idxNum==0 ) return SQLITE_OK;
213128213191
memset(&p->sParse, 0, sizeof(p->sParse));
213129213192
p->sParse.nJPRef = 1;
213130213193
p->sParse.db = p->db;
213131
- if( jsonFuncArgMightBeBinary(argv[0]) ){
213132
- p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
213133
- p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
213194
+ if( jsonArgIsJsonb(argv[0], &p->sParse) ){
213195
+ /* We have JSONB */
213134213196
}else{
213135213197
p->sParse.zJson = (char*)sqlite3_value_text(argv[0]);
213136213198
p->sParse.nJson = sqlite3_value_bytes(argv[0]);
213137213199
if( p->sParse.zJson==0 ){
213138213200
p->i = p->iEnd = 0;
@@ -229245,10 +229307,12 @@
229245229307
int rc = SQLITE_OK;
229246229308
229247229309
if( pTab->nCol==0 ){
229248229310
u8 *abPK;
229249229311
assert( pTab->azCol==0 || pTab->abPK==0 );
229312
+ sqlite3_free(pTab->azCol);
229313
+ pTab->abPK = 0;
229250229314
rc = sessionTableInfo(pSession, db, zDb,
229251229315
pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol,
229252229316
&pTab->azDflt, &pTab->aiIdx, &abPK,
229253229317
((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
229254229318
);
@@ -230265,21 +230329,47 @@
230265230329
230266230330
/* Check the table schemas match */
230267230331
if( rc==SQLITE_OK ){
230268230332
int bHasPk = 0;
230269230333
int bMismatch = 0;
230270
- int nCol; /* Columns in zFrom.zTbl */
230334
+ int nCol = 0; /* Columns in zFrom.zTbl */
230271230335
int bRowid = 0;
230272
- u8 *abPK;
230336
+ u8 *abPK = 0;
230273230337
const char **azCol = 0;
230274
- rc = sessionTableInfo(0, db, zFrom, zTbl,
230275
- &nCol, 0, 0, &azCol, 0, 0, &abPK,
230276
- pSession->bImplicitPK ? &bRowid : 0
230277
- );
230338
+ char *zDbExists = 0;
230339
+
230340
+ /* Check that database zFrom is attached. */
230341
+ zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom);
230342
+ if( zDbExists==0 ){
230343
+ rc = SQLITE_NOMEM;
230344
+ }else{
230345
+ sqlite3_stmt *pDbExists = 0;
230346
+ rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0);
230347
+ if( rc==SQLITE_ERROR ){
230348
+ rc = SQLITE_OK;
230349
+ nCol = -1;
230350
+ }
230351
+ sqlite3_finalize(pDbExists);
230352
+ sqlite3_free(zDbExists);
230353
+ }
230354
+
230355
+ if( rc==SQLITE_OK && nCol==0 ){
230356
+ rc = sessionTableInfo(0, db, zFrom, zTbl,
230357
+ &nCol, 0, 0, &azCol, 0, 0, &abPK,
230358
+ pSession->bImplicitPK ? &bRowid : 0
230359
+ );
230360
+ }
230278230361
if( rc==SQLITE_OK ){
230279230362
if( pTo->nCol!=nCol ){
230280
- bMismatch = 1;
230363
+ if( nCol<=0 ){
230364
+ rc = SQLITE_SCHEMA;
230365
+ if( pzErrMsg ){
230366
+ *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl);
230367
+ }
230368
+ }else{
230369
+ bMismatch = 1;
230370
+ }
230281230371
}else{
230282230372
int i;
230283230373
for(i=0; i<nCol; i++){
230284230374
if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
230285230375
if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
@@ -257173,11 +257263,11 @@
257173257263
int nArg, /* Number of args */
257174257264
sqlite3_value **apUnused /* Function arguments */
257175257265
){
257176257266
assert( nArg==0 );
257177257267
UNUSED_PARAM2(nArg, apUnused);
257178
- sqlite3_result_text(pCtx, "fts5: 2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6", -1, SQLITE_TRANSIENT);
257268
+ sqlite3_result_text(pCtx, "fts5: 2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849", -1, SQLITE_TRANSIENT);
257179257269
}
257180257270
257181257271
/*
257182257272
** Implementation of fts5_locale(LOCALE, TEXT) function.
257183257273
**
257184257274
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 20acd630b91609725794ce84f9eda01d5f3c with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -465,11 +465,11 @@
465 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
466 ** [sqlite_version()] and [sqlite_source_id()].
467 */
468 #define SQLITE_VERSION "3.50.0"
469 #define SQLITE_VERSION_NUMBER 3050000
470 #define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -11872,13 +11872,14 @@
11872 ** This may appear to have some counter-intuitive effects if a single row
11873 ** is written to more than once during a session. For example, if a row
11874 ** is inserted while a session object is enabled, then later deleted while
11875 ** the same session object is disabled, no INSERT record will appear in the
11876 ** changeset, even though the delete took place while the session was disabled.
11877 ** Or, if one field of a row is updated while a session is disabled, and
11878 ** another field of the same row is updated while the session is enabled, the
11879 ** resulting changeset will contain an UPDATE change that updates both fields.
 
11880 */
11881 SQLITE_API int sqlite3session_changeset(
11882 sqlite3_session *pSession, /* Session object */
11883 int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
11884 void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11946,12 +11947,13 @@
11946 ** To clarify, if this function is called and then a changeset constructed
11947 ** using [sqlite3session_changeset()], then after applying that changeset to
11948 ** database zFrom the contents of the two compatible tables would be
11949 ** identical.
11950 **
11951 ** It an error if database zFrom does not exist or does not contain the
11952 ** required compatible table.
 
11953 **
11954 ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
11955 ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
11956 ** may be set to point to a buffer containing an English language error
11957 ** message. It is the responsibility of the caller to free this buffer using
@@ -19162,10 +19164,11 @@
19162 unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
19163 unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
19164 unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
19165 unsigned bNoQuery:1; /* Do not use this index to optimize queries */
19166 unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
 
19167 unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
19168 unsigned bHasExpr:1; /* Index contains an expression, either a literal
19169 ** expression, or a reference to a VIRTUAL column */
19170 #ifdef SQLITE_ENABLE_STAT4
19171 int nSample; /* Number of elements in aSample[] */
@@ -32985,10 +32988,19 @@
32985 va_end(ap);
32986 zBuf[acc.nChar] = 0;
32987 return zBuf;
32988 }
32989
 
 
 
 
 
 
 
 
 
32990 /*
32991 ** This is the routine that actually formats the sqlite3_log() message.
32992 ** We house it in a separate routine from sqlite3_log() to avoid using
32993 ** stack space on small-stack systems when logging is disabled.
32994 **
@@ -33001,11 +33013,11 @@
33001 ** Care must be taken that any sqlite3_log() calls that occur while the
33002 ** memory mutex is held do not use these mechanisms.
33003 */
33004 static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
33005 StrAccum acc; /* String accumulator */
33006 char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */
33007
33008 sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
33009 sqlite3_str_vappendf(&acc, zFormat, ap);
33010 sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
33011 sqlite3StrAccumFinish(&acc));
@@ -95714,11 +95726,11 @@
95714 }
95715 }else{
95716 sqlite3VdbeError(p, "%s", pOp->p4.z);
95717 }
95718 pcx = (int)(pOp - aOp);
95719 sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg);
95720 }
95721 rc = sqlite3VdbeHalt(p);
95722 assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR );
95723 if( rc==SQLITE_BUSY ){
95724 p->rc = SQLITE_BUSY;
@@ -97040,11 +97052,11 @@
97040 pOut->u.i = ~sqlite3VdbeIntValue(pIn1);
97041 }
97042 break;
97043 }
97044
97045 /* Opcode: Once P1 P2 * * *
97046 **
97047 ** Fall through to the next instruction the first time this opcode is
97048 ** encountered on each invocation of the byte-code program. Jump to P2
97049 ** on the second and all subsequent encounters during the same invocation.
97050 **
@@ -97056,10 +97068,16 @@
97056 **
97057 ** For subprograms, there is a bitmask in the VdbeFrame that determines
97058 ** whether or not the jump should be taken. The bitmask is necessary
97059 ** because the self-altering code trick does not work for recursive
97060 ** triggers.
 
 
 
 
 
 
97061 */
97062 case OP_Once: { /* jump */
97063 u32 iAddr; /* Address of this instruction */
97064 assert( p->aOp[0].opcode==OP_Init );
97065 if( p->pFrame ){
@@ -103549,12 +103567,12 @@
103549 sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
103550 }
103551 p->rc = rc;
103552 sqlite3SystemError(db, rc);
103553 testcase( sqlite3GlobalConfig.xLog!=0 );
103554 sqlite3_log(rc, "statement aborts at %d: [%s] %s",
103555 (int)(pOp - aOp), p->zSql, p->zErrMsg);
103556 if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
103557 if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
103558 if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){
103559 db->flags |= SQLITE_CorruptRdOnly;
103560 }
@@ -114020,15 +114038,16 @@
114020 pCopy = sqlite3SelectDup(pParse->db, pSelect, 0);
114021 rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest);
114022 sqlite3SelectDelete(pParse->db, pCopy);
114023 sqlite3DbFree(pParse->db, dest.zAffSdst);
114024 if( addrBloom ){
 
 
114025 sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2;
114026 if( dest.iSDParm2==0 ){
114027 sqlite3VdbeChangeToNoop(v, addrBloom);
114028 }else{
114029 sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2;
114030 }
114031 }
114032 if( rc ){
114033 sqlite3KeyInfoUnref(pKeyInfo);
114034 return;
@@ -114471,11 +114490,11 @@
114471 if( destIfFalse==destIfNull ){
114472 /* Combine Step 3 and Step 5 into a single opcode */
114473 if( ExprHasProperty(pExpr, EP_Subrtn) ){
114474 const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr);
114475 assert( pOp->opcode==OP_Once || pParse->nErr );
114476 if( pOp->opcode==OP_Once && pOp->p3>0 ){
114477 assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) );
114478 sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse,
114479 rLhs, nVector); VdbeCoverage(v);
114480 }
114481 }
@@ -124089,11 +124108,11 @@
124089 */
124090 SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){
124091 int i;
124092 i16 iCol16;
124093 assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN );
124094 assert( pIdx->nColumn<=SQLITE_MAX_COLUMN );
124095 iCol16 = iCol;
124096 for(i=0; i<pIdx->nColumn; i++){
124097 if( iCol16==pIdx->aiColumn[i] ){
124098 return i;
124099 }
@@ -127237,10 +127256,11 @@
127237 }else{
127238 j = pCExpr->iColumn;
127239 assert( j<=0x7fff );
127240 if( j<0 ){
127241 j = pTab->iPKey;
 
127242 }else{
127243 if( pTab->aCol[j].notNull==0 ){
127244 pIndex->uniqNotNull = 0;
127245 }
127246 if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
@@ -149706,11 +149726,12 @@
149706 && pE2->iColumn==pColumn->iColumn
149707 ){
149708 return; /* Already present. Return without doing anything. */
149709 }
149710 }
149711 if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
 
149712 pConst->bHasAffBlob = 1;
149713 }
149714
149715 pConst->nConst++;
149716 pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
@@ -149781,11 +149802,12 @@
149781 for(i=0; i<pConst->nConst; i++){
149782 Expr *pColumn = pConst->apExpr[i*2];
149783 if( pColumn==pExpr ) continue;
149784 if( pColumn->iTable!=pExpr->iTable ) continue;
149785 if( pColumn->iColumn!=pExpr->iColumn ) continue;
149786 if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){
 
149787 break;
149788 }
149789 /* A match is found. Add the EP_FixedCol property */
149790 pConst->nChng++;
149791 ExprClearProperty(pExpr, EP_Leaf);
@@ -152965,10 +152987,16 @@
152965 pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
152966 p->pEList, p, wctrlFlags, p->nSelectRow);
152967 if( pWInfo==0 ) goto select_end;
152968 if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
152969 p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
 
 
 
 
 
 
152970 }
152971 if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
152972 sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
152973 }
152974 if( sSort.pOrderBy ){
@@ -160131,11 +160159,11 @@
160131
160132
160133 /*
160134 ** pX is an expression of the form: (vector) IN (SELECT ...)
160135 ** In other words, it is a vector IN operator with a SELECT clause on the
160136 ** LHS. But not all terms in the vector are indexable and the terms might
160137 ** not be in the correct order for indexing.
160138 **
160139 ** This routine makes a copy of the input pX expression and then adjusts
160140 ** the vector on the LHS with corresponding changes to the SELECT so that
160141 ** the vector contains only index terms and those terms are in the correct
@@ -167926,11 +167954,11 @@
167926 }
167927
167928 if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
167929 && pNew->u.btree.nEq<pProbe->nColumn
167930 && (pNew->u.btree.nEq<pProbe->nKeyCol ||
167931 pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
167932 ){
167933 if( pNew->u.btree.nEq>3 ){
167934 sqlite3ProgressCheck(pParse);
167935 }
167936 whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
@@ -171384,11 +171412,12 @@
171384 wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1);
171385 if( db->mallocFailed ) goto whereBeginError;
171386 }
171387
171388 /* TUNING: Assume that a DISTINCT clause on a subquery reduces
171389 ** the output size by a factor of 8 (LogEst -30).
 
171390 */
171391 if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){
171392 WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n",
171393 pWInfo->nRowOut, pWInfo->nRowOut-30));
171394 pWInfo->nRowOut -= 30;
@@ -206261,20 +206290,20 @@
206261 case FTS3_MATCHINFO_LCS:
206262 nVal = pInfo->nCol;
206263 break;
206264
206265 case FTS3_MATCHINFO_LHITS:
206266 nVal = pInfo->nCol * pInfo->nPhrase;
206267 break;
206268
206269 case FTS3_MATCHINFO_LHITS_BM:
206270 nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
206271 break;
206272
206273 default:
206274 assert( cArg==FTS3_MATCHINFO_HITS );
206275 nVal = pInfo->nCol * pInfo->nPhrase * 3;
206276 break;
206277 }
206278
206279 return nVal;
206280 }
@@ -207956,60 +207985,113 @@
207956 ** Growing our own isspace() routine this way is twice as fast as
207957 ** the library isspace() function, resulting in a 7% overall performance
207958 ** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os).
207959 */
207960 static const char jsonIsSpace[] = {
207961 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
207962 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207963 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207964 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207965 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207966 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207967 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207968 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207969
207970 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207971 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207972 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207973 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207974 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207975 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207976 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207977 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207978 };
207979 #define jsonIsspace(x) (jsonIsSpace[(unsigned char)x])
207980
207981 /*
207982 ** The set of all space characters recognized by jsonIsspace().
207983 ** Useful as the second argument to strspn().
207984 */
 
207985 static const char jsonSpaces[] = "\011\012\015\040";
 
 
 
 
 
207986
207987 /*
207988 ** Characters that are special to JSON. Control characters,
207989 ** '"' and '\\' and '\''. Actually, '\'' is not special to
207990 ** canonical JSON, but it is special in JSON-5, so we include
207991 ** it in the set of special characters.
207992 */
207993 static const char jsonIsOk[256] = {
207994 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207995 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207996 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
207997 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
207998 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
207999 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
208000 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208001 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208002
208003 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208004 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208005 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208006 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208007 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208008 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208009 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
208010 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208011 };
208012
208013 /* Objects */
208014 typedef struct JsonCache JsonCache;
208015 typedef struct JsonString JsonString;
@@ -208150,11 +208232,11 @@
208150
208151 /**************************************************************************
208152 ** Forward references
208153 **************************************************************************/
208154 static void jsonReturnStringAsBlob(JsonString*);
208155 static int jsonFuncArgMightBeBinary(sqlite3_value *pJson);
208156 static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*);
208157 static void jsonReturnParse(sqlite3_context*,JsonParse*);
208158 static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32);
208159 static void jsonParseFree(JsonParse*);
208160 static u32 jsonbPayloadSize(const JsonParse*, u32, u32*);
@@ -208568,15 +208650,13 @@
208568 jsonAppendString(p, z, n);
208569 }
208570 break;
208571 }
208572 default: {
208573 if( jsonFuncArgMightBeBinary(pValue) ){
208574 JsonParse px;
208575 memset(&px, 0, sizeof(px));
208576 px.aBlob = (u8*)sqlite3_value_blob(pValue);
208577 px.nBlob = sqlite3_value_bytes(pValue);
208578 jsonTranslateBlobToText(&px, 0, p);
208579 }else if( p->eErr==0 ){
208580 sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
208581 p->eErr = JSTRING_ERR;
208582 jsonStringReset(p);
@@ -210246,37 +210326,10 @@
210246 }
210247 }
210248 return i;
210249 }
210250
210251
210252 /* Return true if the input pJson
210253 **
210254 ** For performance reasons, this routine does not do a detailed check of the
210255 ** input BLOB to ensure that it is well-formed. Hence, false positives are
210256 ** possible. False negatives should never occur, however.
210257 */
210258 static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){
210259 u32 sz, n;
210260 const u8 *aBlob;
210261 int nBlob;
210262 JsonParse s;
210263 if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0;
210264 aBlob = sqlite3_value_blob(pJson);
210265 nBlob = sqlite3_value_bytes(pJson);
210266 if( nBlob<1 ) return 0;
210267 if( NEVER(aBlob==0) || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0;
210268 memset(&s, 0, sizeof(s));
210269 s.aBlob = (u8*)aBlob;
210270 s.nBlob = nBlob;
210271 n = jsonbPayloadSize(&s, 0, &sz);
210272 if( n==0 ) return 0;
210273 if( sz+n!=(u32)nBlob ) return 0;
210274 if( (aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0 ) return 0;
210275 return sz+n==(u32)nBlob;
210276 }
210277
210278 /*
210279 ** Given that a JSONB_ARRAY object starts at offset i, return
210280 ** the number of entries in that array.
210281 */
210282 static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){
@@ -211100,14 +211153,11 @@
211100 pParse->aBlob = aNull;
211101 pParse->nBlob = 1;
211102 return 0;
211103 }
211104 case SQLITE_BLOB: {
211105 if( jsonFuncArgMightBeBinary(pArg) ){
211106 pParse->aBlob = (u8*)sqlite3_value_blob(pArg);
211107 pParse->nBlob = sqlite3_value_bytes(pArg);
211108 }else{
211109 sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1);
211110 return 1;
211111 }
211112 break;
211113 }
@@ -211254,31 +211304,50 @@
211254 }
211255
211256 /*
211257 ** If pArg is a blob that seems like a JSONB blob, then initialize
211258 ** p to point to that JSONB and return TRUE. If pArg does not seem like
211259 ** a JSONB blob, then return FALSE;
211260 **
211261 ** This routine is only called if it is already known that pArg is a
211262 ** blob. The only open question is whether or not the blob appears
211263 ** to be a JSONB blob.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211264 */
211265 static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){
211266 u32 n, sz = 0;
 
 
211267 p->aBlob = (u8*)sqlite3_value_blob(pArg);
211268 p->nBlob = (u32)sqlite3_value_bytes(pArg);
211269 if( p->nBlob==0 ){
211270 p->aBlob = 0;
211271 return 0;
211272 }
211273 if( NEVER(p->aBlob==0) ){
211274 return 0;
211275 }
211276 if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT
211277 && (n = jsonbPayloadSize(p, 0, &sz))>0
211278 && sz+n==p->nBlob
211279 && ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0)
 
 
 
211280 ){
211281 return 1;
211282 }
211283 p->aBlob = 0;
211284 p->nBlob = 0;
@@ -212367,25 +212436,21 @@
212367 sqlite3_result_int(ctx, 0);
212368 #endif
212369 return;
212370 }
212371 case SQLITE_BLOB: {
212372 if( jsonFuncArgMightBeBinary(argv[0]) ){
 
 
212373 if( flags & 0x04 ){
212374 /* Superficial checking only - accomplished by the
212375 ** jsonFuncArgMightBeBinary() call above. */
212376 res = 1;
212377 }else if( flags & 0x08 ){
212378 /* Strict checking. Check by translating BLOB->TEXT->BLOB. If
212379 ** no errors occur, call that a "strict check". */
212380 JsonParse px;
212381 u32 iErr;
212382 memset(&px, 0, sizeof(px));
212383 px.aBlob = (u8*)sqlite3_value_blob(argv[0]);
212384 px.nBlob = sqlite3_value_bytes(argv[0]);
212385 iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1);
212386 res = iErr==0;
212387 }
212388 break;
212389 }
212390 /* Fall through into interpreting the input as text. See note
212391 ** above at tag-20240123-a. */
@@ -212439,13 +212504,11 @@
212439
212440 assert( argc==1 );
212441 UNUSED_PARAMETER(argc);
212442 memset(&s, 0, sizeof(s));
212443 s.db = sqlite3_context_db_handle(ctx);
212444 if( jsonFuncArgMightBeBinary(argv[0]) ){
212445 s.aBlob = (u8*)sqlite3_value_blob(argv[0]);
212446 s.nBlob = sqlite3_value_bytes(argv[0]);
212447 iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1);
212448 }else{
212449 s.zJson = (char*)sqlite3_value_text(argv[0]);
212450 if( s.zJson==0 ) return; /* NULL input or OOM */
212451 s.nJson = sqlite3_value_bytes(argv[0]);
@@ -213126,13 +213189,12 @@
213126 jsonEachCursorReset(p);
213127 if( idxNum==0 ) return SQLITE_OK;
213128 memset(&p->sParse, 0, sizeof(p->sParse));
213129 p->sParse.nJPRef = 1;
213130 p->sParse.db = p->db;
213131 if( jsonFuncArgMightBeBinary(argv[0]) ){
213132 p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
213133 p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
213134 }else{
213135 p->sParse.zJson = (char*)sqlite3_value_text(argv[0]);
213136 p->sParse.nJson = sqlite3_value_bytes(argv[0]);
213137 if( p->sParse.zJson==0 ){
213138 p->i = p->iEnd = 0;
@@ -229245,10 +229307,12 @@
229245 int rc = SQLITE_OK;
229246
229247 if( pTab->nCol==0 ){
229248 u8 *abPK;
229249 assert( pTab->azCol==0 || pTab->abPK==0 );
 
 
229250 rc = sessionTableInfo(pSession, db, zDb,
229251 pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol,
229252 &pTab->azDflt, &pTab->aiIdx, &abPK,
229253 ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
229254 );
@@ -230265,21 +230329,47 @@
230265
230266 /* Check the table schemas match */
230267 if( rc==SQLITE_OK ){
230268 int bHasPk = 0;
230269 int bMismatch = 0;
230270 int nCol; /* Columns in zFrom.zTbl */
230271 int bRowid = 0;
230272 u8 *abPK;
230273 const char **azCol = 0;
230274 rc = sessionTableInfo(0, db, zFrom, zTbl,
230275 &nCol, 0, 0, &azCol, 0, 0, &abPK,
230276 pSession->bImplicitPK ? &bRowid : 0
230277 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230278 if( rc==SQLITE_OK ){
230279 if( pTo->nCol!=nCol ){
230280 bMismatch = 1;
 
 
 
 
 
 
 
230281 }else{
230282 int i;
230283 for(i=0; i<nCol; i++){
230284 if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
230285 if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
@@ -257173,11 +257263,11 @@
257173 int nArg, /* Number of args */
257174 sqlite3_value **apUnused /* Function arguments */
257175 ){
257176 assert( nArg==0 );
257177 UNUSED_PARAM2(nArg, apUnused);
257178 sqlite3_result_text(pCtx, "fts5: 2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6", -1, SQLITE_TRANSIENT);
257179 }
257180
257181 /*
257182 ** Implementation of fts5_locale(LOCALE, TEXT) function.
257183 **
257184
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 20abf1ec107f942e4527901685d61283c9c2 with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -465,11 +465,11 @@
465 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
466 ** [sqlite_version()] and [sqlite_source_id()].
467 */
468 #define SQLITE_VERSION "3.50.0"
469 #define SQLITE_VERSION_NUMBER 3050000
470 #define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -11872,13 +11872,14 @@
11872 ** This may appear to have some counter-intuitive effects if a single row
11873 ** is written to more than once during a session. For example, if a row
11874 ** is inserted while a session object is enabled, then later deleted while
11875 ** the same session object is disabled, no INSERT record will appear in the
11876 ** changeset, even though the delete took place while the session was disabled.
11877 ** Or, if one field of a row is updated while a session is enabled, and
11878 ** then another field of the same row is updated while the session is disabled,
11879 ** the resulting changeset will contain an UPDATE change that updates both
11880 ** fields.
11881 */
11882 SQLITE_API int sqlite3session_changeset(
11883 sqlite3_session *pSession, /* Session object */
11884 int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
11885 void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11946,12 +11947,13 @@
11947 ** To clarify, if this function is called and then a changeset constructed
11948 ** using [sqlite3session_changeset()], then after applying that changeset to
11949 ** database zFrom the contents of the two compatible tables would be
11950 ** identical.
11951 **
11952 ** Unless the call to this function is a no-op as described above, it is an
11953 ** error if database zFrom does not exist or does not contain the required
11954 ** compatible table.
11955 **
11956 ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
11957 ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
11958 ** may be set to point to a buffer containing an English language error
11959 ** message. It is the responsibility of the caller to free this buffer using
@@ -19162,10 +19164,11 @@
19164 unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
19165 unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
19166 unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
19167 unsigned bNoQuery:1; /* Do not use this index to optimize queries */
19168 unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
19169 unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
19170 unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
19171 unsigned bHasExpr:1; /* Index contains an expression, either a literal
19172 ** expression, or a reference to a VIRTUAL column */
19173 #ifdef SQLITE_ENABLE_STAT4
19174 int nSample; /* Number of elements in aSample[] */
@@ -32985,10 +32988,19 @@
32988 va_end(ap);
32989 zBuf[acc.nChar] = 0;
32990 return zBuf;
32991 }
32992
32993 /* Maximum size of an sqlite3_log() message. */
32994 #if defined(SQLITE_MAX_LOG_MESSAGE)
32995 /* Leave the definition as supplied */
32996 #elif SQLITE_PRINT_BUF_SIZE*10>10000
32997 # define SQLITE_MAX_LOG_MESSAGE 10000
32998 #else
32999 # define SQLITE_MAX_LOG_MESSAGE (SQLITE_PRINT_BUF_SIZE*10)
33000 #endif
33001
33002 /*
33003 ** This is the routine that actually formats the sqlite3_log() message.
33004 ** We house it in a separate routine from sqlite3_log() to avoid using
33005 ** stack space on small-stack systems when logging is disabled.
33006 **
@@ -33001,11 +33013,11 @@
33013 ** Care must be taken that any sqlite3_log() calls that occur while the
33014 ** memory mutex is held do not use these mechanisms.
33015 */
33016 static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
33017 StrAccum acc; /* String accumulator */
33018 char zMsg[SQLITE_MAX_LOG_MESSAGE]; /* Complete log message */
33019
33020 sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
33021 sqlite3_str_vappendf(&acc, zFormat, ap);
33022 sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
33023 sqlite3StrAccumFinish(&acc));
@@ -95714,11 +95726,11 @@
95726 }
95727 }else{
95728 sqlite3VdbeError(p, "%s", pOp->p4.z);
95729 }
95730 pcx = (int)(pOp - aOp);
95731 sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql);
95732 }
95733 rc = sqlite3VdbeHalt(p);
95734 assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR );
95735 if( rc==SQLITE_BUSY ){
95736 p->rc = SQLITE_BUSY;
@@ -97040,11 +97052,11 @@
97052 pOut->u.i = ~sqlite3VdbeIntValue(pIn1);
97053 }
97054 break;
97055 }
97056
97057 /* Opcode: Once P1 P2 P3 * *
97058 **
97059 ** Fall through to the next instruction the first time this opcode is
97060 ** encountered on each invocation of the byte-code program. Jump to P2
97061 ** on the second and all subsequent encounters during the same invocation.
97062 **
@@ -97056,10 +97068,16 @@
97068 **
97069 ** For subprograms, there is a bitmask in the VdbeFrame that determines
97070 ** whether or not the jump should be taken. The bitmask is necessary
97071 ** because the self-altering code trick does not work for recursive
97072 ** triggers.
97073 **
97074 ** The P3 operand is not used directly by this opcode. However P3 is
97075 ** used by the code generator as follows: If this opcode is the start
97076 ** of a subroutine and that subroutine uses a Bloom filter, then P3 will
97077 ** be the register that holds that Bloom filter. See tag-202407032019
97078 ** in the source code for implementation details.
97079 */
97080 case OP_Once: { /* jump */
97081 u32 iAddr; /* Address of this instruction */
97082 assert( p->aOp[0].opcode==OP_Init );
97083 if( p->pFrame ){
@@ -103549,12 +103567,12 @@
103567 sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
103568 }
103569 p->rc = rc;
103570 sqlite3SystemError(db, rc);
103571 testcase( sqlite3GlobalConfig.xLog!=0 );
103572 sqlite3_log(rc, "statement aborts at %d: %s; [%s]",
103573 (int)(pOp - aOp), p->zErrMsg, p->zSql);
103574 if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p);
103575 if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
103576 if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){
103577 db->flags |= SQLITE_CorruptRdOnly;
103578 }
@@ -114020,15 +114038,16 @@
114038 pCopy = sqlite3SelectDup(pParse->db, pSelect, 0);
114039 rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest);
114040 sqlite3SelectDelete(pParse->db, pCopy);
114041 sqlite3DbFree(pParse->db, dest.zAffSdst);
114042 if( addrBloom ){
114043 /* Remember that location of the Bloom filter in the P3 operand
114044 ** of the OP_Once that began this subroutine. tag-202407032019 */
114045 sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2;
114046 if( dest.iSDParm2==0 ){
114047 /* If the Bloom filter won't actually be used, keep it small */
114048 sqlite3VdbeGetOp(v, addrBloom)->p1 = 10;
 
114049 }
114050 }
114051 if( rc ){
114052 sqlite3KeyInfoUnref(pKeyInfo);
114053 return;
@@ -114471,11 +114490,11 @@
114490 if( destIfFalse==destIfNull ){
114491 /* Combine Step 3 and Step 5 into a single opcode */
114492 if( ExprHasProperty(pExpr, EP_Subrtn) ){
114493 const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr);
114494 assert( pOp->opcode==OP_Once || pParse->nErr );
114495 if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */
114496 assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) );
114497 sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse,
114498 rLhs, nVector); VdbeCoverage(v);
114499 }
114500 }
@@ -124089,11 +124108,11 @@
124108 */
124109 SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){
124110 int i;
124111 i16 iCol16;
124112 assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN );
124113 assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 );
124114 iCol16 = iCol;
124115 for(i=0; i<pIdx->nColumn; i++){
124116 if( iCol16==pIdx->aiColumn[i] ){
124117 return i;
124118 }
@@ -127237,10 +127256,11 @@
127256 }else{
127257 j = pCExpr->iColumn;
127258 assert( j<=0x7fff );
127259 if( j<0 ){
127260 j = pTab->iPKey;
127261 pIndex->bIdxRowid = 1;
127262 }else{
127263 if( pTab->aCol[j].notNull==0 ){
127264 pIndex->uniqNotNull = 0;
127265 }
127266 if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
@@ -149706,11 +149726,12 @@
149726 && pE2->iColumn==pColumn->iColumn
149727 ){
149728 return; /* Already present. Return without doing anything. */
149729 }
149730 }
149731 assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
149732 if( sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
149733 pConst->bHasAffBlob = 1;
149734 }
149735
149736 pConst->nConst++;
149737 pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
@@ -149781,11 +149802,12 @@
149802 for(i=0; i<pConst->nConst; i++){
149803 Expr *pColumn = pConst->apExpr[i*2];
149804 if( pColumn==pExpr ) continue;
149805 if( pColumn->iTable!=pExpr->iTable ) continue;
149806 if( pColumn->iColumn!=pExpr->iColumn ) continue;
149807 assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
149808 if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
149809 break;
149810 }
149811 /* A match is found. Add the EP_FixedCol property */
149812 pConst->nChng++;
149813 ExprClearProperty(pExpr, EP_Leaf);
@@ -152965,10 +152987,16 @@
152987 pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
152988 p->pEList, p, wctrlFlags, p->nSelectRow);
152989 if( pWInfo==0 ) goto select_end;
152990 if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
152991 p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
152992 if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){
152993 /* TUNING: For a UNION CTE, because UNION is implies DISTINCT,
152994 ** reduce the estimated output row count by 8 (LogEst 30).
152995 ** Search for tag-20250414a to see other cases */
152996 p->nSelectRow -= 30;
152997 }
152998 }
152999 if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
153000 sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
153001 }
153002 if( sSort.pOrderBy ){
@@ -160131,11 +160159,11 @@
160159
160160
160161 /*
160162 ** pX is an expression of the form: (vector) IN (SELECT ...)
160163 ** In other words, it is a vector IN operator with a SELECT clause on the
160164 ** RHS. But not all terms in the vector are indexable and the terms might
160165 ** not be in the correct order for indexing.
160166 **
160167 ** This routine makes a copy of the input pX expression and then adjusts
160168 ** the vector on the LHS with corresponding changes to the SELECT so that
160169 ** the vector contains only index terms and those terms are in the correct
@@ -167926,11 +167954,11 @@
167954 }
167955
167956 if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
167957 && pNew->u.btree.nEq<pProbe->nColumn
167958 && (pNew->u.btree.nEq<pProbe->nKeyCol ||
167959 (pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
167960 ){
167961 if( pNew->u.btree.nEq>3 ){
167962 sqlite3ProgressCheck(pParse);
167963 }
167964 whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
@@ -171384,11 +171412,12 @@
171412 wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1);
171413 if( db->mallocFailed ) goto whereBeginError;
171414 }
171415
171416 /* TUNING: Assume that a DISTINCT clause on a subquery reduces
171417 ** the output size by a factor of 8 (LogEst -30). Search for
171418 ** tag-20250414a to see other cases.
171419 */
171420 if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){
171421 WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n",
171422 pWInfo->nRowOut, pWInfo->nRowOut-30));
171423 pWInfo->nRowOut -= 30;
@@ -206261,20 +206290,20 @@
206290 case FTS3_MATCHINFO_LCS:
206291 nVal = pInfo->nCol;
206292 break;
206293
206294 case FTS3_MATCHINFO_LHITS:
206295 nVal = (size_t)pInfo->nCol * pInfo->nPhrase;
206296 break;
206297
206298 case FTS3_MATCHINFO_LHITS_BM:
206299 nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
206300 break;
206301
206302 default:
206303 assert( cArg==FTS3_MATCHINFO_HITS );
206304 nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3;
206305 break;
206306 }
206307
206308 return nVal;
206309 }
@@ -207956,60 +207985,113 @@
207985 ** Growing our own isspace() routine this way is twice as fast as
207986 ** the library isspace() function, resulting in a 7% overall performance
207987 ** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os).
207988 */
207989 static const char jsonIsSpace[] = {
207990 #ifdef SQLITE_ASCII
207991 /*0 1 2 3 4 5 6 7 8 9 a b c d e f */
207992 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 */
207993 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
207994 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
207995 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */
207996 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */
207997 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */
207998 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */
207999 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */
208000
208001 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */
208002 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */
208003 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */
208004 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */
208005 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */
208006 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
208007 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */
208008 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */
208009 #endif
208010 #ifdef SQLITE_EBCDIC
208011 /*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208012 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0 */
208013 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208014 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
208015 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */
208016 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */
208017 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */
208018 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */
208019 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */
208020
208021 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */
208022 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */
208023 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */
208024 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */
208025 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */
208026 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
208027 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */
208028 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */
208029 #endif
208030
208031 };
208032 #define jsonIsspace(x) (jsonIsSpace[(unsigned char)x])
208033
208034 /*
208035 ** The set of all space characters recognized by jsonIsspace().
208036 ** Useful as the second argument to strspn().
208037 */
208038 #ifdef SQLITE_ASCII
208039 static const char jsonSpaces[] = "\011\012\015\040";
208040 #endif
208041 #ifdef SQLITE_EBCDIC
208042 static const char jsonSpaces[] = "\005\045\015\100";
208043 #endif
208044
208045
208046 /*
208047 ** Characters that are special to JSON. Control characters,
208048 ** '"' and '\\' and '\''. Actually, '\'' is not special to
208049 ** canonical JSON, but it is special in JSON-5, so we include
208050 ** it in the set of special characters.
208051 */
208052 static const char jsonIsOk[256] = {
208053 #ifdef SQLITE_ASCII
208054 /*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208055 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
208056 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208057 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 2 */
208058 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 3 */
208059 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */
208060 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 5 */
208061 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
208062 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */
208063
208064 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */
208065 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */
208066 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */
208067 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */
208068 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */
208069 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */
208070 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
208071 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */
208072 #endif
208073 #ifdef SQLITE_EBCDIC
208074 /*0 1 2 3 4 5 6 7 8 9 a b c d e f */
208075 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
208076 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */
208077 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */
208078 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 3 */
208079 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */
208080 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5 */
208081 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
208082 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, /* 7 */
208083
208084 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */
208085 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */
208086 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */
208087 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */
208088 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */
208089 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */
208090 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
208091 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */
208092 #endif
208093 };
208094
208095 /* Objects */
208096 typedef struct JsonCache JsonCache;
208097 typedef struct JsonString JsonString;
@@ -208150,11 +208232,11 @@
208232
208233 /**************************************************************************
208234 ** Forward references
208235 **************************************************************************/
208236 static void jsonReturnStringAsBlob(JsonString*);
208237 static int jsonArgIsJsonb(sqlite3_value *pJson, JsonParse *p);
208238 static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*);
208239 static void jsonReturnParse(sqlite3_context*,JsonParse*);
208240 static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32);
208241 static void jsonParseFree(JsonParse*);
208242 static u32 jsonbPayloadSize(const JsonParse*, u32, u32*);
@@ -208568,15 +208650,13 @@
208650 jsonAppendString(p, z, n);
208651 }
208652 break;
208653 }
208654 default: {
208655 JsonParse px;
208656 memset(&px, 0, sizeof(px));
208657 if( jsonArgIsJsonb(pValue, &px) ){
 
 
208658 jsonTranslateBlobToText(&px, 0, p);
208659 }else if( p->eErr==0 ){
208660 sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
208661 p->eErr = JSTRING_ERR;
208662 jsonStringReset(p);
@@ -210246,37 +210326,10 @@
210326 }
210327 }
210328 return i;
210329 }
210330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210331 /*
210332 ** Given that a JSONB_ARRAY object starts at offset i, return
210333 ** the number of entries in that array.
210334 */
210335 static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){
@@ -211100,14 +211153,11 @@
211153 pParse->aBlob = aNull;
211154 pParse->nBlob = 1;
211155 return 0;
211156 }
211157 case SQLITE_BLOB: {
211158 if( !jsonArgIsJsonb(pArg, pParse) ){
 
 
 
211159 sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1);
211160 return 1;
211161 }
211162 break;
211163 }
@@ -211254,31 +211304,50 @@
211304 }
211305
211306 /*
211307 ** If pArg is a blob that seems like a JSONB blob, then initialize
211308 ** p to point to that JSONB and return TRUE. If pArg does not seem like
211309 ** a JSONB blob, then return FALSE.
211310 **
211311 ** For small BLOBs (having no more than 7 bytes of payload) a full
211312 ** validity check is done. So for small BLOBs this routine only returns
211313 ** true if the value is guaranteed to be a valid JSONB. For larger BLOBs
211314 ** (8 byte or more of payload) only the size of the outermost element is
211315 ** checked to verify that the BLOB is superficially valid JSONB.
211316 **
211317 ** A full JSONB validation is done on smaller BLOBs because those BLOBs might
211318 ** also be text JSON that has been incorrectly cast into a BLOB.
211319 ** (See tag-20240123-a and https://sqlite.org/forum/forumpost/012136abd5)
211320 ** If the BLOB is 9 bytes are larger, then it is not possible for the
211321 ** superficial size check done here to pass if the input is really text
211322 ** JSON so we do not need to look deeper in that case.
211323 **
211324 ** Why we only need to do full JSONB validation for smaller BLOBs:
211325 **
211326 ** The first byte of valid JSON text must be one of: '{', '[', '"', ' ', '\n',
211327 ** '\r', '\t', '-', or a digit '0' through '9'. Of these, only a subset
211328 ** can also be the first byte of JSONB: '{', '[', and digits '3'
211329 ** through '9'. In every one of those cases, the payload size is 7 bytes
211330 ** or less. So if we do full JSONB validation for every BLOB where the
211331 ** payload is less than 7 bytes, we will never get a false positive for
211332 ** JSONB on an input that is really text JSON.
211333 */
211334 static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){
211335 u32 n, sz = 0;
211336 u8 c;
211337 if( sqlite3_value_type(pArg)!=SQLITE_BLOB ) return 0;
211338 p->aBlob = (u8*)sqlite3_value_blob(pArg);
211339 p->nBlob = (u32)sqlite3_value_bytes(pArg);
211340 if( p->nBlob>0
211341 && ALWAYS(p->aBlob!=0)
211342 && ((c = p->aBlob[0]) & 0x0f)<=JSONB_OBJECT
 
 
 
 
 
211343 && (n = jsonbPayloadSize(p, 0, &sz))>0
211344 && sz+n==p->nBlob
211345 && ((c & 0x0f)>JSONB_FALSE || sz==0)
211346 && (sz>7
211347 || (c!=0x7b && c!=0x5b && !sqlite3Isdigit(c))
211348 || jsonbValidityCheck(p, 0, p->nBlob, 1)==0)
211349 ){
211350 return 1;
211351 }
211352 p->aBlob = 0;
211353 p->nBlob = 0;
@@ -212367,25 +212436,21 @@
212436 sqlite3_result_int(ctx, 0);
212437 #endif
212438 return;
212439 }
212440 case SQLITE_BLOB: {
212441 JsonParse py;
212442 memset(&py, 0, sizeof(py));
212443 if( jsonArgIsJsonb(argv[0], &py) ){
212444 if( flags & 0x04 ){
212445 /* Superficial checking only - accomplished by the
212446 ** jsonArgIsJsonb() call above. */
212447 res = 1;
212448 }else if( flags & 0x08 ){
212449 /* Strict checking. Check by translating BLOB->TEXT->BLOB. If
212450 ** no errors occur, call that a "strict check". */
212451 res = 0==jsonbValidityCheck(&py, 0, py.nBlob, 1);
 
 
 
 
 
 
212452 }
212453 break;
212454 }
212455 /* Fall through into interpreting the input as text. See note
212456 ** above at tag-20240123-a. */
@@ -212439,13 +212504,11 @@
212504
212505 assert( argc==1 );
212506 UNUSED_PARAMETER(argc);
212507 memset(&s, 0, sizeof(s));
212508 s.db = sqlite3_context_db_handle(ctx);
212509 if( jsonArgIsJsonb(argv[0], &s) ){
 
 
212510 iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1);
212511 }else{
212512 s.zJson = (char*)sqlite3_value_text(argv[0]);
212513 if( s.zJson==0 ) return; /* NULL input or OOM */
212514 s.nJson = sqlite3_value_bytes(argv[0]);
@@ -213126,13 +213189,12 @@
213189 jsonEachCursorReset(p);
213190 if( idxNum==0 ) return SQLITE_OK;
213191 memset(&p->sParse, 0, sizeof(p->sParse));
213192 p->sParse.nJPRef = 1;
213193 p->sParse.db = p->db;
213194 if( jsonArgIsJsonb(argv[0], &p->sParse) ){
213195 /* We have JSONB */
 
213196 }else{
213197 p->sParse.zJson = (char*)sqlite3_value_text(argv[0]);
213198 p->sParse.nJson = sqlite3_value_bytes(argv[0]);
213199 if( p->sParse.zJson==0 ){
213200 p->i = p->iEnd = 0;
@@ -229245,10 +229307,12 @@
229307 int rc = SQLITE_OK;
229308
229309 if( pTab->nCol==0 ){
229310 u8 *abPK;
229311 assert( pTab->azCol==0 || pTab->abPK==0 );
229312 sqlite3_free(pTab->azCol);
229313 pTab->abPK = 0;
229314 rc = sessionTableInfo(pSession, db, zDb,
229315 pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol,
229316 &pTab->azDflt, &pTab->aiIdx, &abPK,
229317 ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
229318 );
@@ -230265,21 +230329,47 @@
230329
230330 /* Check the table schemas match */
230331 if( rc==SQLITE_OK ){
230332 int bHasPk = 0;
230333 int bMismatch = 0;
230334 int nCol = 0; /* Columns in zFrom.zTbl */
230335 int bRowid = 0;
230336 u8 *abPK = 0;
230337 const char **azCol = 0;
230338 char *zDbExists = 0;
230339
230340 /* Check that database zFrom is attached. */
230341 zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom);
230342 if( zDbExists==0 ){
230343 rc = SQLITE_NOMEM;
230344 }else{
230345 sqlite3_stmt *pDbExists = 0;
230346 rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0);
230347 if( rc==SQLITE_ERROR ){
230348 rc = SQLITE_OK;
230349 nCol = -1;
230350 }
230351 sqlite3_finalize(pDbExists);
230352 sqlite3_free(zDbExists);
230353 }
230354
230355 if( rc==SQLITE_OK && nCol==0 ){
230356 rc = sessionTableInfo(0, db, zFrom, zTbl,
230357 &nCol, 0, 0, &azCol, 0, 0, &abPK,
230358 pSession->bImplicitPK ? &bRowid : 0
230359 );
230360 }
230361 if( rc==SQLITE_OK ){
230362 if( pTo->nCol!=nCol ){
230363 if( nCol<=0 ){
230364 rc = SQLITE_SCHEMA;
230365 if( pzErrMsg ){
230366 *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl);
230367 }
230368 }else{
230369 bMismatch = 1;
230370 }
230371 }else{
230372 int i;
230373 for(i=0; i<nCol; i++){
230374 if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
230375 if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
@@ -257173,11 +257263,11 @@
257263 int nArg, /* Number of args */
257264 sqlite3_value **apUnused /* Function arguments */
257265 ){
257266 assert( nArg==0 );
257267 UNUSED_PARAM2(nArg, apUnused);
257268 sqlite3_result_text(pCtx, "fts5: 2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849", -1, SQLITE_TRANSIENT);
257269 }
257270
257271 /*
257272 ** Implementation of fts5_locale(LOCALE, TEXT) function.
257273 **
257274
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
146146
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147147
** [sqlite_version()] and [sqlite_source_id()].
148148
*/
149149
#define SQLITE_VERSION "3.50.0"
150150
#define SQLITE_VERSION_NUMBER 3050000
151
-#define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6"
151
+#define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849"
152152
153153
/*
154154
** CAPI3REF: Run-Time Library Version Numbers
155155
** KEYWORDS: sqlite3_version sqlite3_sourceid
156156
**
@@ -11553,13 +11553,14 @@
1155311553
** This may appear to have some counter-intuitive effects if a single row
1155411554
** is written to more than once during a session. For example, if a row
1155511555
** is inserted while a session object is enabled, then later deleted while
1155611556
** the same session object is disabled, no INSERT record will appear in the
1155711557
** changeset, even though the delete took place while the session was disabled.
11558
-** Or, if one field of a row is updated while a session is disabled, and
11559
-** another field of the same row is updated while the session is enabled, the
11560
-** resulting changeset will contain an UPDATE change that updates both fields.
11558
+** Or, if one field of a row is updated while a session is enabled, and
11559
+** then another field of the same row is updated while the session is disabled,
11560
+** the resulting changeset will contain an UPDATE change that updates both
11561
+** fields.
1156111562
*/
1156211563
SQLITE_API int sqlite3session_changeset(
1156311564
sqlite3_session *pSession, /* Session object */
1156411565
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
1156511566
void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11627,12 +11628,13 @@
1162711628
** To clarify, if this function is called and then a changeset constructed
1162811629
** using [sqlite3session_changeset()], then after applying that changeset to
1162911630
** database zFrom the contents of the two compatible tables would be
1163011631
** identical.
1163111632
**
11632
-** It an error if database zFrom does not exist or does not contain the
11633
-** required compatible table.
11633
+** Unless the call to this function is a no-op as described above, it is an
11634
+** error if database zFrom does not exist or does not contain the required
11635
+** compatible table.
1163411636
**
1163511637
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
1163611638
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
1163711639
** may be set to point to a buffer containing an English language error
1163811640
** message. It is the responsibility of the caller to free this buffer using
1163911641
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.50.0"
150 #define SQLITE_VERSION_NUMBER 3050000
151 #define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
@@ -11553,13 +11553,14 @@
11553 ** This may appear to have some counter-intuitive effects if a single row
11554 ** is written to more than once during a session. For example, if a row
11555 ** is inserted while a session object is enabled, then later deleted while
11556 ** the same session object is disabled, no INSERT record will appear in the
11557 ** changeset, even though the delete took place while the session was disabled.
11558 ** Or, if one field of a row is updated while a session is disabled, and
11559 ** another field of the same row is updated while the session is enabled, the
11560 ** resulting changeset will contain an UPDATE change that updates both fields.
 
11561 */
11562 SQLITE_API int sqlite3session_changeset(
11563 sqlite3_session *pSession, /* Session object */
11564 int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
11565 void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11627,12 +11628,13 @@
11627 ** To clarify, if this function is called and then a changeset constructed
11628 ** using [sqlite3session_changeset()], then after applying that changeset to
11629 ** database zFrom the contents of the two compatible tables would be
11630 ** identical.
11631 **
11632 ** It an error if database zFrom does not exist or does not contain the
11633 ** required compatible table.
 
11634 **
11635 ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
11636 ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
11637 ** may be set to point to a buffer containing an English language error
11638 ** message. It is the responsibility of the caller to free this buffer using
11639
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.50.0"
150 #define SQLITE_VERSION_NUMBER 3050000
151 #define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
@@ -11553,13 +11553,14 @@
11553 ** This may appear to have some counter-intuitive effects if a single row
11554 ** is written to more than once during a session. For example, if a row
11555 ** is inserted while a session object is enabled, then later deleted while
11556 ** the same session object is disabled, no INSERT record will appear in the
11557 ** changeset, even though the delete took place while the session was disabled.
11558 ** Or, if one field of a row is updated while a session is enabled, and
11559 ** then another field of the same row is updated while the session is disabled,
11560 ** the resulting changeset will contain an UPDATE change that updates both
11561 ** fields.
11562 */
11563 SQLITE_API int sqlite3session_changeset(
11564 sqlite3_session *pSession, /* Session object */
11565 int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
11566 void **ppChangeset /* OUT: Buffer containing changeset */
@@ -11627,12 +11628,13 @@
11628 ** To clarify, if this function is called and then a changeset constructed
11629 ** using [sqlite3session_changeset()], then after applying that changeset to
11630 ** database zFrom the contents of the two compatible tables would be
11631 ** identical.
11632 **
11633 ** Unless the call to this function is a no-op as described above, it is an
11634 ** error if database zFrom does not exist or does not contain the required
11635 ** compatible table.
11636 **
11637 ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
11638 ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
11639 ** may be set to point to a buffer containing an English language error
11640 ** message. It is the responsibility of the caller to free this buffer using
11641
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
2828
return $logourl
2929
}
3030
set logourl [getLogoUrl $baseurl]
3131
</th1>
3232
<a href="$logourl">
33
- <img src="$logo_image_url" border="0" alt="$project_name">
33
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
3434
</a>
3535
</div>
3636
<div class="title">
3737
<h1>$<project_name></h1>
3838
<span class="page-title">$<title></span>
3939
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
28 return $logourl
29 }
30 set logourl [getLogoUrl $baseurl]
31 </th1>
32 <a href="$logourl">
33 <img src="$logo_image_url" border="0" alt="$project_name">
34 </a>
35 </div>
36 <div class="title">
37 <h1>$<project_name></h1>
38 <span class="page-title">$<title></span>
39
--- skins/default/header.txt
+++ skins/default/header.txt
@@ -28,11 +28,11 @@
28 return $logourl
29 }
30 set logourl [getLogoUrl $baseurl]
31 </th1>
32 <a href="$logourl">
33 <img src="$logo_image_url" border="0" alt="$<project_name>">
34 </a>
35 </div>
36 <div class="title">
37 <h1>$<project_name></h1>
38 <span class="page-title">$<title></span>
39
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
6565
# Link logo to the top of the current repo
6666
set logourl $baseurl
6767
}
6868
</th1>
6969
<a href="$logourl">
70
- <img src="$logo_image_url" border="0" alt="$project_name">
70
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
7171
</a>
7272
</div>
7373
<div class="title">$<title></div>
7474
<div class="status"><nobr><th1>
7575
if {[info exists login]} {
7676
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$project_name">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr><th1>
75 if {[info exists login]} {
76
--- skins/eagle/header.txt
+++ skins/eagle/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$<project_name>">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr><th1>
75 if {[info exists login]} {
76
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
5959
return $logourl
6060
}
6161
set logourl [getLogoUrl $baseurl]
6262
</th1>
6363
<a href="$logourl">
64
- <img src="$logo_image_url" border="0" alt="$project_name">
64
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
6565
</a>
6666
</div>
6767
<div class="title">$<title></div>
6868
<div class="status"><nobr><th1>
6969
if {[info exists login]} {
7070
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
59 return $logourl
60 }
61 set logourl [getLogoUrl $baseurl]
62 </th1>
63 <a href="$logourl">
64 <img src="$logo_image_url" border="0" alt="$project_name">
65 </a>
66 </div>
67 <div class="title">$<title></div>
68 <div class="status"><nobr><th1>
69 if {[info exists login]} {
70
--- skins/original/header.txt
+++ skins/original/header.txt
@@ -59,11 +59,11 @@
59 return $logourl
60 }
61 set logourl [getLogoUrl $baseurl]
62 </th1>
63 <a href="$logourl">
64 <img src="$logo_image_url" border="0" alt="$<project_name>">
65 </a>
66 </div>
67 <div class="title">$<title></div>
68 <div class="status"><nobr><th1>
69 if {[info exists login]} {
70
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
6565
# Link logo to the top of the current repo
6666
set logourl $baseurl
6767
}
6868
</th1>
6969
<a href="$logourl">
70
- <img src="$logo_image_url" border="0" alt="$project_name">
70
+ <img src="$logo_image_url" border="0" alt="$<project_name>">
7171
</a>
7272
</div>
7373
<div class="title">$<title></div>
7474
<div class="status"><nobr>
7575
<th1>
7676
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$project_name">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr>
75 <th1>
76
--- skins/xekri/header.txt
+++ skins/xekri/header.txt
@@ -65,11 +65,11 @@
65 # Link logo to the top of the current repo
66 set logourl $baseurl
67 }
68 </th1>
69 <a href="$logourl">
70 <img src="$logo_image_url" border="0" alt="$<project_name>">
71 </a>
72 </div>
73 <div class="title">$<title></div>
74 <div class="status"><nobr>
75 <th1>
76
+41 -20
--- src/alerts.c
+++ src/alerts.c
@@ -655,11 +655,10 @@
655655
emailerError(p, "Could not start SMTP session: %s",
656656
p->pSmtp ? p->pSmtp->zErr : "reason unknown");
657657
}else if( p->zDest[0]=='d' ){
658658
smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out);
659659
}
660
- smtp_client_startup(p->pSmtp);
661660
}
662661
}
663662
return p;
664663
}
665664
@@ -985,13 +984,10 @@
985984
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
986985
}else{
987986
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
988987
}
989988
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
990
- if( p->zListId && p->zListId[0] ){
991
- blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
992
- }
993989
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
994990
/* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
995991
** the current unix-time in hex, $(random) is a 64-bit random number,
996992
** and $(from) is the domain part of the email-self setting. */
997993
sqlite3_randomness(sizeof(r1), &r1);
@@ -1033,13 +1029,21 @@
10331029
blob_write_to_file(&all, zFile);
10341030
fossil_free(zFile);
10351031
}else if( p->pSmtp ){
10361032
char **azTo = 0;
10371033
int nTo = 0;
1034
+ SmtpSession *pSmtp = p->pSmtp;
10381035
email_header_to(pHdr, &nTo, &azTo);
1039
- if( nTo>0 ){
1040
- smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all));
1036
+ if( nTo>0 && !pSmtp->bFatal ){
1037
+ smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
1038
+ if( pSmtp->zErr && !pSmtp->bFatal ){
1039
+ smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
1040
+ }
1041
+ if( pSmtp->zErr ){
1042
+ fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry",
1043
+ pSmtp->zErr);
1044
+ }
10411045
email_header_to_free(nTo, azTo);
10421046
}
10431047
}else if( strcmp(p->zDest, "stdout")==0 ){
10441048
char **azTo = 0;
10451049
int nTo = 0;
@@ -3208,18 +3212,21 @@
32083212
Blob fhdr, fbody;
32093213
blob_init(&fhdr, 0, 0);
32103214
blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
32113215
blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
32123216
blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3213
- blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3214
- zUrl, zCode);
3215
- blob_appendf(&fhdr,
3216
- "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3217
- blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3218
- zUrl, zCode);
3219
- /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3220
- ** zUrl, zCode); */
3217
+ if( pSender->zListId && pSender->zListId[0] ){
3218
+ blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
3219
+ blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3220
+ zUrl, zCode);
3221
+ blob_appendf(&fhdr,
3222
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3223
+ blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3224
+ zUrl, zCode);
3225
+ /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3226
+ ** zUrl, zCode); */
3227
+ }
32213228
alert_send(pSender,&fhdr,&fbody,p->zFromName);
32223229
nSent++;
32233230
blob_reset(&fhdr);
32243231
blob_reset(&fbody);
32253232
}else{
@@ -3238,15 +3245,19 @@
32383245
blob_append(&body, "\n", 1);
32393246
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
32403247
}
32413248
}
32423249
if( nHit==0 ) continue;
3243
- blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3244
- zUrl, zCode);
3245
- blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3246
- blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3247
- zUrl, zCode);
3250
+ if( pSender->zListId && pSender->zListId[0] ){
3251
+ blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3252
+ blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3253
+ zUrl, zCode);
3254
+ blob_appendf(&hdr,
3255
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3256
+ blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3257
+ zUrl, zCode);
3258
+ }
32483259
alert_send(pSender,&hdr,&body,0);
32493260
nSent++;
32503261
blob_truncate(&hdr, 0);
32513262
blob_truncate(&body, 0);
32523263
}
@@ -3288,18 +3299,28 @@
32883299
" AND length(sdigest)>0",
32893300
iNewWarn, iOldWarn
32903301
);
32913302
while( db_step(&q)==SQLITE_ROW ){
32923303
Blob hdr, body;
3304
+ const char *zCode = db_column_text(&q,0);
32933305
blob_init(&hdr, 0, 0);
32943306
blob_init(&body, 0, 0);
32953307
alert_renewal_msg(&hdr, &body,
3296
- db_column_text(&q,0),
3308
+ zCode,
32973309
db_column_int(&q,1),
32983310
db_column_text(&q,2),
32993311
db_column_text(&q,3),
33003312
zRepoName, zUrl);
3313
+ if( pSender->zListId && pSender->zListId[0] ){
3314
+ blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3315
+ blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3316
+ zUrl, zCode);
3317
+ blob_appendf(&hdr,
3318
+ "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3319
+ blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3320
+ zUrl, zCode);
3321
+ }
33013322
alert_send(pSender,&hdr,&body,0);
33023323
blob_reset(&hdr);
33033324
blob_reset(&body);
33043325
}
33053326
db_finalize(&q);
33063327
--- src/alerts.c
+++ src/alerts.c
@@ -655,11 +655,10 @@
655 emailerError(p, "Could not start SMTP session: %s",
656 p->pSmtp ? p->pSmtp->zErr : "reason unknown");
657 }else if( p->zDest[0]=='d' ){
658 smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out);
659 }
660 smtp_client_startup(p->pSmtp);
661 }
662 }
663 return p;
664 }
665
@@ -985,13 +984,10 @@
985 blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
986 }else{
987 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
988 }
989 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
990 if( p->zListId && p->zListId[0] ){
991 blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
992 }
993 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
994 /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
995 ** the current unix-time in hex, $(random) is a 64-bit random number,
996 ** and $(from) is the domain part of the email-self setting. */
997 sqlite3_randomness(sizeof(r1), &r1);
@@ -1033,13 +1029,21 @@
1033 blob_write_to_file(&all, zFile);
1034 fossil_free(zFile);
1035 }else if( p->pSmtp ){
1036 char **azTo = 0;
1037 int nTo = 0;
 
1038 email_header_to(pHdr, &nTo, &azTo);
1039 if( nTo>0 ){
1040 smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all));
 
 
 
 
 
 
 
1041 email_header_to_free(nTo, azTo);
1042 }
1043 }else if( strcmp(p->zDest, "stdout")==0 ){
1044 char **azTo = 0;
1045 int nTo = 0;
@@ -3208,18 +3212,21 @@
3208 Blob fhdr, fbody;
3209 blob_init(&fhdr, 0, 0);
3210 blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
3211 blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
3212 blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3213 blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3214 zUrl, zCode);
3215 blob_appendf(&fhdr,
3216 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3217 blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3218 zUrl, zCode);
3219 /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3220 ** zUrl, zCode); */
 
 
 
3221 alert_send(pSender,&fhdr,&fbody,p->zFromName);
3222 nSent++;
3223 blob_reset(&fhdr);
3224 blob_reset(&fbody);
3225 }else{
@@ -3238,15 +3245,19 @@
3238 blob_append(&body, "\n", 1);
3239 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
3240 }
3241 }
3242 if( nHit==0 ) continue;
3243 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3244 zUrl, zCode);
3245 blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3246 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3247 zUrl, zCode);
 
 
 
 
3248 alert_send(pSender,&hdr,&body,0);
3249 nSent++;
3250 blob_truncate(&hdr, 0);
3251 blob_truncate(&body, 0);
3252 }
@@ -3288,18 +3299,28 @@
3288 " AND length(sdigest)>0",
3289 iNewWarn, iOldWarn
3290 );
3291 while( db_step(&q)==SQLITE_ROW ){
3292 Blob hdr, body;
 
3293 blob_init(&hdr, 0, 0);
3294 blob_init(&body, 0, 0);
3295 alert_renewal_msg(&hdr, &body,
3296 db_column_text(&q,0),
3297 db_column_int(&q,1),
3298 db_column_text(&q,2),
3299 db_column_text(&q,3),
3300 zRepoName, zUrl);
 
 
 
 
 
 
 
 
 
3301 alert_send(pSender,&hdr,&body,0);
3302 blob_reset(&hdr);
3303 blob_reset(&body);
3304 }
3305 db_finalize(&q);
3306
--- src/alerts.c
+++ src/alerts.c
@@ -655,11 +655,10 @@
655 emailerError(p, "Could not start SMTP session: %s",
656 p->pSmtp ? p->pSmtp->zErr : "reason unknown");
657 }else if( p->zDest[0]=='d' ){
658 smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out);
659 }
 
660 }
661 }
662 return p;
663 }
664
@@ -985,13 +984,10 @@
984 blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
985 }else{
986 blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
987 }
988 blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
 
 
 
989 if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
990 /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
991 ** the current unix-time in hex, $(random) is a 64-bit random number,
992 ** and $(from) is the domain part of the email-self setting. */
993 sqlite3_randomness(sizeof(r1), &r1);
@@ -1033,13 +1029,21 @@
1029 blob_write_to_file(&all, zFile);
1030 fossil_free(zFile);
1031 }else if( p->pSmtp ){
1032 char **azTo = 0;
1033 int nTo = 0;
1034 SmtpSession *pSmtp = p->pSmtp;
1035 email_header_to(pHdr, &nTo, &azTo);
1036 if( nTo>0 && !pSmtp->bFatal ){
1037 smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
1038 if( pSmtp->zErr && !pSmtp->bFatal ){
1039 smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
1040 }
1041 if( pSmtp->zErr ){
1042 fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry",
1043 pSmtp->zErr);
1044 }
1045 email_header_to_free(nTo, azTo);
1046 }
1047 }else if( strcmp(p->zDest, "stdout")==0 ){
1048 char **azTo = 0;
1049 int nTo = 0;
@@ -3208,18 +3212,21 @@
3212 Blob fhdr, fbody;
3213 blob_init(&fhdr, 0, 0);
3214 blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
3215 blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
3216 blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
3217 if( pSender->zListId && pSender->zListId[0] ){
3218 blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
3219 blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3220 zUrl, zCode);
3221 blob_appendf(&fhdr,
3222 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3223 blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3224 zUrl, zCode);
3225 /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
3226 ** zUrl, zCode); */
3227 }
3228 alert_send(pSender,&fhdr,&fbody,p->zFromName);
3229 nSent++;
3230 blob_reset(&fhdr);
3231 blob_reset(&fbody);
3232 }else{
@@ -3238,15 +3245,19 @@
3245 blob_append(&body, "\n", 1);
3246 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
3247 }
3248 }
3249 if( nHit==0 ) continue;
3250 if( pSender->zListId && pSender->zListId[0] ){
3251 blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3252 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3253 zUrl, zCode);
3254 blob_appendf(&hdr,
3255 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3256 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
3257 zUrl, zCode);
3258 }
3259 alert_send(pSender,&hdr,&body,0);
3260 nSent++;
3261 blob_truncate(&hdr, 0);
3262 blob_truncate(&body, 0);
3263 }
@@ -3288,18 +3299,28 @@
3299 " AND length(sdigest)>0",
3300 iNewWarn, iOldWarn
3301 );
3302 while( db_step(&q)==SQLITE_ROW ){
3303 Blob hdr, body;
3304 const char *zCode = db_column_text(&q,0);
3305 blob_init(&hdr, 0, 0);
3306 blob_init(&body, 0, 0);
3307 alert_renewal_msg(&hdr, &body,
3308 zCode,
3309 db_column_int(&q,1),
3310 db_column_text(&q,2),
3311 db_column_text(&q,3),
3312 zRepoName, zUrl);
3313 if( pSender->zListId && pSender->zListId[0] ){
3314 blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
3315 blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
3316 zUrl, zCode);
3317 blob_appendf(&hdr,
3318 "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
3319 blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
3320 zUrl, zCode);
3321 }
3322 alert_send(pSender,&hdr,&body,0);
3323 blob_reset(&hdr);
3324 blob_reset(&body);
3325 }
3326 db_finalize(&q);
3327
+2 -2
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205205
linkTip = rid != symbolic_name_to_rid("tip", "ci");
206206
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207207
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208208
isBranchCI = branch_includes_uuid(zCI, zUuid);
209209
if( bDocDir ) zCI = mprintf("%S", zUuid);
210
- Th_Store("current_checkin", zCI);
210
+ Th_StoreUnsafe("current_checkin", zCI);
211211
}else{
212212
zCI = 0;
213213
}
214214
}
215215
@@ -771,11 +771,11 @@
771771
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772772
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773773
" FROM event WHERE objid=%d", rid);
774774
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775775
isBranchCI = branch_includes_uuid(zCI, zUuid);
776
- Th_Store("current_checkin", zCI);
776
+ Th_StoreUnsafe("current_checkin", zCI);
777777
}else{
778778
zCI = 0;
779779
}
780780
}
781781
if( zCI==0 ){
782782
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205 linkTip = rid != symbolic_name_to_rid("tip", "ci");
206 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208 isBranchCI = branch_includes_uuid(zCI, zUuid);
209 if( bDocDir ) zCI = mprintf("%S", zUuid);
210 Th_Store("current_checkin", zCI);
211 }else{
212 zCI = 0;
213 }
214 }
215
@@ -771,11 +771,11 @@
771 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772 zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773 " FROM event WHERE objid=%d", rid);
774 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775 isBranchCI = branch_includes_uuid(zCI, zUuid);
776 Th_Store("current_checkin", zCI);
777 }else{
778 zCI = 0;
779 }
780 }
781 if( zCI==0 ){
782
--- src/browse.c
+++ src/browse.c
@@ -205,11 +205,11 @@
205 linkTip = rid != symbolic_name_to_rid("tip", "ci");
206 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
207 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
208 isBranchCI = branch_includes_uuid(zCI, zUuid);
209 if( bDocDir ) zCI = mprintf("%S", zUuid);
210 Th_StoreUnsafe("current_checkin", zCI);
211 }else{
212 zCI = 0;
213 }
214 }
215
@@ -771,11 +771,11 @@
771 rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
772 zNow = db_text("", "SELECT datetime(mtime,toLocal())"
773 " FROM event WHERE objid=%d", rid);
774 isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
775 isBranchCI = branch_includes_uuid(zCI, zUuid);
776 Th_StoreUnsafe("current_checkin", zCI);
777 }else{
778 zCI = 0;
779 }
780 }
781 if( zCI==0 ){
782
+252 -140
--- src/cgi.c
+++ src/cgi.c
@@ -2498,11 +2498,10 @@
24982498
fossil_free(zToFree);
24992499
fgetc(g.httpIn); /* Read past the "," separating header from content */
25002500
cgi_init();
25012501
}
25022502
2503
-
25042503
#if INTERFACE
25052504
/*
25062505
** Bitmap values for the flags parameter to cgi_http_server().
25072506
*/
25082507
#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
@@ -2541,121 +2540,222 @@
25412540
){
25422541
#if defined(_WIN32)
25432542
/* Use win32_http_server() instead */
25442543
fossil_exit(1);
25452544
#else
2546
- int listener = -1; /* The server socket */
2547
- int connection; /* A socket for each individual connection */
2545
+ int listen4 = -1; /* Main socket; IPv4 or unix-domain */
2546
+ int listen6 = -1; /* Aux socket for corresponding IPv6 */
2547
+ int mxListen = -1; /* Maximum of listen4 and listen6 */
2548
+ int connection; /* An incoming connection */
25482549
int nRequest = 0; /* Number of requests handled so far */
25492550
fd_set readfds; /* Set of file descriptors for select() */
25502551
socklen_t lenaddr; /* Length of the inaddr structure */
25512552
int child; /* PID of the child process */
25522553
int nchildren = 0; /* Number of child processes */
25532554
struct timeval delay; /* How long to wait inside select() */
2554
- struct sockaddr_in6 inaddr; /* The socket address */
2555
+ struct sockaddr_in6 inaddr6; /* Address for IPv6 */
2556
+ struct sockaddr_in inaddr4; /* Address for IPv4 */
25552557
struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
25562558
int opt = 1; /* setsockopt flag */
25572559
int rc; /* Result code from system calls */
25582560
int iPort = mnPort; /* Port to try to use */
2559
-
2560
- while( iPort<=mxPort ){
2561
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2562
- /* Initialize a Unix socket named g.zSockName */
2563
- assert( g.zSockName!=0 );
2564
- memset(&uxaddr, 0, sizeof(uxaddr));
2565
- if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2566
- fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2567
- g.zSockName, (int)sizeof(uxaddr.sun_path));
2568
- }
2569
- if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2570
- if( !file_issocket(g.zSockName) ){
2571
- fossil_fatal("cannot name socket \"%s\" because another object"
2572
- " with that name already exists", g.zSockName);
2573
- }else{
2574
- unlink(g.zSockName);
2575
- }
2576
- }
2577
- uxaddr.sun_family = AF_UNIX;
2578
- strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2579
- listener = socket(AF_UNIX, SOCK_STREAM, 0);
2580
- if( listener<0 ){
2581
- fossil_fatal("unable to create a unix socket named %s",
2582
- g.zSockName);
2583
- }
2584
- /* Set the access permission for the new socket. Default to 0660.
2585
- ** But use an alternative specified by --socket-mode if available.
2586
- ** Do this before bind() to avoid a race condition. */
2587
- if( g.zSockMode ){
2588
- file_set_mode(g.zSockName, listener, g.zSockMode, 0);
2589
- }else{
2590
- file_set_mode(g.zSockName, listener, "0660", 1);
2591
- }
2592
- }else{
2593
- /* Initialize a TCP/IP socket on port iPort */
2594
- memset(&inaddr, 0, sizeof(inaddr));
2595
- inaddr.sin6_family = AF_INET6;
2596
- if( zIpAddr ){
2597
- if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){
2598
- fossil_fatal("not a valid IP address: %s", zIpAddr);
2599
- }
2600
- }else if( flags & HTTP_SERVER_LOCALHOST ){
2601
- inaddr.sin6_addr = in6addr_loopback;
2602
- }else{
2603
- inaddr.sin6_addr = in6addr_any;
2604
- }
2605
- inaddr.sin6_port = htons(iPort);
2606
- listener = socket(AF_INET6, SOCK_STREAM, 0);
2607
- if( listener<0 ){
2608
- iPort++;
2609
- continue;
2610
- }
2611
- }
2612
-
2613
- /* if we can't terminate nicely, at least allow the socket to be reused */
2614
- setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
2615
-
2616
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2617
- rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2618
- /* Set the owner of the socket if requested by --socket-owner. This
2619
- ** must wait until after bind(), after the filesystem object has been
2620
- ** created. See https://lkml.org/lkml/2004/11/1/84 and
2621
- ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2622
- if( g.zSockOwner ){
2623
- file_set_owner(g.zSockName, listener, g.zSockOwner);
2624
- }
2625
- }else{
2626
- rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));
2627
- }
2628
- if( rc<0 ){
2629
- close(listener);
2630
- iPort++;
2631
- continue;
2632
- }
2633
- break;
2634
- }
2635
- if( iPort>mxPort ){
2636
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2637
- fossil_fatal("unable to listen on unix socket %s", zIpAddr);
2638
- }else if( mnPort==mxPort ){
2639
- fossil_fatal("unable to open listening socket on port %d", mnPort);
2640
- }else{
2641
- fossil_fatal("unable to open listening socket on any"
2642
- " port in the range %d..%d", mnPort, mxPort);
2643
- }
2644
- }
2645
- if( iPort>mxPort ) return 1;
2646
- listen(listener,10);
2647
- if( flags & HTTP_SERVER_UNIXSOCKET ){
2648
- fossil_print("Listening for %s requests on unix socket %s\n",
2649
- (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2650
- g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName);
2651
- }else{
2652
- fossil_print("Listening for %s requests on TCP port %d\n",
2653
- (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2654
- g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort);
2655
- }
2656
- fflush(stdout);
2561
+ const char *zRequestType; /* Type of requests to listen for */
2562
+
2563
+
2564
+ if( flags & HTTP_SERVER_SCGI ){
2565
+ zRequestType = "SCGI";
2566
+ }else if( g.httpUseSSL ){
2567
+ zRequestType = "TLS-encrypted HTTPS";
2568
+ }else{
2569
+ zRequestType = "HTTP";
2570
+ }
2571
+
2572
+ if( flags & HTTP_SERVER_UNIXSOCKET ){
2573
+ /* CASE 1: A unix socket named g.zSockName. After creation, set the
2574
+ ** permissions on the new socket to g.zSockMode and make the
2575
+ ** owner of the socket be g.zSockOwner.
2576
+ */
2577
+ assert( g.zSockName!=0 );
2578
+ memset(&uxaddr, 0, sizeof(uxaddr));
2579
+ if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2580
+ fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2581
+ g.zSockName, (int)sizeof(uxaddr.sun_path));
2582
+ }
2583
+ if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2584
+ if( !file_issocket(g.zSockName) ){
2585
+ fossil_fatal("cannot name socket \"%s\" because another object"
2586
+ " with that name already exists", g.zSockName);
2587
+ }else{
2588
+ unlink(g.zSockName);
2589
+ }
2590
+ }
2591
+ uxaddr.sun_family = AF_UNIX;
2592
+ strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2593
+ listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
2594
+ if( listen4<0 ){
2595
+ fossil_fatal("unable to create a unix socket named %s",
2596
+ g.zSockName);
2597
+ }
2598
+ mxListen = listen4;
2599
+ listen6 = -1;
2600
+
2601
+ /* Set the access permission for the new socket. Default to 0660.
2602
+ ** But use an alternative specified by --socket-mode if available.
2603
+ ** Do this before bind() to avoid a race condition. */
2604
+ if( g.zSockMode ){
2605
+ file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
2606
+ }else{
2607
+ file_set_mode(g.zSockName, listen4, "0660", 1);
2608
+ }
2609
+ rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2610
+ /* Set the owner of the socket if requested by --socket-owner. This
2611
+ ** must wait until after bind(), after the filesystem object has been
2612
+ ** created. See https://lkml.org/lkml/2004/11/1/84 and
2613
+ ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2614
+ if( g.zSockOwner ){
2615
+ file_set_owner(g.zSockName, listen4, g.zSockOwner);
2616
+ }
2617
+ fossil_print("Listening for %s requests on unix socket %s\n",
2618
+ zRequestType, g.zSockName);
2619
+ fflush(stdout);
2620
+ }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
2621
+ /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
2622
+ */
2623
+ assert( mnPort==mxPort );
2624
+ memset(&inaddr6, 0, sizeof(inaddr6));
2625
+ inaddr6.sin6_family = AF_INET6;
2626
+ inaddr6.sin6_port = htons(iPort);
2627
+ if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
2628
+ fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2629
+ }
2630
+ listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2631
+ if( listen6>0 ){
2632
+ opt = 1;
2633
+ setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2634
+ rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2635
+ if( rc<0 ){
2636
+ close(listen6);
2637
+ listen6 = -1;
2638
+ }
2639
+ }
2640
+ if( listen6<0 ){
2641
+ fossil_fatal("cannot open a listening socket on [%s]:%d",
2642
+ zIpAddr, mnPort);
2643
+ }
2644
+ mxListen = listen6;
2645
+ listen4 = -1;
2646
+ fossil_print("Listening for %s requests on [%s]:%d\n",
2647
+ zRequestType, zIpAddr, iPort);
2648
+ fflush(stdout);
2649
+ }else if( zIpAddr && zIpAddr[0] ){
2650
+ /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
2651
+ */
2652
+ assert( mnPort==mxPort );
2653
+ memset(&inaddr4, 0, sizeof(inaddr4));
2654
+ inaddr4.sin_family = AF_INET;
2655
+ inaddr4.sin_port = htons(iPort);
2656
+ if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
2657
+ inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2658
+ if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2659
+ fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2660
+ }
2661
+ listen4 = socket(AF_INET, SOCK_STREAM, 0);
2662
+ if( listen4>0 ){
2663
+ setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2664
+ rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2665
+ if( rc<0 ){
2666
+ close(listen4);
2667
+ listen4 = -1;
2668
+ }
2669
+ }
2670
+ if( listen4<0 ){
2671
+ fossil_fatal("cannot open a listening socket on %s:%d",
2672
+ zIpAddr, mnPort);
2673
+ }
2674
+ mxListen = listen4;
2675
+ listen6 = -1;
2676
+ fossil_print("Listening for %s requests on TCP port %s:%d\n",
2677
+ zRequestType, zIpAddr, iPort);
2678
+ fflush(stdout);
2679
+ }else{
2680
+ /* CASE 4: Listen on all available IP addresses, or on only loopback
2681
+ ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
2682
+ ** first available in the range of mnPort..mxPort. Listen
2683
+ ** on both IPv4 and IPv6, if possible. The TCP port scan is done
2684
+ ** on IPv4.
2685
+ */
2686
+ while( iPort<=mxPort ){
2687
+ const char *zProto;
2688
+ memset(&inaddr4, 0, sizeof(inaddr4));
2689
+ inaddr4.sin_family = AF_INET;
2690
+ inaddr4.sin_port = htons(iPort);
2691
+ if( flags & HTTP_SERVER_LOCALHOST ){
2692
+ inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2693
+ }else{
2694
+ inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
2695
+ }
2696
+ listen4 = socket(AF_INET, SOCK_STREAM, 0);
2697
+ if( listen4>0 ){
2698
+ setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2699
+ rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2700
+ if( rc<0 ){
2701
+ close(listen4);
2702
+ listen4 = -1;
2703
+ }
2704
+ }
2705
+ if( listen4<0 ){
2706
+ iPort++;
2707
+ continue;
2708
+ }
2709
+ mxListen = listen4;
2710
+
2711
+ /* If we get here, that means we found an open TCP port at iPort for
2712
+ ** IPv4. Try to set up a corresponding IPv6 socket on the same port.
2713
+ */
2714
+ memset(&inaddr6, 0, sizeof(inaddr6));
2715
+ inaddr6.sin6_family = AF_INET6;
2716
+ inaddr6.sin6_port = htons(iPort);
2717
+ if( flags & HTTP_SERVER_LOCALHOST ){
2718
+ inaddr6.sin6_addr = in6addr_loopback;
2719
+ }else{
2720
+ inaddr6.sin6_addr = in6addr_any;
2721
+ }
2722
+ listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2723
+ if( listen6>0 ){
2724
+ setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2725
+ setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
2726
+ rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2727
+ if( rc<0 ){
2728
+ close(listen6);
2729
+ listen6 = -1;
2730
+ }
2731
+ }
2732
+ if( listen6<0 ){
2733
+ zProto = "IPv4 only";
2734
+ }else{
2735
+ zProto = "IPv4 and IPv6";
2736
+ if( listen6>listen4 ) mxListen = listen6;
2737
+ }
2738
+
2739
+ fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
2740
+ zRequestType,
2741
+ (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
2742
+ iPort, zProto);
2743
+ fflush(stdout);
2744
+ break;
2745
+ }
2746
+ if( iPort>mxPort ){
2747
+ fossil_fatal("no available TCP ports in the range %d..%d",
2748
+ mnPort, mxPort);
2749
+ }
2750
+ }
2751
+
2752
+ /* If we get to this point, that means there is at least one listening
2753
+ ** socket on either listen4 or listen6 and perhaps on both. */
2754
+ assert( listen4>0 || listen6>0 );
2755
+ if( listen4>0 ) listen(listen4,10);
2756
+ if( listen6>0 ) listen(listen6,10);
26572757
if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
26582758
assert( strstr(zBrowser,"%d")!=0 );
26592759
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
26602760
#if defined(__CYGWIN__)
26612761
/* On Cygwin, we can do better than "echo" */
@@ -2669,57 +2769,69 @@
26692769
#endif
26702770
if( fossil_system(zBrowser)<0 ){
26712771
fossil_warning("cannot start browser: %s\n", zBrowser);
26722772
}
26732773
}
2774
+
2775
+ /* What for incomming requests. For each request, fork() a child process
2776
+ ** to deal with that request. The child process returns. The parent
2777
+ ** keeps on listening and never returns.
2778
+ */
26742779
while( 1 ){
26752780
#if FOSSIL_MAX_CONNECTIONS>0
26762781
while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
26772782
if( wait(0)>=0 ) nchildren--;
26782783
}
26792784
#endif
26802785
delay.tv_sec = 0;
26812786
delay.tv_usec = 100000;
26822787
FD_ZERO(&readfds);
2683
- assert( listener>=0 );
2684
- FD_SET( listener, &readfds);
2685
- select( listener+1, &readfds, 0, 0, &delay);
2686
- if( FD_ISSET(listener, &readfds) ){
2687
- lenaddr = sizeof(inaddr);
2688
- connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
2689
- if( connection>=0 ){
2690
- if( flags & HTTP_SERVER_NOFORK ){
2691
- child = 0;
2692
- }else{
2693
- child = fork();
2694
- }
2695
- if( child!=0 ){
2696
- if( child>0 ){
2697
- nchildren++;
2698
- nRequest++;
2699
- }
2700
- close(connection);
2701
- }else{
2702
- int nErr = 0, fd;
2703
- g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2704
- close(0);
2705
- fd = dup(connection);
2706
- if( fd!=0 ) nErr++;
2707
- close(1);
2708
- fd = dup(connection);
2709
- if( fd!=1 ) nErr++;
2710
- if( 0 && !g.fAnyTrace ){
2711
- close(2);
2712
- fd = dup(connection);
2713
- if( fd!=2 ) nErr++;
2714
- }
2715
- close(connection);
2716
- close(listener);
2717
- g.nPendingRequest = nchildren+1;
2718
- g.nRequest = nRequest+1;
2719
- return nErr;
2720
- }
2788
+ assert( listen4>0 || listen6>0 );
2789
+ if( listen4>0 ) FD_SET( listen4, &readfds);
2790
+ if( listen6>0 ) FD_SET( listen6, &readfds);
2791
+ select( mxListen+1, &readfds, 0, 0, &delay);
2792
+ if( listen4>0 && FD_ISSET(listen4, &readfds) ){
2793
+ lenaddr = sizeof(inaddr4);
2794
+ connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
2795
+ }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
2796
+ lenaddr = sizeof(inaddr6);
2797
+ connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
2798
+ }else{
2799
+ connection = -1;
2800
+ }
2801
+ if( connection>=0 ){
2802
+ if( flags & HTTP_SERVER_NOFORK ){
2803
+ child = 0;
2804
+ }else{
2805
+ child = fork();
2806
+ }
2807
+ if( child!=0 ){
2808
+ if( child>0 ){
2809
+ nchildren++;
2810
+ nRequest++;
2811
+ }
2812
+ close(connection);
2813
+ }else{
2814
+ int nErr = 0, fd;
2815
+ g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2816
+ close(0);
2817
+ fd = dup(connection);
2818
+ if( fd!=0 ) nErr++;
2819
+ close(1);
2820
+ fd = dup(connection);
2821
+ if( fd!=1 ) nErr++;
2822
+ if( 0 && !g.fAnyTrace ){
2823
+ close(2);
2824
+ fd = dup(connection);
2825
+ if( fd!=2 ) nErr++;
2826
+ }
2827
+ close(connection);
2828
+ if( listen4>0 ) close(listen4);
2829
+ if( listen6>0 ) close(listen6);
2830
+ g.nPendingRequest = nchildren+1;
2831
+ g.nRequest = nRequest+1;
2832
+ return nErr;
27212833
}
27222834
}
27232835
/* Bury dead children */
27242836
if( nchildren ){
27252837
while(1){
27262838
--- src/cgi.c
+++ src/cgi.c
@@ -2498,11 +2498,10 @@
2498 fossil_free(zToFree);
2499 fgetc(g.httpIn); /* Read past the "," separating header from content */
2500 cgi_init();
2501 }
2502
2503
2504 #if INTERFACE
2505 /*
2506 ** Bitmap values for the flags parameter to cgi_http_server().
2507 */
2508 #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
@@ -2541,121 +2540,222 @@
2541 ){
2542 #if defined(_WIN32)
2543 /* Use win32_http_server() instead */
2544 fossil_exit(1);
2545 #else
2546 int listener = -1; /* The server socket */
2547 int connection; /* A socket for each individual connection */
 
 
2548 int nRequest = 0; /* Number of requests handled so far */
2549 fd_set readfds; /* Set of file descriptors for select() */
2550 socklen_t lenaddr; /* Length of the inaddr structure */
2551 int child; /* PID of the child process */
2552 int nchildren = 0; /* Number of child processes */
2553 struct timeval delay; /* How long to wait inside select() */
2554 struct sockaddr_in6 inaddr; /* The socket address */
 
2555 struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
2556 int opt = 1; /* setsockopt flag */
2557 int rc; /* Result code from system calls */
2558 int iPort = mnPort; /* Port to try to use */
2559
2560 while( iPort<=mxPort ){
2561 if( flags & HTTP_SERVER_UNIXSOCKET ){
2562 /* Initialize a Unix socket named g.zSockName */
2563 assert( g.zSockName!=0 );
2564 memset(&uxaddr, 0, sizeof(uxaddr));
2565 if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2566 fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2567 g.zSockName, (int)sizeof(uxaddr.sun_path));
2568 }
2569 if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2570 if( !file_issocket(g.zSockName) ){
2571 fossil_fatal("cannot name socket \"%s\" because another object"
2572 " with that name already exists", g.zSockName);
2573 }else{
2574 unlink(g.zSockName);
2575 }
2576 }
2577 uxaddr.sun_family = AF_UNIX;
2578 strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2579 listener = socket(AF_UNIX, SOCK_STREAM, 0);
2580 if( listener<0 ){
2581 fossil_fatal("unable to create a unix socket named %s",
2582 g.zSockName);
2583 }
2584 /* Set the access permission for the new socket. Default to 0660.
2585 ** But use an alternative specified by --socket-mode if available.
2586 ** Do this before bind() to avoid a race condition. */
2587 if( g.zSockMode ){
2588 file_set_mode(g.zSockName, listener, g.zSockMode, 0);
2589 }else{
2590 file_set_mode(g.zSockName, listener, "0660", 1);
2591 }
2592 }else{
2593 /* Initialize a TCP/IP socket on port iPort */
2594 memset(&inaddr, 0, sizeof(inaddr));
2595 inaddr.sin6_family = AF_INET6;
2596 if( zIpAddr ){
2597 if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){
2598 fossil_fatal("not a valid IP address: %s", zIpAddr);
2599 }
2600 }else if( flags & HTTP_SERVER_LOCALHOST ){
2601 inaddr.sin6_addr = in6addr_loopback;
2602 }else{
2603 inaddr.sin6_addr = in6addr_any;
2604 }
2605 inaddr.sin6_port = htons(iPort);
2606 listener = socket(AF_INET6, SOCK_STREAM, 0);
2607 if( listener<0 ){
2608 iPort++;
2609 continue;
2610 }
2611 }
2612
2613 /* if we can't terminate nicely, at least allow the socket to be reused */
2614 setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
2615
2616 if( flags & HTTP_SERVER_UNIXSOCKET ){
2617 rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2618 /* Set the owner of the socket if requested by --socket-owner. This
2619 ** must wait until after bind(), after the filesystem object has been
2620 ** created. See https://lkml.org/lkml/2004/11/1/84 and
2621 ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2622 if( g.zSockOwner ){
2623 file_set_owner(g.zSockName, listener, g.zSockOwner);
2624 }
2625 }else{
2626 rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));
2627 }
2628 if( rc<0 ){
2629 close(listener);
2630 iPort++;
2631 continue;
2632 }
2633 break;
2634 }
2635 if( iPort>mxPort ){
2636 if( flags & HTTP_SERVER_UNIXSOCKET ){
2637 fossil_fatal("unable to listen on unix socket %s", zIpAddr);
2638 }else if( mnPort==mxPort ){
2639 fossil_fatal("unable to open listening socket on port %d", mnPort);
2640 }else{
2641 fossil_fatal("unable to open listening socket on any"
2642 " port in the range %d..%d", mnPort, mxPort);
2643 }
2644 }
2645 if( iPort>mxPort ) return 1;
2646 listen(listener,10);
2647 if( flags & HTTP_SERVER_UNIXSOCKET ){
2648 fossil_print("Listening for %s requests on unix socket %s\n",
2649 (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2650 g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName);
2651 }else{
2652 fossil_print("Listening for %s requests on TCP port %d\n",
2653 (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
2654 g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort);
2655 }
2656 fflush(stdout);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2657 if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
2658 assert( strstr(zBrowser,"%d")!=0 );
2659 zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2660 #if defined(__CYGWIN__)
2661 /* On Cygwin, we can do better than "echo" */
@@ -2669,57 +2769,69 @@
2669 #endif
2670 if( fossil_system(zBrowser)<0 ){
2671 fossil_warning("cannot start browser: %s\n", zBrowser);
2672 }
2673 }
 
 
 
 
 
2674 while( 1 ){
2675 #if FOSSIL_MAX_CONNECTIONS>0
2676 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2677 if( wait(0)>=0 ) nchildren--;
2678 }
2679 #endif
2680 delay.tv_sec = 0;
2681 delay.tv_usec = 100000;
2682 FD_ZERO(&readfds);
2683 assert( listener>=0 );
2684 FD_SET( listener, &readfds);
2685 select( listener+1, &readfds, 0, 0, &delay);
2686 if( FD_ISSET(listener, &readfds) ){
2687 lenaddr = sizeof(inaddr);
2688 connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
2689 if( connection>=0 ){
2690 if( flags & HTTP_SERVER_NOFORK ){
2691 child = 0;
2692 }else{
2693 child = fork();
2694 }
2695 if( child!=0 ){
2696 if( child>0 ){
2697 nchildren++;
2698 nRequest++;
2699 }
2700 close(connection);
2701 }else{
2702 int nErr = 0, fd;
2703 g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2704 close(0);
2705 fd = dup(connection);
2706 if( fd!=0 ) nErr++;
2707 close(1);
2708 fd = dup(connection);
2709 if( fd!=1 ) nErr++;
2710 if( 0 && !g.fAnyTrace ){
2711 close(2);
2712 fd = dup(connection);
2713 if( fd!=2 ) nErr++;
2714 }
2715 close(connection);
2716 close(listener);
2717 g.nPendingRequest = nchildren+1;
2718 g.nRequest = nRequest+1;
2719 return nErr;
2720 }
 
 
 
 
 
 
 
2721 }
2722 }
2723 /* Bury dead children */
2724 if( nchildren ){
2725 while(1){
2726
--- src/cgi.c
+++ src/cgi.c
@@ -2498,11 +2498,10 @@
2498 fossil_free(zToFree);
2499 fgetc(g.httpIn); /* Read past the "," separating header from content */
2500 cgi_init();
2501 }
2502
 
2503 #if INTERFACE
2504 /*
2505 ** Bitmap values for the flags parameter to cgi_http_server().
2506 */
2507 #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
@@ -2541,121 +2540,222 @@
2540 ){
2541 #if defined(_WIN32)
2542 /* Use win32_http_server() instead */
2543 fossil_exit(1);
2544 #else
2545 int listen4 = -1; /* Main socket; IPv4 or unix-domain */
2546 int listen6 = -1; /* Aux socket for corresponding IPv6 */
2547 int mxListen = -1; /* Maximum of listen4 and listen6 */
2548 int connection; /* An incoming connection */
2549 int nRequest = 0; /* Number of requests handled so far */
2550 fd_set readfds; /* Set of file descriptors for select() */
2551 socklen_t lenaddr; /* Length of the inaddr structure */
2552 int child; /* PID of the child process */
2553 int nchildren = 0; /* Number of child processes */
2554 struct timeval delay; /* How long to wait inside select() */
2555 struct sockaddr_in6 inaddr6; /* Address for IPv6 */
2556 struct sockaddr_in inaddr4; /* Address for IPv4 */
2557 struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
2558 int opt = 1; /* setsockopt flag */
2559 int rc; /* Result code from system calls */
2560 int iPort = mnPort; /* Port to try to use */
2561 const char *zRequestType; /* Type of requests to listen for */
2562
2563
2564 if( flags & HTTP_SERVER_SCGI ){
2565 zRequestType = "SCGI";
2566 }else if( g.httpUseSSL ){
2567 zRequestType = "TLS-encrypted HTTPS";
2568 }else{
2569 zRequestType = "HTTP";
2570 }
2571
2572 if( flags & HTTP_SERVER_UNIXSOCKET ){
2573 /* CASE 1: A unix socket named g.zSockName. After creation, set the
2574 ** permissions on the new socket to g.zSockMode and make the
2575 ** owner of the socket be g.zSockOwner.
2576 */
2577 assert( g.zSockName!=0 );
2578 memset(&uxaddr, 0, sizeof(uxaddr));
2579 if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2580 fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2581 g.zSockName, (int)sizeof(uxaddr.sun_path));
2582 }
2583 if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2584 if( !file_issocket(g.zSockName) ){
2585 fossil_fatal("cannot name socket \"%s\" because another object"
2586 " with that name already exists", g.zSockName);
2587 }else{
2588 unlink(g.zSockName);
2589 }
2590 }
2591 uxaddr.sun_family = AF_UNIX;
2592 strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2593 listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
2594 if( listen4<0 ){
2595 fossil_fatal("unable to create a unix socket named %s",
2596 g.zSockName);
2597 }
2598 mxListen = listen4;
2599 listen6 = -1;
2600
2601 /* Set the access permission for the new socket. Default to 0660.
2602 ** But use an alternative specified by --socket-mode if available.
2603 ** Do this before bind() to avoid a race condition. */
2604 if( g.zSockMode ){
2605 file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
2606 }else{
2607 file_set_mode(g.zSockName, listen4, "0660", 1);
2608 }
2609 rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2610 /* Set the owner of the socket if requested by --socket-owner. This
2611 ** must wait until after bind(), after the filesystem object has been
2612 ** created. See https://lkml.org/lkml/2004/11/1/84 and
2613 ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2614 if( g.zSockOwner ){
2615 file_set_owner(g.zSockName, listen4, g.zSockOwner);
2616 }
2617 fossil_print("Listening for %s requests on unix socket %s\n",
2618 zRequestType, g.zSockName);
2619 fflush(stdout);
2620 }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
2621 /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
2622 */
2623 assert( mnPort==mxPort );
2624 memset(&inaddr6, 0, sizeof(inaddr6));
2625 inaddr6.sin6_family = AF_INET6;
2626 inaddr6.sin6_port = htons(iPort);
2627 if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
2628 fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2629 }
2630 listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2631 if( listen6>0 ){
2632 opt = 1;
2633 setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2634 rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2635 if( rc<0 ){
2636 close(listen6);
2637 listen6 = -1;
2638 }
2639 }
2640 if( listen6<0 ){
2641 fossil_fatal("cannot open a listening socket on [%s]:%d",
2642 zIpAddr, mnPort);
2643 }
2644 mxListen = listen6;
2645 listen4 = -1;
2646 fossil_print("Listening for %s requests on [%s]:%d\n",
2647 zRequestType, zIpAddr, iPort);
2648 fflush(stdout);
2649 }else if( zIpAddr && zIpAddr[0] ){
2650 /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
2651 */
2652 assert( mnPort==mxPort );
2653 memset(&inaddr4, 0, sizeof(inaddr4));
2654 inaddr4.sin_family = AF_INET;
2655 inaddr4.sin_port = htons(iPort);
2656 if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
2657 inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2658 if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2659 fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2660 }
2661 listen4 = socket(AF_INET, SOCK_STREAM, 0);
2662 if( listen4>0 ){
2663 setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2664 rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2665 if( rc<0 ){
2666 close(listen4);
2667 listen4 = -1;
2668 }
2669 }
2670 if( listen4<0 ){
2671 fossil_fatal("cannot open a listening socket on %s:%d",
2672 zIpAddr, mnPort);
2673 }
2674 mxListen = listen4;
2675 listen6 = -1;
2676 fossil_print("Listening for %s requests on TCP port %s:%d\n",
2677 zRequestType, zIpAddr, iPort);
2678 fflush(stdout);
2679 }else{
2680 /* CASE 4: Listen on all available IP addresses, or on only loopback
2681 ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
2682 ** first available in the range of mnPort..mxPort. Listen
2683 ** on both IPv4 and IPv6, if possible. The TCP port scan is done
2684 ** on IPv4.
2685 */
2686 while( iPort<=mxPort ){
2687 const char *zProto;
2688 memset(&inaddr4, 0, sizeof(inaddr4));
2689 inaddr4.sin_family = AF_INET;
2690 inaddr4.sin_port = htons(iPort);
2691 if( flags & HTTP_SERVER_LOCALHOST ){
2692 inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2693 }else{
2694 inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
2695 }
2696 listen4 = socket(AF_INET, SOCK_STREAM, 0);
2697 if( listen4>0 ){
2698 setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2699 rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2700 if( rc<0 ){
2701 close(listen4);
2702 listen4 = -1;
2703 }
2704 }
2705 if( listen4<0 ){
2706 iPort++;
2707 continue;
2708 }
2709 mxListen = listen4;
2710
2711 /* If we get here, that means we found an open TCP port at iPort for
2712 ** IPv4. Try to set up a corresponding IPv6 socket on the same port.
2713 */
2714 memset(&inaddr6, 0, sizeof(inaddr6));
2715 inaddr6.sin6_family = AF_INET6;
2716 inaddr6.sin6_port = htons(iPort);
2717 if( flags & HTTP_SERVER_LOCALHOST ){
2718 inaddr6.sin6_addr = in6addr_loopback;
2719 }else{
2720 inaddr6.sin6_addr = in6addr_any;
2721 }
2722 listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2723 if( listen6>0 ){
2724 setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2725 setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
2726 rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2727 if( rc<0 ){
2728 close(listen6);
2729 listen6 = -1;
2730 }
2731 }
2732 if( listen6<0 ){
2733 zProto = "IPv4 only";
2734 }else{
2735 zProto = "IPv4 and IPv6";
2736 if( listen6>listen4 ) mxListen = listen6;
2737 }
2738
2739 fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
2740 zRequestType,
2741 (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
2742 iPort, zProto);
2743 fflush(stdout);
2744 break;
2745 }
2746 if( iPort>mxPort ){
2747 fossil_fatal("no available TCP ports in the range %d..%d",
2748 mnPort, mxPort);
2749 }
2750 }
2751
2752 /* If we get to this point, that means there is at least one listening
2753 ** socket on either listen4 or listen6 and perhaps on both. */
2754 assert( listen4>0 || listen6>0 );
2755 if( listen4>0 ) listen(listen4,10);
2756 if( listen6>0 ) listen(listen6,10);
2757 if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
2758 assert( strstr(zBrowser,"%d")!=0 );
2759 zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2760 #if defined(__CYGWIN__)
2761 /* On Cygwin, we can do better than "echo" */
@@ -2669,57 +2769,69 @@
2769 #endif
2770 if( fossil_system(zBrowser)<0 ){
2771 fossil_warning("cannot start browser: %s\n", zBrowser);
2772 }
2773 }
2774
2775 /* What for incomming requests. For each request, fork() a child process
2776 ** to deal with that request. The child process returns. The parent
2777 ** keeps on listening and never returns.
2778 */
2779 while( 1 ){
2780 #if FOSSIL_MAX_CONNECTIONS>0
2781 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2782 if( wait(0)>=0 ) nchildren--;
2783 }
2784 #endif
2785 delay.tv_sec = 0;
2786 delay.tv_usec = 100000;
2787 FD_ZERO(&readfds);
2788 assert( listen4>0 || listen6>0 );
2789 if( listen4>0 ) FD_SET( listen4, &readfds);
2790 if( listen6>0 ) FD_SET( listen6, &readfds);
2791 select( mxListen+1, &readfds, 0, 0, &delay);
2792 if( listen4>0 && FD_ISSET(listen4, &readfds) ){
2793 lenaddr = sizeof(inaddr4);
2794 connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
2795 }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
2796 lenaddr = sizeof(inaddr6);
2797 connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
2798 }else{
2799 connection = -1;
2800 }
2801 if( connection>=0 ){
2802 if( flags & HTTP_SERVER_NOFORK ){
2803 child = 0;
2804 }else{
2805 child = fork();
2806 }
2807 if( child!=0 ){
2808 if( child>0 ){
2809 nchildren++;
2810 nRequest++;
2811 }
2812 close(connection);
2813 }else{
2814 int nErr = 0, fd;
2815 g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2816 close(0);
2817 fd = dup(connection);
2818 if( fd!=0 ) nErr++;
2819 close(1);
2820 fd = dup(connection);
2821 if( fd!=1 ) nErr++;
2822 if( 0 && !g.fAnyTrace ){
2823 close(2);
2824 fd = dup(connection);
2825 if( fd!=2 ) nErr++;
2826 }
2827 close(connection);
2828 if( listen4>0 ) close(listen4);
2829 if( listen6>0 ) close(listen6);
2830 g.nPendingRequest = nchildren+1;
2831 g.nRequest = nRequest+1;
2832 return nErr;
2833 }
2834 }
2835 /* Bury dead children */
2836 if( nchildren ){
2837 while(1){
2838
--- src/checkin.c
+++ src/checkin.c
@@ -2444,10 +2444,11 @@
24442444
** --close Close the branch being committed
24452445
** --date-override DATETIME Make DATETIME the time of the check-in.
24462446
** Useful when importing historical check-ins
24472447
** from another version control system.
24482448
** --delta Use a delta manifest in the commit process
2449
+** --editor NAME Text editor to use for check-in comment.
24492450
** --hash Verify file status using hashing rather
24502451
** than relying on filesystem mtimes
24512452
** --if-changes Make this command a silent no-op if there
24522453
** are no changes
24532454
** --ignore-clock-skew If a clock skew is detected, ignore it and
@@ -2599,10 +2600,11 @@
25992600
useCksum = db_get_boolean("repo-cksum", 1);
26002601
bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
26012602
outputManifest = db_get_manifest_setting(0);
26022603
mxSize = db_large_file_size();
26032604
if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
2605
+ (void)fossil_text_editor();
26042606
verify_all_options();
26052607
26062608
/* The --no-warnings flag and the --force flag each imply
26072609
** the --no-verify-comment flag */
26082610
if( noWarningFlag || forceFlag ){
26092611
--- src/checkin.c
+++ src/checkin.c
@@ -2444,10 +2444,11 @@
2444 ** --close Close the branch being committed
2445 ** --date-override DATETIME Make DATETIME the time of the check-in.
2446 ** Useful when importing historical check-ins
2447 ** from another version control system.
2448 ** --delta Use a delta manifest in the commit process
 
2449 ** --hash Verify file status using hashing rather
2450 ** than relying on filesystem mtimes
2451 ** --if-changes Make this command a silent no-op if there
2452 ** are no changes
2453 ** --ignore-clock-skew If a clock skew is detected, ignore it and
@@ -2599,10 +2600,11 @@
2599 useCksum = db_get_boolean("repo-cksum", 1);
2600 bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
2601 outputManifest = db_get_manifest_setting(0);
2602 mxSize = db_large_file_size();
2603 if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
 
2604 verify_all_options();
2605
2606 /* The --no-warnings flag and the --force flag each imply
2607 ** the --no-verify-comment flag */
2608 if( noWarningFlag || forceFlag ){
2609
--- src/checkin.c
+++ src/checkin.c
@@ -2444,10 +2444,11 @@
2444 ** --close Close the branch being committed
2445 ** --date-override DATETIME Make DATETIME the time of the check-in.
2446 ** Useful when importing historical check-ins
2447 ** from another version control system.
2448 ** --delta Use a delta manifest in the commit process
2449 ** --editor NAME Text editor to use for check-in comment.
2450 ** --hash Verify file status using hashing rather
2451 ** than relying on filesystem mtimes
2452 ** --if-changes Make this command a silent no-op if there
2453 ** are no changes
2454 ** --ignore-clock-skew If a clock skew is detected, ignore it and
@@ -2599,10 +2600,11 @@
2600 useCksum = db_get_boolean("repo-cksum", 1);
2601 bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
2602 outputManifest = db_get_manifest_setting(0);
2603 mxSize = db_large_file_size();
2604 if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
2605 (void)fossil_text_editor();
2606 verify_all_options();
2607
2608 /* The --no-warnings flag and the --force flag each imply
2609 ** the --no-verify-comment flag */
2610 if( noWarningFlag || forceFlag ){
2611
+4 -2
--- src/clone.c
+++ src/clone.c
@@ -430,12 +430,14 @@
430430
const char *zNm = db_get("short-project-name","download");
431431
char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm);
432432
@ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a>
433433
zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm);
434434
@ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a>
435
- zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm);
436
- @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a>
435
+ if( g.zLogin!=0 ){
436
+ zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm);
437
+ @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a>
438
+ }
437439
}
438440
if( !g.perm.Clone ){
439441
@ <p>You are not authorized to clone this repository.
440442
if( g.zLogin==0 || g.zLogin[0]==0 ){
441443
@ Maybe you would be able to clone if you
442444
--- src/clone.c
+++ src/clone.c
@@ -430,12 +430,14 @@
430 const char *zNm = db_get("short-project-name","download");
431 char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm);
432 @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a>
433 zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm);
434 @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a>
435 zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm);
436 @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a>
 
 
437 }
438 if( !g.perm.Clone ){
439 @ <p>You are not authorized to clone this repository.
440 if( g.zLogin==0 || g.zLogin[0]==0 ){
441 @ Maybe you would be able to clone if you
442
--- src/clone.c
+++ src/clone.c
@@ -430,12 +430,14 @@
430 const char *zNm = db_get("short-project-name","download");
431 char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm);
432 @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a>
433 zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm);
434 @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a>
435 if( g.zLogin!=0 ){
436 zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm);
437 @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a>
438 }
439 }
440 if( !g.perm.Clone ){
441 @ <p>You are not authorized to clone this repository.
442 if( g.zLogin==0 || g.zLogin[0]==0 ){
443 @ Maybe you would be able to clone if you
444
+2 -2
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
33693369
if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
33703370
if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
33713371
fossil_print("project-id: %s\n", db_get("project-code", 0));
33723372
fossil_print("server-id: %s\n", db_get("server-code", 0));
33733373
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374
- fossil_print("admin-user: %s (initial password is \"%s\")\n",
3374
+ fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
33753375
g.zLogin, zPassword);
33763376
hash_user_password(g.zLogin);
33773377
}
33783378
33793379
/*
@@ -4758,11 +4758,11 @@
47584758
** SETTING: comment-format width=16 default=1
47594759
** Set the algorithm for printing timeline comments to the console.
47604760
**
47614761
** Possible values are:
47624762
** 1 Use the original comment printing algorithm:
4763
-** * Leading and trialing whitespace is removed
4763
+** * Leading and trailing whitespace is removed
47644764
** * Internal whitespace is converted into a single space (0x20)
47654765
** * Line breaks occurs at whitespace or hyphens if possible
47664766
** This is the recommended value and the default.
47674767
**
47684768
** Or a bitwise combination of the following flags:
47694769
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
3369 if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
3370 if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
3371 fossil_print("project-id: %s\n", db_get("project-code", 0));
3372 fossil_print("server-id: %s\n", db_get("server-code", 0));
3373 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374 fossil_print("admin-user: %s (initial password is \"%s\")\n",
3375 g.zLogin, zPassword);
3376 hash_user_password(g.zLogin);
3377 }
3378
3379 /*
@@ -4758,11 +4758,11 @@
4758 ** SETTING: comment-format width=16 default=1
4759 ** Set the algorithm for printing timeline comments to the console.
4760 **
4761 ** Possible values are:
4762 ** 1 Use the original comment printing algorithm:
4763 ** * Leading and trialing whitespace is removed
4764 ** * Internal whitespace is converted into a single space (0x20)
4765 ** * Line breaks occurs at whitespace or hyphens if possible
4766 ** This is the recommended value and the default.
4767 **
4768 ** Or a bitwise combination of the following flags:
4769
--- src/db.c
+++ src/db.c
@@ -3369,11 +3369,11 @@
3369 if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
3370 if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
3371 fossil_print("project-id: %s\n", db_get("project-code", 0));
3372 fossil_print("server-id: %s\n", db_get("server-code", 0));
3373 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
3374 fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
3375 g.zLogin, zPassword);
3376 hash_user_password(g.zLogin);
3377 }
3378
3379 /*
@@ -4758,11 +4758,11 @@
4758 ** SETTING: comment-format width=16 default=1
4759 ** Set the algorithm for printing timeline comments to the console.
4760 **
4761 ** Possible values are:
4762 ** 1 Use the original comment printing algorithm:
4763 ** * Leading and trailing whitespace is removed
4764 ** * Internal whitespace is converted into a single space (0x20)
4765 ** * Line breaks occurs at whitespace or hyphens if possible
4766 ** This is the recommended value and the default.
4767 **
4768 ** Or a bitwise combination of the following flags:
4769
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751751
border-left: 1px solid gold;
752752
}
753753
body.cpage-ckout .file-change-line,
754754
body.cpage-info .file-change-line,
755755
body.cpage-vinfo .file-change-line,
756
+body.cpage-ci .file-change-line,
756757
body.cpage-vdiff .file-change-line {
757758
margin-top: 16px;
758759
margin-bottom: 16px;
759760
margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
760761
display: flex;
761762
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vinfo .file-change-line,
 
756 body.cpage-vdiff .file-change-line {
757 margin-top: 16px;
758 margin-bottom: 16px;
759 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
760 display: flex;
761
--- src/default.css
+++ src/default.css
@@ -751,10 +751,11 @@
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vinfo .file-change-line,
756 body.cpage-ci .file-change-line,
757 body.cpage-vdiff .file-change-line {
758 margin-top: 16px;
759 margin-bottom: 16px;
760 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
761 display: flex;
762
+1 -1
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
10521052
*/
10531053
zMime = nMiss==0 ? P("mimetype") : 0;
10541054
if( zMime==0 ){
10551055
zMime = mimetype_from_name(zName);
10561056
}
1057
- Th_Store("doc_name", zName);
1057
+ Th_StoreUnsafe("doc_name", zName);
10581058
if( vid ){
10591059
Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
10601060
" FROM blob WHERE rid=%d", vid));
10611061
Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
10621062
" WHERE objid=%d AND type='ci'", vid));
10631063
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
1052 */
1053 zMime = nMiss==0 ? P("mimetype") : 0;
1054 if( zMime==0 ){
1055 zMime = mimetype_from_name(zName);
1056 }
1057 Th_Store("doc_name", zName);
1058 if( vid ){
1059 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
1060 " FROM blob WHERE rid=%d", vid));
1061 Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
1062 " WHERE objid=%d AND type='ci'", vid));
1063
--- src/doc.c
+++ src/doc.c
@@ -1052,11 +1052,11 @@
1052 */
1053 zMime = nMiss==0 ? P("mimetype") : 0;
1054 if( zMime==0 ){
1055 zMime = mimetype_from_name(zName);
1056 }
1057 Th_StoreUnsafe("doc_name", zName);
1058 if( vid ){
1059 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
1060 " FROM blob WHERE rid=%d", vid));
1061 Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
1062 " WHERE objid=%d AND type='ci'", vid));
1063
+32 -5
--- src/forum.c
+++ src/forum.c
@@ -59,10 +59,11 @@
5959
ForumPost *pFirst; /* First post in chronological order */
6060
ForumPost *pLast; /* Last post in chronological order */
6161
ForumPost *pDisplay; /* Entries in display order */
6262
ForumPost *pTail; /* Last on the display list */
6363
int mxIndent; /* Maximum indentation level */
64
+ int nArtifact; /* Number of forum artifacts in this thread */
6465
};
6566
#endif /* INTERFACE */
6667
6768
/*
6869
** Return true if the forum post with the given rid has been
@@ -109,12 +110,17 @@
109110
** the post.
110111
**
111112
** If bCheckIrt is true then p's thread in-response-to parents are
112113
** checked (recursively) for closure, else only p is checked.
113114
*/
114
-static int forumpost_is_closed(ForumPost *p, int bCheckIrt){
115
- while(p){
115
+static int forumpost_is_closed(
116
+ ForumThread *pThread, /* Thread that the post is a member of */
117
+ ForumPost *p, /* the forum post */
118
+ int bCheckIrt /* True to check In-Reply-To posts */
119
+){
120
+ int mx = pThread->nArtifact+1;
121
+ while( p && (mx--)>0 ){
116122
if( p->pEditHead ) p = p->pEditHead;
117123
if( p->iClosed || !bCheckIrt ) return p->iClosed;
118124
p = p->pIrt;
119125
}
120126
return 0;
@@ -409,10 +415,11 @@
409415
pThread->pFirst = pPost;
410416
}else{
411417
pThread->pLast->pNext = pPost;
412418
}
413419
pThread->pLast = pPost;
420
+ pThread->nArtifact++;
414421
415422
/* Find the in-reply-to post. Default to the topic post if the replied-to
416423
** post cannot be found. */
417424
if( firt ){
418425
pPost->pIrt = pThread->pFirst;
@@ -520,10 +527,11 @@
520527
fossil_fatal("Not a forum post: \"%s\"", zName);
521528
}
522529
fossil_print("fpid = %d\n", fpid);
523530
fossil_print("froot = %d\n", froot);
524531
pThread = forumthread_create(froot, 1);
532
+ fossil_print("count = %d\n", pThread->nArtifact);
525533
fossil_print("Chronological:\n");
526534
fossil_print(
527535
/* 0 1 2 3 4 5 6 7 */
528536
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
529537
" sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
@@ -565,10 +573,11 @@
565573
int froot;
566574
const char *zName = P("name");
567575
ForumThread *pThread;
568576
ForumPost *p;
569577
char *fuuid;
578
+ Stmt q;
570579
571580
login_check_credentials();
572581
if( !g.perm.Admin ){
573582
return;
574583
}
@@ -599,10 +608,27 @@
599608
for(p=pThread->pFirst; p; p=p->pNext){
600609
@ %h(p->zUuid)
601610
}
602611
forumthread_delete(pThread);
603612
@ </pre>
613
+ @ <hr>
614
+ @ <h2>Related FORUMPOST Table Content</h2>
615
+ @ <table border="1" cellpadding="4" cellspacing="0">
616
+ @ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime
617
+ db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)"
618
+ " FROM forumpost"
619
+ " WHERE froot=%d"
620
+ " ORDER BY fmtime", froot);
621
+ while( db_step(&q)==SQLITE_ROW ){
622
+ @ <tr><td>%d(db_column_int(&q,0))\
623
+ @ <td>%d(db_column_int(&q,1))\
624
+ @ <td>%d(db_column_int(&q,2))\
625
+ @ <td>%d(db_column_int(&q,3))\
626
+ @ <td>%h(db_column_text(&q,4))</tr>
627
+ }
628
+ @ </table>
629
+ db_finalize(&q);
604630
style_finish_page();
605631
}
606632
607633
/*
608634
** Render a forum post for display
@@ -725,10 +751,11 @@
725751
726752
/*
727753
** Display a single post in a forum thread.
728754
*/
729755
static void forum_display_post(
756
+ ForumThread *pThread, /* The thread that this post is a member of */
730757
ForumPost *p, /* Forum post to display */
731758
int iIndentScale, /* Indent scale factor */
732759
int bRaw, /* True to omit the border */
733760
int bUnf, /* True to leave the post unformatted */
734761
int bHist, /* True if showing edit history */
@@ -747,14 +774,14 @@
747774
const char *zMimetype;/* Formatting MIME type */
748775
749776
/* Get the manifest for the post. Abort if not found (e.g. shunned). */
750777
pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
751778
if( !pManifest ) return;
752
- iClosed = forumpost_is_closed(p, 1);
779
+ iClosed = forumpost_is_closed(pThread, p, 1);
753780
/* When not in raw mode, create the border around the post. */
754781
if( !bRaw ){
755
- /* Open the <div> enclosing the post. Set the class string to mark the post
782
+ /* Open the <div> enclosing the post. Set the class string to mark the post
756783
** as selected and/or obsolete. */
757784
iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
758785
@ <div id='forum%d(p->fpid)' class='forumTime\
759786
@ %s(bSelect ? " forumSel" : "")\
760787
@ %s(iClosed ? " forumClosed" : "")\
@@ -1027,11 +1054,11 @@
10271054
}
10281055
10291056
/* Display the appropriate subset of posts in sequence. */
10301057
while( p ){
10311058
/* Display the post. */
1032
- forum_display_post(p, iIndentScale, mode==FD_RAW,
1059
+ forum_display_post(pThread, p, iIndentScale, mode==FD_RAW,
10331060
bUnf, bHist, p==pSelect, zQuery);
10341061
10351062
/* Advance to the next post in the thread. */
10361063
if( mode==FD_CHRONO ){
10371064
/* Chronological mode: display posts (optionally including edits) in their
10381065
--- src/forum.c
+++ src/forum.c
@@ -59,10 +59,11 @@
59 ForumPost *pFirst; /* First post in chronological order */
60 ForumPost *pLast; /* Last post in chronological order */
61 ForumPost *pDisplay; /* Entries in display order */
62 ForumPost *pTail; /* Last on the display list */
63 int mxIndent; /* Maximum indentation level */
 
64 };
65 #endif /* INTERFACE */
66
67 /*
68 ** Return true if the forum post with the given rid has been
@@ -109,12 +110,17 @@
109 ** the post.
110 **
111 ** If bCheckIrt is true then p's thread in-response-to parents are
112 ** checked (recursively) for closure, else only p is checked.
113 */
114 static int forumpost_is_closed(ForumPost *p, int bCheckIrt){
115 while(p){
 
 
 
 
 
116 if( p->pEditHead ) p = p->pEditHead;
117 if( p->iClosed || !bCheckIrt ) return p->iClosed;
118 p = p->pIrt;
119 }
120 return 0;
@@ -409,10 +415,11 @@
409 pThread->pFirst = pPost;
410 }else{
411 pThread->pLast->pNext = pPost;
412 }
413 pThread->pLast = pPost;
 
414
415 /* Find the in-reply-to post. Default to the topic post if the replied-to
416 ** post cannot be found. */
417 if( firt ){
418 pPost->pIrt = pThread->pFirst;
@@ -520,10 +527,11 @@
520 fossil_fatal("Not a forum post: \"%s\"", zName);
521 }
522 fossil_print("fpid = %d\n", fpid);
523 fossil_print("froot = %d\n", froot);
524 pThread = forumthread_create(froot, 1);
 
525 fossil_print("Chronological:\n");
526 fossil_print(
527 /* 0 1 2 3 4 5 6 7 */
528 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
529 " sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
@@ -565,10 +573,11 @@
565 int froot;
566 const char *zName = P("name");
567 ForumThread *pThread;
568 ForumPost *p;
569 char *fuuid;
 
570
571 login_check_credentials();
572 if( !g.perm.Admin ){
573 return;
574 }
@@ -599,10 +608,27 @@
599 for(p=pThread->pFirst; p; p=p->pNext){
600 @ %h(p->zUuid)
601 }
602 forumthread_delete(pThread);
603 @ </pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604 style_finish_page();
605 }
606
607 /*
608 ** Render a forum post for display
@@ -725,10 +751,11 @@
725
726 /*
727 ** Display a single post in a forum thread.
728 */
729 static void forum_display_post(
 
730 ForumPost *p, /* Forum post to display */
731 int iIndentScale, /* Indent scale factor */
732 int bRaw, /* True to omit the border */
733 int bUnf, /* True to leave the post unformatted */
734 int bHist, /* True if showing edit history */
@@ -747,14 +774,14 @@
747 const char *zMimetype;/* Formatting MIME type */
748
749 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
750 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
751 if( !pManifest ) return;
752 iClosed = forumpost_is_closed(p, 1);
753 /* When not in raw mode, create the border around the post. */
754 if( !bRaw ){
755 /* Open the <div> enclosing the post. Set the class string to mark the post
756 ** as selected and/or obsolete. */
757 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
758 @ <div id='forum%d(p->fpid)' class='forumTime\
759 @ %s(bSelect ? " forumSel" : "")\
760 @ %s(iClosed ? " forumClosed" : "")\
@@ -1027,11 +1054,11 @@
1027 }
1028
1029 /* Display the appropriate subset of posts in sequence. */
1030 while( p ){
1031 /* Display the post. */
1032 forum_display_post(p, iIndentScale, mode==FD_RAW,
1033 bUnf, bHist, p==pSelect, zQuery);
1034
1035 /* Advance to the next post in the thread. */
1036 if( mode==FD_CHRONO ){
1037 /* Chronological mode: display posts (optionally including edits) in their
1038
--- src/forum.c
+++ src/forum.c
@@ -59,10 +59,11 @@
59 ForumPost *pFirst; /* First post in chronological order */
60 ForumPost *pLast; /* Last post in chronological order */
61 ForumPost *pDisplay; /* Entries in display order */
62 ForumPost *pTail; /* Last on the display list */
63 int mxIndent; /* Maximum indentation level */
64 int nArtifact; /* Number of forum artifacts in this thread */
65 };
66 #endif /* INTERFACE */
67
68 /*
69 ** Return true if the forum post with the given rid has been
@@ -109,12 +110,17 @@
110 ** the post.
111 **
112 ** If bCheckIrt is true then p's thread in-response-to parents are
113 ** checked (recursively) for closure, else only p is checked.
114 */
115 static int forumpost_is_closed(
116 ForumThread *pThread, /* Thread that the post is a member of */
117 ForumPost *p, /* the forum post */
118 int bCheckIrt /* True to check In-Reply-To posts */
119 ){
120 int mx = pThread->nArtifact+1;
121 while( p && (mx--)>0 ){
122 if( p->pEditHead ) p = p->pEditHead;
123 if( p->iClosed || !bCheckIrt ) return p->iClosed;
124 p = p->pIrt;
125 }
126 return 0;
@@ -409,10 +415,11 @@
415 pThread->pFirst = pPost;
416 }else{
417 pThread->pLast->pNext = pPost;
418 }
419 pThread->pLast = pPost;
420 pThread->nArtifact++;
421
422 /* Find the in-reply-to post. Default to the topic post if the replied-to
423 ** post cannot be found. */
424 if( firt ){
425 pPost->pIrt = pThread->pFirst;
@@ -520,10 +527,11 @@
527 fossil_fatal("Not a forum post: \"%s\"", zName);
528 }
529 fossil_print("fpid = %d\n", fpid);
530 fossil_print("froot = %d\n", froot);
531 pThread = forumthread_create(froot, 1);
532 fossil_print("count = %d\n", pThread->nArtifact);
533 fossil_print("Chronological:\n");
534 fossil_print(
535 /* 0 1 2 3 4 5 6 7 */
536 /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
537 " sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
@@ -565,10 +573,11 @@
573 int froot;
574 const char *zName = P("name");
575 ForumThread *pThread;
576 ForumPost *p;
577 char *fuuid;
578 Stmt q;
579
580 login_check_credentials();
581 if( !g.perm.Admin ){
582 return;
583 }
@@ -599,10 +608,27 @@
608 for(p=pThread->pFirst; p; p=p->pNext){
609 @ %h(p->zUuid)
610 }
611 forumthread_delete(pThread);
612 @ </pre>
613 @ <hr>
614 @ <h2>Related FORUMPOST Table Content</h2>
615 @ <table border="1" cellpadding="4" cellspacing="0">
616 @ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime
617 db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)"
618 " FROM forumpost"
619 " WHERE froot=%d"
620 " ORDER BY fmtime", froot);
621 while( db_step(&q)==SQLITE_ROW ){
622 @ <tr><td>%d(db_column_int(&q,0))\
623 @ <td>%d(db_column_int(&q,1))\
624 @ <td>%d(db_column_int(&q,2))\
625 @ <td>%d(db_column_int(&q,3))\
626 @ <td>%h(db_column_text(&q,4))</tr>
627 }
628 @ </table>
629 db_finalize(&q);
630 style_finish_page();
631 }
632
633 /*
634 ** Render a forum post for display
@@ -725,10 +751,11 @@
751
752 /*
753 ** Display a single post in a forum thread.
754 */
755 static void forum_display_post(
756 ForumThread *pThread, /* The thread that this post is a member of */
757 ForumPost *p, /* Forum post to display */
758 int iIndentScale, /* Indent scale factor */
759 int bRaw, /* True to omit the border */
760 int bUnf, /* True to leave the post unformatted */
761 int bHist, /* True if showing edit history */
@@ -747,14 +774,14 @@
774 const char *zMimetype;/* Formatting MIME type */
775
776 /* Get the manifest for the post. Abort if not found (e.g. shunned). */
777 pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
778 if( !pManifest ) return;
779 iClosed = forumpost_is_closed(pThread, p, 1);
780 /* When not in raw mode, create the border around the post. */
781 if( !bRaw ){
782 /* Open the <div> enclosing the post. Set the class string to mark the post
783 ** as selected and/or obsolete. */
784 iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
785 @ <div id='forum%d(p->fpid)' class='forumTime\
786 @ %s(bSelect ? " forumSel" : "")\
787 @ %s(iClosed ? " forumClosed" : "")\
@@ -1027,11 +1054,11 @@
1054 }
1055
1056 /* Display the appropriate subset of posts in sequence. */
1057 while( p ){
1058 /* Display the post. */
1059 forum_display_post(pThread, p, iIndentScale, mode==FD_RAW,
1060 bUnf, bHist, p==pSelect, zQuery);
1061
1062 /* Advance to the next post in the thread. */
1063 if( mode==FD_CHRONO ){
1064 /* Chronological mode: display posts (optionally including edits) in their
1065
+1 -1
--- src/graph.c
+++ src/graph.c
@@ -121,11 +121,11 @@
121121
u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122122
** rail that the node */
123123
u8 bOverfull; /* Unable to allocate sufficient rails */
124124
u64 mergeRail; /* Rails used for merge lines */
125125
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
126
- u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
126
+ u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */
127127
};
128128
129129
#endif
130130
131131
/* The N-th bit */
132132
--- src/graph.c
+++ src/graph.c
@@ -121,11 +121,11 @@
121 u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122 ** rail that the node */
123 u8 bOverfull; /* Unable to allocate sufficient rails */
124 u64 mergeRail; /* Rails used for merge lines */
125 GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
126 u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
127 };
128
129 #endif
130
131 /* The N-th bit */
132
--- src/graph.c
+++ src/graph.c
@@ -121,11 +121,11 @@
121 u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122 ** rail that the node */
123 u8 bOverfull; /* Unable to allocate sufficient rails */
124 u64 mergeRail; /* Rails used for merge lines */
125 GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
126 u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */
127 };
128
129 #endif
130
131 /* The N-th bit */
132
+12 -12
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -319,11 +319,13 @@
319319
** The following OpenSSL configuration options must not be used for this feature
320320
** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not
321321
** currently set these options when building OpenSSL for Windows. */
322322
#if defined(_WIN32)
323323
#if OPENSSL_VERSION_NUMBER >= 0x030200000
324
- if( SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 ){
324
+ if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */
325
+ && SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0
326
+ ){
325327
fossil_print("NOTICE: Failed to load the Windows root certificates.\n");
326328
}
327329
#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
328330
#endif /* _WIN32 */
329331
@@ -999,12 +1001,12 @@
9991001
fossil_print("\n"
10001002
" The OpenSSL library is not used by this build of Fossil\n\n"
10011003
);
10021004
}
10031005
#else
1004
- fossil_print("OpenSSL-version: %s (0x%09x)\n",
1005
- SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
1006
+ fossil_print("OpenSSL-version: %s (0x%09llx)\n",
1007
+ SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay());
10061008
if( verbose ){
10071009
fossil_print("\n"
10081010
" The version of the OpenSSL library being used\n"
10091011
" by this instance of Fossil. Version 3.0.0 or\n"
10101012
" later is recommended.\n\n"
@@ -1061,20 +1063,18 @@
10611063
" values are built into your OpenSSL library.\n\n"
10621064
);
10631065
}
10641066
10651067
#if defined(_WIN32)
1066
-#if OPENSSL_VERSION_NUMBER >= 0x030200000
1067
- fossil_print(" OpenSSL-winstore: Yes\n");
1068
-#else /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
1069
- fossil_print(" OpenSSL-winstore: No\n");
1070
-#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
1068
+ fossil_print(" OpenSSL-winstore: %s\n",
1069
+ (SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No");
10711070
if( verbose ){
10721071
fossil_print("\n"
1073
- " OpenSSL 3.2.0, or newer, use the root certificates managed by\n"
1074
- " the Windows operating system. The installed root certificates\n"
1075
- " are listed by the command:\n\n"
1072
+ " OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n"
1073
+ " are able to use the root certificates managed by the Windows\n"
1074
+ " operating system. The installed root certificates are listed\n"
1075
+ " by the command:\n\n"
10761076
" certutil -store \"ROOT\"\n\n"
10771077
);
10781078
}
10791079
#endif /* _WIN32 */
10801080
@@ -1232,10 +1232,10 @@
12321232
** freed by the caller.
12331233
*/
12341234
char *fossil_openssl_version(void){
12351235
#if defined(FOSSIL_ENABLE_SSL)
12361236
return mprintf("%s (0x%09x)\n",
1237
- SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
1237
+ SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay());
12381238
#else
12391239
return mprintf("none");
12401240
#endif
12411241
}
12421242
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -319,11 +319,13 @@
319 ** The following OpenSSL configuration options must not be used for this feature
320 ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not
321 ** currently set these options when building OpenSSL for Windows. */
322 #if defined(_WIN32)
323 #if OPENSSL_VERSION_NUMBER >= 0x030200000
324 if( SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 ){
 
 
325 fossil_print("NOTICE: Failed to load the Windows root certificates.\n");
326 }
327 #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
328 #endif /* _WIN32 */
329
@@ -999,12 +1001,12 @@
999 fossil_print("\n"
1000 " The OpenSSL library is not used by this build of Fossil\n\n"
1001 );
1002 }
1003 #else
1004 fossil_print("OpenSSL-version: %s (0x%09x)\n",
1005 SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
1006 if( verbose ){
1007 fossil_print("\n"
1008 " The version of the OpenSSL library being used\n"
1009 " by this instance of Fossil. Version 3.0.0 or\n"
1010 " later is recommended.\n\n"
@@ -1061,20 +1063,18 @@
1061 " values are built into your OpenSSL library.\n\n"
1062 );
1063 }
1064
1065 #if defined(_WIN32)
1066 #if OPENSSL_VERSION_NUMBER >= 0x030200000
1067 fossil_print(" OpenSSL-winstore: Yes\n");
1068 #else /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
1069 fossil_print(" OpenSSL-winstore: No\n");
1070 #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
1071 if( verbose ){
1072 fossil_print("\n"
1073 " OpenSSL 3.2.0, or newer, use the root certificates managed by\n"
1074 " the Windows operating system. The installed root certificates\n"
1075 " are listed by the command:\n\n"
 
1076 " certutil -store \"ROOT\"\n\n"
1077 );
1078 }
1079 #endif /* _WIN32 */
1080
@@ -1232,10 +1232,10 @@
1232 ** freed by the caller.
1233 */
1234 char *fossil_openssl_version(void){
1235 #if defined(FOSSIL_ENABLE_SSL)
1236 return mprintf("%s (0x%09x)\n",
1237 SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
1238 #else
1239 return mprintf("none");
1240 #endif
1241 }
1242
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -319,11 +319,13 @@
319 ** The following OpenSSL configuration options must not be used for this feature
320 ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not
321 ** currently set these options when building OpenSSL for Windows. */
322 #if defined(_WIN32)
323 #if OPENSSL_VERSION_NUMBER >= 0x030200000
324 if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */
325 && SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0
326 ){
327 fossil_print("NOTICE: Failed to load the Windows root certificates.\n");
328 }
329 #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
330 #endif /* _WIN32 */
331
@@ -999,12 +1001,12 @@
1001 fossil_print("\n"
1002 " The OpenSSL library is not used by this build of Fossil\n\n"
1003 );
1004 }
1005 #else
1006 fossil_print("OpenSSL-version: %s (0x%09llx)\n",
1007 SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay());
1008 if( verbose ){
1009 fossil_print("\n"
1010 " The version of the OpenSSL library being used\n"
1011 " by this instance of Fossil. Version 3.0.0 or\n"
1012 " later is recommended.\n\n"
@@ -1061,20 +1063,18 @@
1063 " values are built into your OpenSSL library.\n\n"
1064 );
1065 }
1066
1067 #if defined(_WIN32)
1068 fossil_print(" OpenSSL-winstore: %s\n",
1069 (SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No");
 
 
 
1070 if( verbose ){
1071 fossil_print("\n"
1072 " OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n"
1073 " are able to use the root certificates managed by the Windows\n"
1074 " operating system. The installed root certificates are listed\n"
1075 " by the command:\n\n"
1076 " certutil -store \"ROOT\"\n\n"
1077 );
1078 }
1079 #endif /* _WIN32 */
1080
@@ -1232,10 +1232,10 @@
1232 ** freed by the caller.
1233 */
1234 char *fossil_openssl_version(void){
1235 #if defined(FOSSIL_ENABLE_SSL)
1236 return mprintf("%s (0x%09x)\n",
1237 SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay());
1238 #else
1239 return mprintf("none");
1240 #endif
1241 }
1242
+7 -5
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951951
const char *zOrigDate;
952952
int okWiki = 0;
953953
Blob wiki_read_links = BLOB_INITIALIZER;
954954
Blob wiki_add_links = BLOB_INITIALIZER;
955955
956
- Th_Store("current_checkin", zName);
956
+ Th_StoreUnsafe("current_checkin", zName);
957957
style_header("Check-in [%S]", zUuid);
958958
login_anonymous_available();
959959
zEUser = db_text(0,
960960
"SELECT value FROM tagxref"
961961
" WHERE tagid=%d AND rid=%d AND tagtype>0",
@@ -993,12 +993,14 @@
993993
}
994994
zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid);
995995
@ <tr><th>Downloads:</th><td>
996996
@ %z(href("%s",zUrl))Tarball</a>
997997
@ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a>
998
- @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\
999
- @ SQL archive</a></td></tr>
998
+ if( g.zLogin!=0 ){
999
+ @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\
1000
+ @ SQL archive</a></td></tr>
1001
+ }
10001002
fossil_free(zUrl);
10011003
blob_reset(&projName);
10021004
}
10031005
10041006
@ <tr><th>Timelines:</th><td>
@@ -1182,14 +1184,14 @@
11821184
@ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
11831185
@ Side-by-Side&nbsp;Diff</a>
11841186
}
11851187
if( diffType!=0 ){
11861188
if( *zW ){
1187
- @ %z(chref("button","%R/%s/%T",zPage,zName))
1189
+ @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
11881190
@ Show&nbsp;Whitespace&nbsp;Changes</a>
11891191
}else{
1190
- @ %z(chref("button","%R/%s/%T?w",zPage,zName))
1192
+ @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
11911193
@ Ignore&nbsp;Whitespace</a>
11921194
}
11931195
}
11941196
if( zParent ){
11951197
@ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
11961198
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951 const char *zOrigDate;
952 int okWiki = 0;
953 Blob wiki_read_links = BLOB_INITIALIZER;
954 Blob wiki_add_links = BLOB_INITIALIZER;
955
956 Th_Store("current_checkin", zName);
957 style_header("Check-in [%S]", zUuid);
958 login_anonymous_available();
959 zEUser = db_text(0,
960 "SELECT value FROM tagxref"
961 " WHERE tagid=%d AND rid=%d AND tagtype>0",
@@ -993,12 +993,14 @@
993 }
994 zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid);
995 @ <tr><th>Downloads:</th><td>
996 @ %z(href("%s",zUrl))Tarball</a>
997 @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a>
998 @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\
999 @ SQL archive</a></td></tr>
 
 
1000 fossil_free(zUrl);
1001 blob_reset(&projName);
1002 }
1003
1004 @ <tr><th>Timelines:</th><td>
@@ -1182,14 +1184,14 @@
1182 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
1183 @ Side-by-Side&nbsp;Diff</a>
1184 }
1185 if( diffType!=0 ){
1186 if( *zW ){
1187 @ %z(chref("button","%R/%s/%T",zPage,zName))
1188 @ Show&nbsp;Whitespace&nbsp;Changes</a>
1189 }else{
1190 @ %z(chref("button","%R/%s/%T?w",zPage,zName))
1191 @ Ignore&nbsp;Whitespace</a>
1192 }
1193 }
1194 if( zParent ){
1195 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1196
--- src/info.c
+++ src/info.c
@@ -951,11 +951,11 @@
951 const char *zOrigDate;
952 int okWiki = 0;
953 Blob wiki_read_links = BLOB_INITIALIZER;
954 Blob wiki_add_links = BLOB_INITIALIZER;
955
956 Th_StoreUnsafe("current_checkin", zName);
957 style_header("Check-in [%S]", zUuid);
958 login_anonymous_available();
959 zEUser = db_text(0,
960 "SELECT value FROM tagxref"
961 " WHERE tagid=%d AND rid=%d AND tagtype>0",
@@ -993,12 +993,14 @@
993 }
994 zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid);
995 @ <tr><th>Downloads:</th><td>
996 @ %z(href("%s",zUrl))Tarball</a>
997 @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a>
998 if( g.zLogin!=0 ){
999 @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\
1000 @ SQL archive</a></td></tr>
1001 }
1002 fossil_free(zUrl);
1003 blob_reset(&projName);
1004 }
1005
1006 @ <tr><th>Timelines:</th><td>
@@ -1182,14 +1184,14 @@
1184 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
1185 @ Side-by-Side&nbsp;Diff</a>
1186 }
1187 if( diffType!=0 ){
1188 if( *zW ){
1189 @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
1190 @ Show&nbsp;Whitespace&nbsp;Changes</a>
1191 }else{
1192 @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
1193 @ Ignore&nbsp;Whitespace</a>
1194 }
1195 }
1196 if( zParent ){
1197 @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
1198
+21 -8
--- src/loadctrl.c
+++ src/loadctrl.c
@@ -43,10 +43,30 @@
4343
** Print the load average on the host machine.
4444
*/
4545
void loadavg_test_cmd(void){
4646
fossil_print("load-average: %f\n", load_average());
4747
}
48
+
49
+/*
50
+** WEBPAGE: test-overload
51
+**
52
+** Generate the response that would normally be shown only when
53
+** service is denied due to an overload condition. This is for
54
+** testing of the overload warning page.
55
+*/
56
+void overload_page(void){
57
+ double mxLoad = atof(db_get("max-loadavg", "0.0"));
58
+ style_set_current_feature("test");
59
+ style_header("Server Overload");
60
+ @ <h2>The server load is currently too high.
61
+ @ Please try again later.</h2>
62
+ @ <p>Current load average: %f(load_average())<br>
63
+ @ Load average limit: %f(mxLoad)<br>
64
+ @ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br>
65
+ @ Timestamp: %h(db_text("","SELECT datetime()"))Z</p>
66
+ style_finish_page();
67
+}
4868
4969
/*
5070
** Abort the current page request if the load average of the host
5171
** computer is too high. Admin and Setup users are exempt from this
5272
** restriction.
@@ -60,17 +80,10 @@
6080
login_check_credentials();
6181
if(g.perm.Admin || g.perm.Setup){
6282
return;
6383
}
6484
#endif
65
-
66
- style_set_current_feature("test");
67
- style_header("Server Overload");
68
- @ <h2>The server load is currently too high.
69
- @ Please try again later.</h2>
70
- @ <p>Current load average: %f(load_average()).<br>
71
- @ Load average limit: %f(mxLoad)</p>
72
- style_finish_page();
85
+ overload_page();
7386
cgi_set_status(503,"Server Overload");
7487
cgi_reply();
7588
exit(0);
7689
}
7790
--- src/loadctrl.c
+++ src/loadctrl.c
@@ -43,10 +43,30 @@
43 ** Print the load average on the host machine.
44 */
45 void loadavg_test_cmd(void){
46 fossil_print("load-average: %f\n", load_average());
47 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
49 /*
50 ** Abort the current page request if the load average of the host
51 ** computer is too high. Admin and Setup users are exempt from this
52 ** restriction.
@@ -60,17 +80,10 @@
60 login_check_credentials();
61 if(g.perm.Admin || g.perm.Setup){
62 return;
63 }
64 #endif
65
66 style_set_current_feature("test");
67 style_header("Server Overload");
68 @ <h2>The server load is currently too high.
69 @ Please try again later.</h2>
70 @ <p>Current load average: %f(load_average()).<br>
71 @ Load average limit: %f(mxLoad)</p>
72 style_finish_page();
73 cgi_set_status(503,"Server Overload");
74 cgi_reply();
75 exit(0);
76 }
77
--- src/loadctrl.c
+++ src/loadctrl.c
@@ -43,10 +43,30 @@
43 ** Print the load average on the host machine.
44 */
45 void loadavg_test_cmd(void){
46 fossil_print("load-average: %f\n", load_average());
47 }
48
49 /*
50 ** WEBPAGE: test-overload
51 **
52 ** Generate the response that would normally be shown only when
53 ** service is denied due to an overload condition. This is for
54 ** testing of the overload warning page.
55 */
56 void overload_page(void){
57 double mxLoad = atof(db_get("max-loadavg", "0.0"));
58 style_set_current_feature("test");
59 style_header("Server Overload");
60 @ <h2>The server load is currently too high.
61 @ Please try again later.</h2>
62 @ <p>Current load average: %f(load_average())<br>
63 @ Load average limit: %f(mxLoad)<br>
64 @ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br>
65 @ Timestamp: %h(db_text("","SELECT datetime()"))Z</p>
66 style_finish_page();
67 }
68
69 /*
70 ** Abort the current page request if the load average of the host
71 ** computer is too high. Admin and Setup users are exempt from this
72 ** restriction.
@@ -60,17 +80,10 @@
80 login_check_credentials();
81 if(g.perm.Admin || g.perm.Setup){
82 return;
83 }
84 #endif
85 overload_page();
 
 
 
 
 
 
 
86 cgi_set_status(503,"Server Overload");
87 cgi_reply();
88 exit(0);
89 }
90
+1 -1
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
14161416
*/
14171417
zIpAddr = PD("REMOTE_ADDR","nil");
14181418
if( ( cgi_is_loopback(zIpAddr)
14191419
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
14201420
&& g.useLocalauth
1421
- && db_get_int("localauth",0)==0
1421
+ && db_get_boolean("localauth",0)==0
14221422
&& P("HTTPS")==0
14231423
){
14241424
char *zSeed;
14251425
if( g.localOpen ) zLogin = db_lget("default-user",0);
14261426
if( zLogin!=0 ){
14271427
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
1416 */
1417 zIpAddr = PD("REMOTE_ADDR","nil");
1418 if( ( cgi_is_loopback(zIpAddr)
1419 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
1420 && g.useLocalauth
1421 && db_get_int("localauth",0)==0
1422 && P("HTTPS")==0
1423 ){
1424 char *zSeed;
1425 if( g.localOpen ) zLogin = db_lget("default-user",0);
1426 if( zLogin!=0 ){
1427
--- src/login.c
+++ src/login.c
@@ -1416,11 +1416,11 @@
1416 */
1417 zIpAddr = PD("REMOTE_ADDR","nil");
1418 if( ( cgi_is_loopback(zIpAddr)
1419 || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
1420 && g.useLocalauth
1421 && db_get_boolean("localauth",0)==0
1422 && P("HTTPS")==0
1423 ){
1424 char *zSeed;
1425 if( g.localOpen ) zLogin = db_lget("default-user",0);
1426 if( zLogin!=0 ){
1427
+39 -11
--- src/main.c
+++ src/main.c
@@ -2365,11 +2365,18 @@
23652365
** notfound: URL When in "directory:" mode, redirect to
23662366
** URL if no suitable repository is found.
23672367
**
23682368
** repolist When in "directory:" mode, display a page
23692369
** showing a list of available repositories if
2370
-** the URL is "/".
2370
+** the URL is "/". Some control over the display
2371
+** is accomplished using environment variables.
2372
+** FOSSIL_REPOLIST_TITLE is the tital of the page.
2373
+** FOSSIL_REPOLIST_SHOW cause the "Description"
2374
+** column to display if it contains "description" as
2375
+** as a substring, and causes the Login-Group column
2376
+** to display if it contains the "login-group"
2377
+** substring.
23712378
**
23722379
** localauth Grant administrator privileges to connections
23732380
** from 127.0.0.1 or ::1.
23742381
**
23752382
** nossl Signal that no SSL connections are available.
@@ -3523,11 +3530,12 @@
35233530
}
35243531
blob_append_escaped_arg(&ssh, "fossil", 1);
35253532
}else{
35263533
blob_appendf(&ssh, " %$", zFossilCmd);
35273534
}
3528
- blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3535
+ blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d",
3536
+ iPort);
35293537
if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
35303538
if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
35313539
if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
35323540
if( zExtPage ){
35333541
if( !file_is_absolute_path(zExtPage) ){
@@ -3710,10 +3718,13 @@
37103718
** case=3 Extra db_end_transaction()
37113719
** case=4 Error during SQL processing
37123720
** case=5 Call the segfault handler
37133721
** case=6 Call webpage_assert()
37143722
** case=7 Call webpage_error()
3723
+** case=8 Simulate a timeout
3724
+** case=9 Simulate a TH1 XSS vulnerability
3725
+** case=10 Simulate a TH1 SQL-injection vulnerability
37153726
*/
37163727
void test_warning_page(void){
37173728
int iCase = atoi(PD("case","0"));
37183729
int i;
37193730
login_check_credentials();
@@ -3722,17 +3733,15 @@
37223733
return;
37233734
}
37243735
style_set_current_feature("test");
37253736
style_header("Warning Test Page");
37263737
style_submenu_element("Error Log","%R/errorlog");
3727
- if( iCase<1 || iCase>4 ){
3728
- @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3729
- @ by clicking on one of the following cases:
3730
- }else{
3731
- @ <p>This is the test page for case=%d(iCase). All possible cases:
3732
- }
3733
- for(i=1; i<=8; i++){
3738
+ @ <p>This page will generate various kinds of errors to test Fossil's
3739
+ @ reaction. Depending on settings, a message might be written
3740
+ @ into the <a href="%R/errorlog">error log</a>. Click on
3741
+ @ one of the following hyperlinks to generate a simulated error:
3742
+ for(i=1; i<=10; i++){
37343743
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
37353744
}
37363745
@ </p>
37373746
@ <p><ol>
37383747
@ <li value='1'> Call fossil_warning()
@@ -3761,20 +3770,39 @@
37613770
}
37623771
@ <li value='6'> call webpage_assert(0)
37633772
if( iCase==6 ){
37643773
webpage_assert( 5==7 );
37653774
}
3766
- @ <li value='7'> call webpage_error()"
3775
+ @ <li value='7'> call webpage_error()
37673776
if( iCase==7 ){
37683777
cgi_reset_content();
37693778
webpage_error("Case 7 from /test-warning");
37703779
}
3771
- @ <li value='8'> simulated timeout"
3780
+ @ <li value='8'> simulated timeout
37723781
if( iCase==8 ){
37733782
fossil_set_timeout(1);
37743783
cgi_reset_content();
37753784
sqlite3_sleep(1100);
3785
+ }
3786
+ @ <li value='9'> simulated TH1 XSS vulnerability
3787
+ @ <li value='10'> simulated TH1 SQL-injection vulnerability
3788
+ if( iCase==9 || iCase==10 ){
3789
+ const char *zR;
3790
+ int n, rc;
3791
+ static const char *zTH1[] = {
3792
+ /* case 9 */ "html [taint {<b>XSS</b>}]",
3793
+ /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3794
+ " html \"<b>[htmlize $msg]</b>\"\n"
3795
+ "}"
3796
+ };
3797
+ rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3798
+ zR = Th_GetResult(g.interp, &n);
3799
+ if( rc==TH_OK ){
3800
+ @ <pre class="th1result">%h(zR)</pre>
3801
+ }else{
3802
+ @ <pre class="th1error">%h(zR)</pre>
3803
+ }
37763804
}
37773805
@ </ol>
37783806
@ <p>End of test</p>
37793807
style_finish_page();
37803808
}
37813809
--- src/main.c
+++ src/main.c
@@ -2365,11 +2365,18 @@
2365 ** notfound: URL When in "directory:" mode, redirect to
2366 ** URL if no suitable repository is found.
2367 **
2368 ** repolist When in "directory:" mode, display a page
2369 ** showing a list of available repositories if
2370 ** the URL is "/".
 
 
 
 
 
 
 
2371 **
2372 ** localauth Grant administrator privileges to connections
2373 ** from 127.0.0.1 or ::1.
2374 **
2375 ** nossl Signal that no SSL connections are available.
@@ -3523,11 +3530,12 @@
3523 }
3524 blob_append_escaped_arg(&ssh, "fossil", 1);
3525 }else{
3526 blob_appendf(&ssh, " %$", zFossilCmd);
3527 }
3528 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
 
3529 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3530 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3531 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3532 if( zExtPage ){
3533 if( !file_is_absolute_path(zExtPage) ){
@@ -3710,10 +3718,13 @@
3710 ** case=3 Extra db_end_transaction()
3711 ** case=4 Error during SQL processing
3712 ** case=5 Call the segfault handler
3713 ** case=6 Call webpage_assert()
3714 ** case=7 Call webpage_error()
 
 
 
3715 */
3716 void test_warning_page(void){
3717 int iCase = atoi(PD("case","0"));
3718 int i;
3719 login_check_credentials();
@@ -3722,17 +3733,15 @@
3722 return;
3723 }
3724 style_set_current_feature("test");
3725 style_header("Warning Test Page");
3726 style_submenu_element("Error Log","%R/errorlog");
3727 if( iCase<1 || iCase>4 ){
3728 @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3729 @ by clicking on one of the following cases:
3730 }else{
3731 @ <p>This is the test page for case=%d(iCase). All possible cases:
3732 }
3733 for(i=1; i<=8; i++){
3734 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3735 }
3736 @ </p>
3737 @ <p><ol>
3738 @ <li value='1'> Call fossil_warning()
@@ -3761,20 +3770,39 @@
3761 }
3762 @ <li value='6'> call webpage_assert(0)
3763 if( iCase==6 ){
3764 webpage_assert( 5==7 );
3765 }
3766 @ <li value='7'> call webpage_error()"
3767 if( iCase==7 ){
3768 cgi_reset_content();
3769 webpage_error("Case 7 from /test-warning");
3770 }
3771 @ <li value='8'> simulated timeout"
3772 if( iCase==8 ){
3773 fossil_set_timeout(1);
3774 cgi_reset_content();
3775 sqlite3_sleep(1100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3776 }
3777 @ </ol>
3778 @ <p>End of test</p>
3779 style_finish_page();
3780 }
3781
--- src/main.c
+++ src/main.c
@@ -2365,11 +2365,18 @@
2365 ** notfound: URL When in "directory:" mode, redirect to
2366 ** URL if no suitable repository is found.
2367 **
2368 ** repolist When in "directory:" mode, display a page
2369 ** showing a list of available repositories if
2370 ** the URL is "/". Some control over the display
2371 ** is accomplished using environment variables.
2372 ** FOSSIL_REPOLIST_TITLE is the tital of the page.
2373 ** FOSSIL_REPOLIST_SHOW cause the "Description"
2374 ** column to display if it contains "description" as
2375 ** as a substring, and causes the Login-Group column
2376 ** to display if it contains the "login-group"
2377 ** substring.
2378 **
2379 ** localauth Grant administrator privileges to connections
2380 ** from 127.0.0.1 or ::1.
2381 **
2382 ** nossl Signal that no SSL connections are available.
@@ -3523,11 +3530,12 @@
3530 }
3531 blob_append_escaped_arg(&ssh, "fossil", 1);
3532 }else{
3533 blob_appendf(&ssh, " %$", zFossilCmd);
3534 }
3535 blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d",
3536 iPort);
3537 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3538 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3539 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3540 if( zExtPage ){
3541 if( !file_is_absolute_path(zExtPage) ){
@@ -3710,10 +3718,13 @@
3718 ** case=3 Extra db_end_transaction()
3719 ** case=4 Error during SQL processing
3720 ** case=5 Call the segfault handler
3721 ** case=6 Call webpage_assert()
3722 ** case=7 Call webpage_error()
3723 ** case=8 Simulate a timeout
3724 ** case=9 Simulate a TH1 XSS vulnerability
3725 ** case=10 Simulate a TH1 SQL-injection vulnerability
3726 */
3727 void test_warning_page(void){
3728 int iCase = atoi(PD("case","0"));
3729 int i;
3730 login_check_credentials();
@@ -3722,17 +3733,15 @@
3733 return;
3734 }
3735 style_set_current_feature("test");
3736 style_header("Warning Test Page");
3737 style_submenu_element("Error Log","%R/errorlog");
3738 @ <p>This page will generate various kinds of errors to test Fossil's
3739 @ reaction. Depending on settings, a message might be written
3740 @ into the <a href="%R/errorlog">error log</a>. Click on
3741 @ one of the following hyperlinks to generate a simulated error:
3742 for(i=1; i<=10; i++){
 
 
3743 @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3744 }
3745 @ </p>
3746 @ <p><ol>
3747 @ <li value='1'> Call fossil_warning()
@@ -3761,20 +3770,39 @@
3770 }
3771 @ <li value='6'> call webpage_assert(0)
3772 if( iCase==6 ){
3773 webpage_assert( 5==7 );
3774 }
3775 @ <li value='7'> call webpage_error()
3776 if( iCase==7 ){
3777 cgi_reset_content();
3778 webpage_error("Case 7 from /test-warning");
3779 }
3780 @ <li value='8'> simulated timeout
3781 if( iCase==8 ){
3782 fossil_set_timeout(1);
3783 cgi_reset_content();
3784 sqlite3_sleep(1100);
3785 }
3786 @ <li value='9'> simulated TH1 XSS vulnerability
3787 @ <li value='10'> simulated TH1 SQL-injection vulnerability
3788 if( iCase==9 || iCase==10 ){
3789 const char *zR;
3790 int n, rc;
3791 static const char *zTH1[] = {
3792 /* case 9 */ "html [taint {<b>XSS</b>}]",
3793 /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3794 " html \"<b>[htmlize $msg]</b>\"\n"
3795 "}"
3796 };
3797 rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3798 zR = Th_GetResult(g.interp, &n);
3799 if( rc==TH_OK ){
3800 @ <pre class="th1result">%h(zR)</pre>
3801 }else{
3802 @ <pre class="th1error">%h(zR)</pre>
3803 }
3804 }
3805 @ </ol>
3806 @ <p>End of test</p>
3807 style_finish_page();
3808 }
3809
+2 -2
--- src/manifest.c
+++ src/manifest.c
@@ -3024,11 +3024,11 @@
30243024
CARD_STR2(K, p->zTicketUuid);
30253025
CARD_STR2(L, p->zWikiTitle);
30263026
ISA( CFTYPE_CLUSTER ){
30273027
CARD_LETTER(M);
30283028
blob_append_char(b, '[');
3029
- for( int i = 0; i < p->nCChild; ++i ){
3029
+ for( i = 0; i < p->nCChild; ++i ){
30303030
if( i>0 ) blob_append_char(b, ',');
30313031
blob_appendf(b, "%!j", p->azCChild[i]);
30323032
}
30333033
blob_append_char(b, ']');
30343034
}
@@ -3059,11 +3059,11 @@
30593059
}
30603060
CARD_STR2(R, p->zRepoCksum);
30613061
if( p->nTag ){
30623062
CARD_LETTER(T);
30633063
blob_append_char(b, '[');
3064
- for( int i = 0; i < p->nTag; ++i ){
3064
+ for( i = 0; i < p->nTag; ++i ){
30653065
const char *zName = p->aTag[i].zName;
30663066
if( i>0 ) blob_append_char(b, ',');
30673067
blob_append_char(b, '{');
30683068
blob_appendf(b, "\"type\":\"%c\"", *zName);
30693069
KVP_STR(1, name, &zName[1]);
30703070
--- src/manifest.c
+++ src/manifest.c
@@ -3024,11 +3024,11 @@
3024 CARD_STR2(K, p->zTicketUuid);
3025 CARD_STR2(L, p->zWikiTitle);
3026 ISA( CFTYPE_CLUSTER ){
3027 CARD_LETTER(M);
3028 blob_append_char(b, '[');
3029 for( int i = 0; i < p->nCChild; ++i ){
3030 if( i>0 ) blob_append_char(b, ',');
3031 blob_appendf(b, "%!j", p->azCChild[i]);
3032 }
3033 blob_append_char(b, ']');
3034 }
@@ -3059,11 +3059,11 @@
3059 }
3060 CARD_STR2(R, p->zRepoCksum);
3061 if( p->nTag ){
3062 CARD_LETTER(T);
3063 blob_append_char(b, '[');
3064 for( int i = 0; i < p->nTag; ++i ){
3065 const char *zName = p->aTag[i].zName;
3066 if( i>0 ) blob_append_char(b, ',');
3067 blob_append_char(b, '{');
3068 blob_appendf(b, "\"type\":\"%c\"", *zName);
3069 KVP_STR(1, name, &zName[1]);
3070
--- src/manifest.c
+++ src/manifest.c
@@ -3024,11 +3024,11 @@
3024 CARD_STR2(K, p->zTicketUuid);
3025 CARD_STR2(L, p->zWikiTitle);
3026 ISA( CFTYPE_CLUSTER ){
3027 CARD_LETTER(M);
3028 blob_append_char(b, '[');
3029 for( i = 0; i < p->nCChild; ++i ){
3030 if( i>0 ) blob_append_char(b, ',');
3031 blob_appendf(b, "%!j", p->azCChild[i]);
3032 }
3033 blob_append_char(b, ']');
3034 }
@@ -3059,11 +3059,11 @@
3059 }
3060 CARD_STR2(R, p->zRepoCksum);
3061 if( p->nTag ){
3062 CARD_LETTER(T);
3063 blob_append_char(b, '[');
3064 for( i = 0; i < p->nTag; ++i ){
3065 const char *zName = p->aTag[i].zName;
3066 if( i>0 ) blob_append_char(b, ',');
3067 blob_append_char(b, '{');
3068 blob_appendf(b, "\"type\":\"%c\"", *zName);
3069 KVP_STR(1, name, &zName[1]);
3070
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -278,11 +278,11 @@
278278
struct Blob *head_row,
279279
struct Blob *rows,
280280
void *opaque
281281
){
282282
INTER_BLOCK(ob);
283
- blob_append_literal(ob, "<table>\n");
283
+ blob_append_literal(ob, "<table class='md-table'>\n");
284284
if( head_row && blob_size(head_row)>0 ){
285285
blob_append_literal(ob, "<thead>\n");
286286
blob_appendb(ob, head_row);
287287
blob_append_literal(ob, "</thead>\n<tbody>\n");
288288
}
@@ -696,11 +696,11 @@
696696
){
697697
blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698698
}
699699
blob_append(&bSrc, zSrc, nSrc)
700700
/*have to dup input to ensure a NUL-terminated source string */;
701
- pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
701
+ pikchr_process(blob_str(&bSrc), pikFlags, ob);
702702
blob_reset(&bSrc);
703703
}
704704
705705
/* Invoked for `...` blocks where there are nSep grave accents in a
706706
** row that serve as the delimiter. According to CommonMark:
707707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -278,11 +278,11 @@
278 struct Blob *head_row,
279 struct Blob *rows,
280 void *opaque
281 ){
282 INTER_BLOCK(ob);
283 blob_append_literal(ob, "<table>\n");
284 if( head_row && blob_size(head_row)>0 ){
285 blob_append_literal(ob, "<thead>\n");
286 blob_appendb(ob, head_row);
287 blob_append_literal(ob, "</thead>\n<tbody>\n");
288 }
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -278,11 +278,11 @@
278 struct Blob *head_row,
279 struct Blob *rows,
280 void *opaque
281 ){
282 INTER_BLOCK(ob);
283 blob_append_literal(ob, "<table class='md-table'>\n");
284 if( head_row && blob_size(head_row)>0 ){
285 blob_append_literal(ob, "<thead>\n");
286 blob_appendb(ob, head_row);
287 blob_append_literal(ob, "</thead>\n<tbody>\n");
288 }
@@ -696,11 +696,11 @@
696 ){
697 blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
698 }
699 blob_append(&bSrc, zSrc, nSrc)
700 /*have to dup input to ensure a NUL-terminated source string */;
701 pikchr_process(blob_str(&bSrc), pikFlags, ob);
702 blob_reset(&bSrc);
703 }
704
705 /* Invoked for `...` blocks where there are nSep grave accents in a
706 ** row that serve as the delimiter. According to CommonMark:
707
+65 -6
--- src/name.c
+++ src/name.c
@@ -1816,11 +1816,11 @@
18161816
}
18171817
if( bUnclst==0 ){
18181818
style_submenu_element("Unclustered","bloblist?unclustered");
18191819
}
18201820
if( g.perm.Admin ){
1821
- style_submenu_element("Artifact Log", "rcvfromlist");
1821
+ style_submenu_element("Xfer Log", "rcvfromlist");
18221822
}
18231823
if( !phantomOnly ){
18241824
style_submenu_element("Phantoms", "bloblist?phan");
18251825
}
18261826
style_submenu_element("Clusters","clusterlist");
@@ -2005,11 +2005,11 @@
20052005
void phantom_list_page(void){
20062006
login_check_credentials();
20072007
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
20082008
style_header("Public Phantom Artifacts");
20092009
if( g.perm.Admin ){
2010
- style_submenu_element("Artifact Log", "rcvfromlist");
2010
+ style_submenu_element("Xfer Log", "rcvfromlist");
20112011
style_submenu_element("Artifact List", "bloblist");
20122012
}
20132013
if( g.perm.Write ){
20142014
style_submenu_element("Artifact Stats", "artifact_stats");
20152015
}
@@ -2030,11 +2030,11 @@
20302030
int n = atoi(PD("n","250"));
20312031
20322032
login_check_credentials();
20332033
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
20342034
if( g.perm.Admin ){
2035
- style_submenu_element("Artifact Log", "rcvfromlist");
2035
+ style_submenu_element("Xfer Log", "rcvfromlist");
20362036
}
20372037
if( g.perm.Write ){
20382038
style_submenu_element("Artifact Stats", "artifact_stats");
20392039
}
20402040
style_submenu_element("All Artifacts", "bloblist");
@@ -2203,15 +2203,74 @@
22032203
/*
22042204
** COMMAND: test-phantoms
22052205
**
22062206
** Usage: %fossil test-phantoms
22072207
**
2208
-** Show all phantom artifacts
2208
+** Show all phantom artifacts. A phantom artifact is one for which there
2209
+** is no content. Options:
2210
+**
2211
+** --count Show only a count of the number of phantoms.
2212
+** --delta Show all delta-phantoms. A delta-phantom is a
2213
+** artifact for which there is a delta but the delta
2214
+** source is a phantom.
2215
+** --list Just list the phantoms. Do not try to describe them.
22092216
*/
22102217
void test_phatoms_cmd(void){
2218
+ int bDelta;
2219
+ int bList;
2220
+ int bCount;
2221
+ unsigned nPhantom = 0;
2222
+ unsigned nDeltaPhantom = 0;
22112223
db_find_and_open_repository(0,0);
2212
- describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
2224
+ bDelta = find_option("delta", 0, 0)!=0;
2225
+ bList = find_option("list", 0, 0)!=0;
2226
+ bCount = find_option("count", 0, 0)!=0;
2227
+ verify_all_options();
2228
+ if( bList || bCount ){
2229
+ Stmt q1, q2;
2230
+ db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0");
2231
+ while( db_step(&q1)==SQLITE_ROW ){
2232
+ int rid = db_column_int(&q1, 0);
2233
+ nPhantom++;
2234
+ if( !bCount ){
2235
+ fossil_print("%S (%d)\n", db_column_text(&q1,1), rid);
2236
+ }
2237
+ db_prepare(&q2,
2238
+ "WITH RECURSIVE deltasof(rid) AS ("
2239
+ " SELECT rid FROM delta WHERE srcid=%d"
2240
+ " UNION"
2241
+ " SELECT delta.rid FROM deltasof, delta"
2242
+ " WHERE delta.srcid=deltasof.rid)"
2243
+ "SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob"
2244
+ " ON blob.rid=deltasof.rid", rid
2245
+ );
2246
+ while( db_step(&q2)==SQLITE_ROW ){
2247
+ nDeltaPhantom++;
2248
+ if( !bCount ){
2249
+ fossil_print(" %S (%d)\n", db_column_text(&q2,1),
2250
+ db_column_int(&q2,0));
2251
+ }
2252
+ }
2253
+ db_finalize(&q2);
2254
+ }
2255
+ db_finalize(&q1);
2256
+ if( nPhantom ){
2257
+ fossil_print("Phantoms: %u Delta-phantoms: %u\n",
2258
+ nPhantom, nDeltaPhantom);
2259
+ }
2260
+ }else if( bDelta ){
2261
+ describe_artifacts_to_stdout(
2262
+ "IN (WITH RECURSIVE delta_phantom(rid) AS (\n"
2263
+ " SELECT delta.rid FROM blob, delta\n"
2264
+ " WHERE blob.size<0 AND delta.srcid=blob.rid\n"
2265
+ " UNION\n"
2266
+ " SELECT delta.rid FROM delta_phantom, delta\n"
2267
+ " WHERE delta.srcid=delta_phantom.rid)\n"
2268
+ " SELECT rid FROM delta_phantom)", 0);
2269
+ }else{
2270
+ describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
2271
+ }
22132272
}
22142273
22152274
/* Maximum number of collision examples to remember */
22162275
#define MAX_COLLIDE 25
22172276
@@ -2311,11 +2370,11 @@
23112370
login_check_credentials();
23122371
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
23132372
style_header("All Cluster Artifacts");
23142373
style_submenu_element("All Artifactst", "bloblist");
23152374
if( g.perm.Admin ){
2316
- style_submenu_element("Artifact Log", "rcvfromlist");
2375
+ style_submenu_element("Xfer Log", "rcvfromlist");
23172376
}
23182377
style_submenu_element("Phantoms", "bloblist?phan");
23192378
if( g.perm.Write ){
23202379
style_submenu_element("Artifact Stats", "artifact_stats");
23212380
}
23222381
--- src/name.c
+++ src/name.c
@@ -1816,11 +1816,11 @@
1816 }
1817 if( bUnclst==0 ){
1818 style_submenu_element("Unclustered","bloblist?unclustered");
1819 }
1820 if( g.perm.Admin ){
1821 style_submenu_element("Artifact Log", "rcvfromlist");
1822 }
1823 if( !phantomOnly ){
1824 style_submenu_element("Phantoms", "bloblist?phan");
1825 }
1826 style_submenu_element("Clusters","clusterlist");
@@ -2005,11 +2005,11 @@
2005 void phantom_list_page(void){
2006 login_check_credentials();
2007 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2008 style_header("Public Phantom Artifacts");
2009 if( g.perm.Admin ){
2010 style_submenu_element("Artifact Log", "rcvfromlist");
2011 style_submenu_element("Artifact List", "bloblist");
2012 }
2013 if( g.perm.Write ){
2014 style_submenu_element("Artifact Stats", "artifact_stats");
2015 }
@@ -2030,11 +2030,11 @@
2030 int n = atoi(PD("n","250"));
2031
2032 login_check_credentials();
2033 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2034 if( g.perm.Admin ){
2035 style_submenu_element("Artifact Log", "rcvfromlist");
2036 }
2037 if( g.perm.Write ){
2038 style_submenu_element("Artifact Stats", "artifact_stats");
2039 }
2040 style_submenu_element("All Artifacts", "bloblist");
@@ -2203,15 +2203,74 @@
2203 /*
2204 ** COMMAND: test-phantoms
2205 **
2206 ** Usage: %fossil test-phantoms
2207 **
2208 ** Show all phantom artifacts
 
 
 
 
 
 
 
2209 */
2210 void test_phatoms_cmd(void){
 
 
 
 
 
2211 db_find_and_open_repository(0,0);
2212 describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2213 }
2214
2215 /* Maximum number of collision examples to remember */
2216 #define MAX_COLLIDE 25
2217
@@ -2311,11 +2370,11 @@
2311 login_check_credentials();
2312 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2313 style_header("All Cluster Artifacts");
2314 style_submenu_element("All Artifactst", "bloblist");
2315 if( g.perm.Admin ){
2316 style_submenu_element("Artifact Log", "rcvfromlist");
2317 }
2318 style_submenu_element("Phantoms", "bloblist?phan");
2319 if( g.perm.Write ){
2320 style_submenu_element("Artifact Stats", "artifact_stats");
2321 }
2322
--- src/name.c
+++ src/name.c
@@ -1816,11 +1816,11 @@
1816 }
1817 if( bUnclst==0 ){
1818 style_submenu_element("Unclustered","bloblist?unclustered");
1819 }
1820 if( g.perm.Admin ){
1821 style_submenu_element("Xfer Log", "rcvfromlist");
1822 }
1823 if( !phantomOnly ){
1824 style_submenu_element("Phantoms", "bloblist?phan");
1825 }
1826 style_submenu_element("Clusters","clusterlist");
@@ -2005,11 +2005,11 @@
2005 void phantom_list_page(void){
2006 login_check_credentials();
2007 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2008 style_header("Public Phantom Artifacts");
2009 if( g.perm.Admin ){
2010 style_submenu_element("Xfer Log", "rcvfromlist");
2011 style_submenu_element("Artifact List", "bloblist");
2012 }
2013 if( g.perm.Write ){
2014 style_submenu_element("Artifact Stats", "artifact_stats");
2015 }
@@ -2030,11 +2030,11 @@
2030 int n = atoi(PD("n","250"));
2031
2032 login_check_credentials();
2033 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2034 if( g.perm.Admin ){
2035 style_submenu_element("Xfer Log", "rcvfromlist");
2036 }
2037 if( g.perm.Write ){
2038 style_submenu_element("Artifact Stats", "artifact_stats");
2039 }
2040 style_submenu_element("All Artifacts", "bloblist");
@@ -2203,15 +2203,74 @@
2203 /*
2204 ** COMMAND: test-phantoms
2205 **
2206 ** Usage: %fossil test-phantoms
2207 **
2208 ** Show all phantom artifacts. A phantom artifact is one for which there
2209 ** is no content. Options:
2210 **
2211 ** --count Show only a count of the number of phantoms.
2212 ** --delta Show all delta-phantoms. A delta-phantom is a
2213 ** artifact for which there is a delta but the delta
2214 ** source is a phantom.
2215 ** --list Just list the phantoms. Do not try to describe them.
2216 */
2217 void test_phatoms_cmd(void){
2218 int bDelta;
2219 int bList;
2220 int bCount;
2221 unsigned nPhantom = 0;
2222 unsigned nDeltaPhantom = 0;
2223 db_find_and_open_repository(0,0);
2224 bDelta = find_option("delta", 0, 0)!=0;
2225 bList = find_option("list", 0, 0)!=0;
2226 bCount = find_option("count", 0, 0)!=0;
2227 verify_all_options();
2228 if( bList || bCount ){
2229 Stmt q1, q2;
2230 db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0");
2231 while( db_step(&q1)==SQLITE_ROW ){
2232 int rid = db_column_int(&q1, 0);
2233 nPhantom++;
2234 if( !bCount ){
2235 fossil_print("%S (%d)\n", db_column_text(&q1,1), rid);
2236 }
2237 db_prepare(&q2,
2238 "WITH RECURSIVE deltasof(rid) AS ("
2239 " SELECT rid FROM delta WHERE srcid=%d"
2240 " UNION"
2241 " SELECT delta.rid FROM deltasof, delta"
2242 " WHERE delta.srcid=deltasof.rid)"
2243 "SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob"
2244 " ON blob.rid=deltasof.rid", rid
2245 );
2246 while( db_step(&q2)==SQLITE_ROW ){
2247 nDeltaPhantom++;
2248 if( !bCount ){
2249 fossil_print(" %S (%d)\n", db_column_text(&q2,1),
2250 db_column_int(&q2,0));
2251 }
2252 }
2253 db_finalize(&q2);
2254 }
2255 db_finalize(&q1);
2256 if( nPhantom ){
2257 fossil_print("Phantoms: %u Delta-phantoms: %u\n",
2258 nPhantom, nDeltaPhantom);
2259 }
2260 }else if( bDelta ){
2261 describe_artifacts_to_stdout(
2262 "IN (WITH RECURSIVE delta_phantom(rid) AS (\n"
2263 " SELECT delta.rid FROM blob, delta\n"
2264 " WHERE blob.size<0 AND delta.srcid=blob.rid\n"
2265 " UNION\n"
2266 " SELECT delta.rid FROM delta_phantom, delta\n"
2267 " WHERE delta.srcid=delta_phantom.rid)\n"
2268 " SELECT rid FROM delta_phantom)", 0);
2269 }else{
2270 describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
2271 }
2272 }
2273
2274 /* Maximum number of collision examples to remember */
2275 #define MAX_COLLIDE 25
2276
@@ -2311,11 +2370,11 @@
2370 login_check_credentials();
2371 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2372 style_header("All Cluster Artifacts");
2373 style_submenu_element("All Artifactst", "bloblist");
2374 if( g.perm.Admin ){
2375 style_submenu_element("Xfer Log", "rcvfromlist");
2376 }
2377 style_submenu_element("Phantoms", "bloblist?phan");
2378 if( g.perm.Write ){
2379 style_submenu_element("Artifact Stats", "artifact_stats");
2380 }
2381
+89 -182
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
2727
/* The first two must match the values from pikchr.c */
2828
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
2929
#define PIKCHR_PROCESS_DARK_MODE 0x0002
3030
/* end of flags supported directly by pikchr() */
3131
#define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32
-#define PIKCHR_PROCESS_TH1 0x0004
33
-#define PIKCHR_PROCESS_TH1_NOSVG 0x0008
3432
#define PIKCHR_PROCESS_NONCE 0x0010
3533
#define PIKCHR_PROCESS_ERR_PRE 0x0020
3634
#define PIKCHR_PROCESS_SRC 0x0040
3735
#define PIKCHR_PROCESS_DIV 0x0080
3836
#define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
4341
#define PIKCHR_PROCESS_DIV_SOURCE 0x2000
4442
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
4543
#endif
4644
4745
/*
48
-** Processes a pikchr script, optionally with embedded TH1, and
49
-** produces HTML code for it. zIn is the NUL-terminated input
46
+** Processes a pikchr script. zIn is the NUL-terminated input
5047
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51
-** flags documented below. thFlags may be a bitmask of any of the
52
-** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53
-** appending to it without modifying any prior contents.
48
+** flags documented below. Output is sent to pOut,
5449
**
55
-** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56
-** processing failed. In either case, the error message (if any) from
57
-** TH1 or pikchr will be appended to pOut.
50
+** Returns 0 on success, or non-zero if pikchr processing failed.
51
+** In either case, the error message (if any) from pikchr will be
52
+** appended to pOut.
5853
**
5954
** pikFlags flag descriptions:
6055
**
61
-** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62
-** init flags specified in the 3rd argument. If thFlags is non-0 then
63
-** this flag is assumed even if it is not specified.
64
-**
65
-** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66
-** TH1 eval step, thus the output will be (presumably) a
67
-** TH1-generated/processed pikchr script (or whatever else the TH1
68
-** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69
-** if it is not specified.
70
-**
71
-** All of the remaining flags listed below are ignored if
72
-** PIKCHR_PROCESS_TH1_NOSVG is specified!
73
-**
7456
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
7557
** element which specifies a max-width style value based on the SVG's
7658
** calculated size. This flag has multiple mutually exclusive forms:
7759
**
7860
** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
11698
**
11799
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118100
** error report is wrapped in a PRE element, else it is retained
119101
** as-is (intended only for console output).
120102
*/
121
-int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122
- Blob * pOut){
123
- Blob bIn = empty_blob;
103
+int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
124104
int isErr = 0;
105
+ int w = 0, h = 0;
106
+ char *zOut;
125107
const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126108
? safe_html_nonce(1) : 0;
127109
128110
if(!(PIKCHR_PROCESS_DIV & pikFlags)
129111
/* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135117
| PIKCHR_PROCESS_DIV_SOURCE_INLINE
136118
| PIKCHR_PROCESS_DIV_TOGGLE
137119
) & pikFlags){
138120
pikFlags |= PIKCHR_PROCESS_DIV;
139121
}
140
- if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141
- /* If any TH1_xxx flags are set, set TH1 */
142
- && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143
- pikFlags |= PIKCHR_PROCESS_TH1;
144
- }
145
- if(zNonce){
146
- blob_appendf(pOut, "%s\n", zNonce);
147
- }
148
- if(PIKCHR_PROCESS_TH1 & pikFlags){
149
- Blob out = empty_blob;
150
- isErr = Th_RenderToBlob(zIn, &out, thFlags)
151
- ? 1 : 0;
152
- if(isErr){
153
- blob_append(pOut, blob_str(&out), blob_size(&out));
154
- blob_reset(&out);
155
- }else{
156
- bIn = out;
157
- }
158
- }else{
159
- blob_init(&bIn, zIn, -1);
160
- }
161
- if(!isErr){
162
- if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163
- blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164
- }else{
165
- int w = 0, h = 0;
166
- const char * zContent = blob_str(&bIn);
167
- char *zOut;
168
- zOut = pikchr(zContent, "pikchr",
169
- 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170
- &w, &h);
171
- if( w>0 && h>0 ){
172
- const char * zClassToggle = "";
173
- const char * zClassSource = "";
174
- const char * zWrapperClass = "";
175
- if(PIKCHR_PROCESS_DIV & pikFlags){
176
- if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177
- zWrapperClass = " center";
178
- }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179
- zWrapperClass = " indent";
180
- }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181
- zWrapperClass = " float-left";
182
- }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183
- zWrapperClass = " float-right";
184
- }
185
- if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186
- zClassToggle = " toggle";
187
- }
188
- if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189
- if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190
- zClassSource = " source source-inline";
191
- }else{
192
- zClassSource = " source-inline";
193
- }
194
- pikFlags |= PIKCHR_PROCESS_SRC;
195
- }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196
- zClassSource = " source";
197
- pikFlags |= PIKCHR_PROCESS_SRC;
198
- }
199
- blob_appendf(pOut,"<div class='pikchr-wrapper"
200
- "%s%s%s'>"
201
- "<div class=\"pikchr-svg\" "
202
- "style=\"max-width:%dpx\">\n",
203
- zWrapperClass/*safe-for-%s*/,
204
- zClassToggle/*safe-for-%s*/,
205
- zClassSource/*safe-for-%s*/, w);
206
- }
207
- blob_append(pOut, zOut, -1);
208
- if(PIKCHR_PROCESS_DIV & pikFlags){
209
- blob_append(pOut, "</div>\n", 7);
210
- }
211
- if(PIKCHR_PROCESS_SRC & pikFlags){
212
- static int counter = 0;
213
- ++counter;
214
- blob_appendf(pOut, "<div class='pikchr-src'>"
215
- "<pre id='pikchr-src-%d'>%h</pre>"
216
- "<span class='hidden'>"
217
- "<a href='%R/pikchrshow?fromSession' "
218
- "class='pikchr-src-pikchrshow' target='_new-%d' "
219
- "data-pikchrid='pikchr-src-%d' "
220
- "title='Open this pikchr in /pikchrshow'"
221
- ">&rarr; /pikchrshow</a></span>"
222
- "</div>\n",
223
- counter, blob_str(&bIn), counter, counter);
224
- }
225
- if(PIKCHR_PROCESS_DIV & pikFlags){
226
- blob_append(pOut, "</div>\n", 7);
227
- }
228
- }else{
229
- isErr = 2;
230
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231
- blob_append(pOut, "<pre class='error'>\n", 20);
232
- }
233
- blob_appendf(pOut, "%h", zOut);
234
- if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235
- blob_append(pOut, "\n</pre>\n", 8);
236
- }
237
- }
238
- fossil_free(zOut);
239
- }
240
- }
241
- if(zNonce){
242
- blob_appendf(pOut, "%s\n", zNonce);
243
- }
244
- blob_reset(&bIn);
122
+ if(zNonce){
123
+ blob_appendf(pOut, "%s\n", zNonce);
124
+ }
125
+ zOut = pikchr(zIn, "pikchr",
126
+ 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127
+ &w, &h);
128
+ if( w>0 && h>0 ){
129
+ const char * zClassToggle = "";
130
+ const char * zClassSource = "";
131
+ const char * zWrapperClass = "";
132
+ if(PIKCHR_PROCESS_DIV & pikFlags){
133
+ if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134
+ zWrapperClass = " center";
135
+ }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136
+ zWrapperClass = " indent";
137
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138
+ zWrapperClass = " float-left";
139
+ }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140
+ zWrapperClass = " float-right";
141
+ }
142
+ if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143
+ zClassToggle = " toggle";
144
+ }
145
+ if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146
+ if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147
+ zClassSource = " source source-inline";
148
+ }else{
149
+ zClassSource = " source-inline";
150
+ }
151
+ pikFlags |= PIKCHR_PROCESS_SRC;
152
+ }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153
+ zClassSource = " source";
154
+ pikFlags |= PIKCHR_PROCESS_SRC;
155
+ }
156
+ blob_appendf(pOut,"<div class='pikchr-wrapper"
157
+ "%s%s%s'>"
158
+ "<div class=\"pikchr-svg\" "
159
+ "style=\"max-width:%dpx\">\n",
160
+ zWrapperClass/*safe-for-%s*/,
161
+ zClassToggle/*safe-for-%s*/,
162
+ zClassSource/*safe-for-%s*/, w);
163
+ }
164
+ blob_append(pOut, zOut, -1);
165
+ if(PIKCHR_PROCESS_DIV & pikFlags){
166
+ blob_append(pOut, "</div>\n", 7);
167
+ }
168
+ if(PIKCHR_PROCESS_SRC & pikFlags){
169
+ static int counter = 0;
170
+ ++counter;
171
+ blob_appendf(pOut, "<div class='pikchr-src'>"
172
+ "<pre id='pikchr-src-%d'>%h</pre>"
173
+ "<span class='hidden'>"
174
+ "<a href='%R/pikchrshow?fromSession' "
175
+ "class='pikchr-src-pikchrshow' target='_new-%d' "
176
+ "data-pikchrid='pikchr-src-%d' "
177
+ "title='Open this pikchr in /pikchrshow'"
178
+ ">&rarr; /pikchrshow</a></span>"
179
+ "</div>\n",
180
+ counter, zIn, counter, counter);
181
+ }
182
+ if(PIKCHR_PROCESS_DIV & pikFlags){
183
+ blob_append(pOut, "</div>\n", 7);
184
+ }
185
+ }else{
186
+ isErr = 2;
187
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188
+ blob_append(pOut, "<pre class='error'>\n", 20);
189
+ }
190
+ blob_appendf(pOut, "%h", zOut);
191
+ if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192
+ blob_append(pOut, "\n</pre>\n", 8);
193
+ }
194
+ }
195
+ fossil_free(zOut);
196
+ if(zNonce){
197
+ blob_appendf(pOut, "%s\n", zNonce);
198
+ }
245199
return isErr;
246200
}
247201
248202
/*
249203
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279233
TODO: respond with JSON instead.*/
280234
cgi_set_content_type("text/html");
281235
if(zContent && *zContent){
282236
Blob out = empty_blob;
283237
const int isErr =
284
- pikchr_process(zContent, pikFlags, 0, &out);
238
+ pikchr_process(zContent, pikFlags, &out);
285239
if(isErr){
286240
cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287241
}
288242
CX("%b", &out);
289243
blob_reset(&out);
@@ -384,11 +338,11 @@
384338
/* Reminder: Firefox does not properly flexbox a LEGEND
385339
element, always flowing it in column mode. */);
386340
CX("<div id='pikchrshow-output'>");
387341
if(*zContent){
388342
Blob out = empty_blob;
389
- pikchr_process(zContent, pikFlags, 0, &out);
343
+ pikchr_process(zContent, pikFlags, &out);
390344
CX("%b", &out);
391345
blob_reset(&out);
392346
} CX("</div>"/*#pikchrshow-output*/);
393347
} CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394348
} CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561515
**
562516
** -src Store the input pikchr's source code in the output as
563517
** a separate element adjacent to the SVG one. Implied
564518
** by -div-source.
565519
**
566
-**
567
-** -th Process the input using TH1 before passing it to pikchr
568
-**
569
-** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570
-** pikchr script uses '$' for its own purposes and that
571
-** causes issues. This only affects parsing of '$' outside
572
-** of TH1 script blocks. Code in such blocks is unaffected.
573
-**
574
-** -th-nosvg When using -th, output the post-TH1'd script
575
-** instead of the pikchr-rendered output
576
-**
577
-** -th-trace Trace TH1 execution (for debugging purposes)
578
-**
579520
** -dark Change pikchr colors to assume a dark-mode theme.
580521
**
581522
**
582523
** The -div-indent/center/left/right flags may not be combined.
583
-**
584
-** TH1-related Notes and Caveats:
585
-**
586
-** If the -th flag is used, this command must open a fossil database
587
-** for certain functionality to work (via a check-out or the -R REPO
588
-** flag). If opening a db fails, execution will continue but any TH1
589
-** commands which require a db will trigger a fatal error.
590
-**
591
-** In Fossil skins, TH1 variables in the form $varName are expanded
592
-** as-is and those in the form $<varName> are htmlized in the
593
-** resulting output. This processor disables the htmlizing step, so $x
594
-** and $<x> are equivalent unless the TH1-processed pikchr script
595
-** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596
-** that option will interfere with pikchr output, however, e.g. by
597
-** HTML-encoding double-quotes.
598
-**
599
-** Many of the fossil-installed TH1 functions simply do not make any
600
-** sense for pikchr scripts.
601524
*/
602525
void pikchr_cmd(void){
603526
Blob bIn = empty_blob;
604527
Blob bOut = empty_blob;
605528
const char * zInfile = "-";
606529
const char * zOutfile = "-";
607
- const int fTh1 = find_option("th",0,0)!=0;
608
- const int fNosvg = find_option("th-nosvg",0,0)!=0;
609530
int isErr = 0;
610531
int pikFlags = find_option("src",0,0)!=0
611532
? PIKCHR_PROCESS_SRC : 0;
612
- u32 fThFlags = TH_INIT_NO_ENCODE
613
- | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
-
615
- Th_InitTraceLog()/*processes -th-trace flag*/;
616533
617534
if(find_option("div",0,0)!=0){
618535
pikFlags |= PIKCHR_PROCESS_DIV;
619536
}else if(find_option("div-indent",0,0)!=0){
620537
pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644561
}
645562
if(g.argc>3){
646563
zOutfile = g.argv[3];
647564
}
648565
blob_read_from_file(&bIn, zInfile, ExtFILE);
649
- if(fTh1){
650
- db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651
- /* ^^^ needed for certain TH1 functions to work */;
652
- pikFlags |= PIKCHR_PROCESS_TH1;
653
- if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654
- }
655
- isErr = pikchr_process(blob_str(&bIn), pikFlags,
656
- fTh1 ? fThFlags : 0, &bOut);
566
+ isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
657567
if(isErr){
658
- fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659
- 1==isErr ? ' ' : '\n',
660
- &bOut);
568
+ fossil_fatal("pikchr ERROR: %b", &bOut);
661569
}else{
662570
blob_write_to_file(&bOut, zOutfile);
663571
}
664
- Th_PrintTraceLog();
665572
blob_reset(&bIn);
666573
blob_reset(&bOut);
667574
}
668575
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
32 #define PIKCHR_PROCESS_TH1 0x0004
33 #define PIKCHR_PROCESS_TH1_NOSVG 0x0008
34 #define PIKCHR_PROCESS_NONCE 0x0010
35 #define PIKCHR_PROCESS_ERR_PRE 0x0020
36 #define PIKCHR_PROCESS_SRC 0x0040
37 #define PIKCHR_PROCESS_DIV 0x0080
38 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
43 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
44 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
45 #endif
46
47 /*
48 ** Processes a pikchr script, optionally with embedded TH1, and
49 ** produces HTML code for it. zIn is the NUL-terminated input
50 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
51 ** flags documented below. thFlags may be a bitmask of any of the
52 ** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
53 ** appending to it without modifying any prior contents.
54 **
55 ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
56 ** processing failed. In either case, the error message (if any) from
57 ** TH1 or pikchr will be appended to pOut.
58 **
59 ** pikFlags flag descriptions:
60 **
61 ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
62 ** init flags specified in the 3rd argument. If thFlags is non-0 then
63 ** this flag is assumed even if it is not specified.
64 **
65 ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
66 ** TH1 eval step, thus the output will be (presumably) a
67 ** TH1-generated/processed pikchr script (or whatever else the TH1
68 ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
69 ** if it is not specified.
70 **
71 ** All of the remaining flags listed below are ignored if
72 ** PIKCHR_PROCESS_TH1_NOSVG is specified!
73 **
74 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
75 ** element which specifies a max-width style value based on the SVG's
76 ** calculated size. This flag has multiple mutually exclusive forms:
77 **
78 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
116 **
117 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
118 ** error report is wrapped in a PRE element, else it is retained
119 ** as-is (intended only for console output).
120 */
121 int pikchr_process(const char * zIn, int pikFlags, int thFlags,
122 Blob * pOut){
123 Blob bIn = empty_blob;
124 int isErr = 0;
 
 
125 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
126 ? safe_html_nonce(1) : 0;
127
128 if(!(PIKCHR_PROCESS_DIV & pikFlags)
129 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
135 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
136 | PIKCHR_PROCESS_DIV_TOGGLE
137 ) & pikFlags){
138 pikFlags |= PIKCHR_PROCESS_DIV;
139 }
140 if(!(PIKCHR_PROCESS_TH1 & pikFlags)
141 /* If any TH1_xxx flags are set, set TH1 */
142 && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
143 pikFlags |= PIKCHR_PROCESS_TH1;
144 }
145 if(zNonce){
146 blob_appendf(pOut, "%s\n", zNonce);
147 }
148 if(PIKCHR_PROCESS_TH1 & pikFlags){
149 Blob out = empty_blob;
150 isErr = Th_RenderToBlob(zIn, &out, thFlags)
151 ? 1 : 0;
152 if(isErr){
153 blob_append(pOut, blob_str(&out), blob_size(&out));
154 blob_reset(&out);
155 }else{
156 bIn = out;
157 }
158 }else{
159 blob_init(&bIn, zIn, -1);
160 }
161 if(!isErr){
162 if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
163 blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
164 }else{
165 int w = 0, h = 0;
166 const char * zContent = blob_str(&bIn);
167 char *zOut;
168 zOut = pikchr(zContent, "pikchr",
169 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
170 &w, &h);
171 if( w>0 && h>0 ){
172 const char * zClassToggle = "";
173 const char * zClassSource = "";
174 const char * zWrapperClass = "";
175 if(PIKCHR_PROCESS_DIV & pikFlags){
176 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
177 zWrapperClass = " center";
178 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
179 zWrapperClass = " indent";
180 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
181 zWrapperClass = " float-left";
182 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
183 zWrapperClass = " float-right";
184 }
185 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
186 zClassToggle = " toggle";
187 }
188 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
189 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
190 zClassSource = " source source-inline";
191 }else{
192 zClassSource = " source-inline";
193 }
194 pikFlags |= PIKCHR_PROCESS_SRC;
195 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
196 zClassSource = " source";
197 pikFlags |= PIKCHR_PROCESS_SRC;
198 }
199 blob_appendf(pOut,"<div class='pikchr-wrapper"
200 "%s%s%s'>"
201 "<div class=\"pikchr-svg\" "
202 "style=\"max-width:%dpx\">\n",
203 zWrapperClass/*safe-for-%s*/,
204 zClassToggle/*safe-for-%s*/,
205 zClassSource/*safe-for-%s*/, w);
206 }
207 blob_append(pOut, zOut, -1);
208 if(PIKCHR_PROCESS_DIV & pikFlags){
209 blob_append(pOut, "</div>\n", 7);
210 }
211 if(PIKCHR_PROCESS_SRC & pikFlags){
212 static int counter = 0;
213 ++counter;
214 blob_appendf(pOut, "<div class='pikchr-src'>"
215 "<pre id='pikchr-src-%d'>%h</pre>"
216 "<span class='hidden'>"
217 "<a href='%R/pikchrshow?fromSession' "
218 "class='pikchr-src-pikchrshow' target='_new-%d' "
219 "data-pikchrid='pikchr-src-%d' "
220 "title='Open this pikchr in /pikchrshow'"
221 ">&rarr; /pikchrshow</a></span>"
222 "</div>\n",
223 counter, blob_str(&bIn), counter, counter);
224 }
225 if(PIKCHR_PROCESS_DIV & pikFlags){
226 blob_append(pOut, "</div>\n", 7);
227 }
228 }else{
229 isErr = 2;
230 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
231 blob_append(pOut, "<pre class='error'>\n", 20);
232 }
233 blob_appendf(pOut, "%h", zOut);
234 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
235 blob_append(pOut, "\n</pre>\n", 8);
236 }
237 }
238 fossil_free(zOut);
239 }
240 }
241 if(zNonce){
242 blob_appendf(pOut, "%s\n", zNonce);
243 }
244 blob_reset(&bIn);
245 return isErr;
246 }
247
248 /*
249 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
279 TODO: respond with JSON instead.*/
280 cgi_set_content_type("text/html");
281 if(zContent && *zContent){
282 Blob out = empty_blob;
283 const int isErr =
284 pikchr_process(zContent, pikFlags, 0, &out);
285 if(isErr){
286 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
287 }
288 CX("%b", &out);
289 blob_reset(&out);
@@ -384,11 +338,11 @@
384 /* Reminder: Firefox does not properly flexbox a LEGEND
385 element, always flowing it in column mode. */);
386 CX("<div id='pikchrshow-output'>");
387 if(*zContent){
388 Blob out = empty_blob;
389 pikchr_process(zContent, pikFlags, 0, &out);
390 CX("%b", &out);
391 blob_reset(&out);
392 } CX("</div>"/*#pikchrshow-output*/);
393 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
394 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
561 **
562 ** -src Store the input pikchr's source code in the output as
563 ** a separate element adjacent to the SVG one. Implied
564 ** by -div-source.
565 **
566 **
567 ** -th Process the input using TH1 before passing it to pikchr
568 **
569 ** -th-novar Disable $var and $<var> TH1 processing. Use this if the
570 ** pikchr script uses '$' for its own purposes and that
571 ** causes issues. This only affects parsing of '$' outside
572 ** of TH1 script blocks. Code in such blocks is unaffected.
573 **
574 ** -th-nosvg When using -th, output the post-TH1'd script
575 ** instead of the pikchr-rendered output
576 **
577 ** -th-trace Trace TH1 execution (for debugging purposes)
578 **
579 ** -dark Change pikchr colors to assume a dark-mode theme.
580 **
581 **
582 ** The -div-indent/center/left/right flags may not be combined.
583 **
584 ** TH1-related Notes and Caveats:
585 **
586 ** If the -th flag is used, this command must open a fossil database
587 ** for certain functionality to work (via a check-out or the -R REPO
588 ** flag). If opening a db fails, execution will continue but any TH1
589 ** commands which require a db will trigger a fatal error.
590 **
591 ** In Fossil skins, TH1 variables in the form $varName are expanded
592 ** as-is and those in the form $<varName> are htmlized in the
593 ** resulting output. This processor disables the htmlizing step, so $x
594 ** and $<x> are equivalent unless the TH1-processed pikchr script
595 ** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
596 ** that option will interfere with pikchr output, however, e.g. by
597 ** HTML-encoding double-quotes.
598 **
599 ** Many of the fossil-installed TH1 functions simply do not make any
600 ** sense for pikchr scripts.
601 */
602 void pikchr_cmd(void){
603 Blob bIn = empty_blob;
604 Blob bOut = empty_blob;
605 const char * zInfile = "-";
606 const char * zOutfile = "-";
607 const int fTh1 = find_option("th",0,0)!=0;
608 const int fNosvg = find_option("th-nosvg",0,0)!=0;
609 int isErr = 0;
610 int pikFlags = find_option("src",0,0)!=0
611 ? PIKCHR_PROCESS_SRC : 0;
612 u32 fThFlags = TH_INIT_NO_ENCODE
613 | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);
614
615 Th_InitTraceLog()/*processes -th-trace flag*/;
616
617 if(find_option("div",0,0)!=0){
618 pikFlags |= PIKCHR_PROCESS_DIV;
619 }else if(find_option("div-indent",0,0)!=0){
620 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
644 }
645 if(g.argc>3){
646 zOutfile = g.argv[3];
647 }
648 blob_read_from_file(&bIn, zInfile, ExtFILE);
649 if(fTh1){
650 db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
651 /* ^^^ needed for certain TH1 functions to work */;
652 pikFlags |= PIKCHR_PROCESS_TH1;
653 if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
654 }
655 isErr = pikchr_process(blob_str(&bIn), pikFlags,
656 fTh1 ? fThFlags : 0, &bOut);
657 if(isErr){
658 fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
659 1==isErr ? ' ' : '\n',
660 &bOut);
661 }else{
662 blob_write_to_file(&bOut, zOutfile);
663 }
664 Th_PrintTraceLog();
665 blob_reset(&bIn);
666 blob_reset(&bOut);
667 }
668
--- src/pikchrshow.c
+++ src/pikchrshow.c
@@ -27,12 +27,10 @@
27 /* The first two must match the values from pikchr.c */
28 #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001
29 #define PIKCHR_PROCESS_DARK_MODE 0x0002
30 /* end of flags supported directly by pikchr() */
31 #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */
 
 
32 #define PIKCHR_PROCESS_NONCE 0x0010
33 #define PIKCHR_PROCESS_ERR_PRE 0x0020
34 #define PIKCHR_PROCESS_SRC 0x0040
35 #define PIKCHR_PROCESS_DIV 0x0080
36 #define PIKCHR_PROCESS_DIV_INDENT 0x0100
@@ -43,36 +41,20 @@
41 #define PIKCHR_PROCESS_DIV_SOURCE 0x2000
42 #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
43 #endif
44
45 /*
46 ** Processes a pikchr script. zIn is the NUL-terminated input
 
47 ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
48 ** flags documented below. Output is sent to pOut,
 
 
49 **
50 ** Returns 0 on success, or non-zero if pikchr processing failed.
51 ** In either case, the error message (if any) from pikchr will be
52 ** appended to pOut.
53 **
54 ** pikFlags flag descriptions:
55 **
 
 
 
 
 
 
 
 
 
 
 
 
 
56 ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
57 ** element which specifies a max-width style value based on the SVG's
58 ** calculated size. This flag has multiple mutually exclusive forms:
59 **
60 ** - PIKCHR_PROCESS_DIV uses default element alignment.
@@ -116,14 +98,14 @@
98 **
99 ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
100 ** error report is wrapped in a PRE element, else it is retained
101 ** as-is (intended only for console output).
102 */
103 int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
 
 
104 int isErr = 0;
105 int w = 0, h = 0;
106 char *zOut;
107 const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
108 ? safe_html_nonce(1) : 0;
109
110 if(!(PIKCHR_PROCESS_DIV & pikFlags)
111 /* If any DIV_xxx flags are set, set DIV */
@@ -135,115 +117,87 @@
117 | PIKCHR_PROCESS_DIV_SOURCE_INLINE
118 | PIKCHR_PROCESS_DIV_TOGGLE
119 ) & pikFlags){
120 pikFlags |= PIKCHR_PROCESS_DIV;
121 }
122 if(zNonce){
123 blob_appendf(pOut, "%s\n", zNonce);
124 }
125 zOut = pikchr(zIn, "pikchr",
126 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
127 &w, &h);
128 if( w>0 && h>0 ){
129 const char * zClassToggle = "";
130 const char * zClassSource = "";
131 const char * zWrapperClass = "";
132 if(PIKCHR_PROCESS_DIV & pikFlags){
133 if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
134 zWrapperClass = " center";
135 }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
136 zWrapperClass = " indent";
137 }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
138 zWrapperClass = " float-left";
139 }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
140 zWrapperClass = " float-right";
141 }
142 if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
143 zClassToggle = " toggle";
144 }
145 if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
146 if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
147 zClassSource = " source source-inline";
148 }else{
149 zClassSource = " source-inline";
150 }
151 pikFlags |= PIKCHR_PROCESS_SRC;
152 }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
153 zClassSource = " source";
154 pikFlags |= PIKCHR_PROCESS_SRC;
155 }
156 blob_appendf(pOut,"<div class='pikchr-wrapper"
157 "%s%s%s'>"
158 "<div class=\"pikchr-svg\" "
159 "style=\"max-width:%dpx\">\n",
160 zWrapperClass/*safe-for-%s*/,
161 zClassToggle/*safe-for-%s*/,
162 zClassSource/*safe-for-%s*/, w);
163 }
164 blob_append(pOut, zOut, -1);
165 if(PIKCHR_PROCESS_DIV & pikFlags){
166 blob_append(pOut, "</div>\n", 7);
167 }
168 if(PIKCHR_PROCESS_SRC & pikFlags){
169 static int counter = 0;
170 ++counter;
171 blob_appendf(pOut, "<div class='pikchr-src'>"
172 "<pre id='pikchr-src-%d'>%h</pre>"
173 "<span class='hidden'>"
174 "<a href='%R/pikchrshow?fromSession' "
175 "class='pikchr-src-pikchrshow' target='_new-%d' "
176 "data-pikchrid='pikchr-src-%d' "
177 "title='Open this pikchr in /pikchrshow'"
178 ">&rarr; /pikchrshow</a></span>"
179 "</div>\n",
180 counter, zIn, counter, counter);
181 }
182 if(PIKCHR_PROCESS_DIV & pikFlags){
183 blob_append(pOut, "</div>\n", 7);
184 }
185 }else{
186 isErr = 2;
187 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
188 blob_append(pOut, "<pre class='error'>\n", 20);
189 }
190 blob_appendf(pOut, "%h", zOut);
191 if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
192 blob_append(pOut, "\n</pre>\n", 8);
193 }
194 }
195 fossil_free(zOut);
196 if(zNonce){
197 blob_appendf(pOut, "%s\n", zNonce);
198 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199 return isErr;
200 }
201
202 /*
203 ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
@@ -279,11 +233,11 @@
233 TODO: respond with JSON instead.*/
234 cgi_set_content_type("text/html");
235 if(zContent && *zContent){
236 Blob out = empty_blob;
237 const int isErr =
238 pikchr_process(zContent, pikFlags, &out);
239 if(isErr){
240 cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
241 }
242 CX("%b", &out);
243 blob_reset(&out);
@@ -384,11 +338,11 @@
338 /* Reminder: Firefox does not properly flexbox a LEGEND
339 element, always flowing it in column mode. */);
340 CX("<div id='pikchrshow-output'>");
341 if(*zContent){
342 Blob out = empty_blob;
343 pikchr_process(zContent, pikFlags, &out);
344 CX("%b", &out);
345 blob_reset(&out);
346 } CX("</div>"/*#pikchrshow-output*/);
347 } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
348 } CX("</div>"/*sbs-wrapper*/);
@@ -561,60 +515,23 @@
515 **
516 ** -src Store the input pikchr's source code in the output as
517 ** a separate element adjacent to the SVG one. Implied
518 ** by -div-source.
519 **
 
 
 
 
 
 
 
 
 
 
 
 
 
520 ** -dark Change pikchr colors to assume a dark-mode theme.
521 **
522 **
523 ** The -div-indent/center/left/right flags may not be combined.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524 */
525 void pikchr_cmd(void){
526 Blob bIn = empty_blob;
527 Blob bOut = empty_blob;
528 const char * zInfile = "-";
529 const char * zOutfile = "-";
 
 
530 int isErr = 0;
531 int pikFlags = find_option("src",0,0)!=0
532 ? PIKCHR_PROCESS_SRC : 0;
 
 
 
 
533
534 if(find_option("div",0,0)!=0){
535 pikFlags |= PIKCHR_PROCESS_DIV;
536 }else if(find_option("div-indent",0,0)!=0){
537 pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
@@ -644,24 +561,14 @@
561 }
562 if(g.argc>3){
563 zOutfile = g.argv[3];
564 }
565 blob_read_from_file(&bIn, zInfile, ExtFILE);
566 isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
 
 
 
 
 
 
 
567 if(isErr){
568 fossil_fatal("pikchr ERROR: %b", &bOut);
 
 
569 }else{
570 blob_write_to_file(&bOut, zOutfile);
571 }
 
572 blob_reset(&bIn);
573 blob_reset(&bOut);
574 }
575
+7 -2
--- src/printf.c
+++ src/printf.c
@@ -1077,10 +1077,11 @@
10771077
time_t now;
10781078
FILE *out;
10791079
const char *z;
10801080
int i;
10811081
int bDetail = 0;
1082
+ int bBrief = 0;
10821083
va_list ap;
10831084
static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
10841085
"HTTP_USER_AGENT",
10851086
"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
10861087
"REQUEST_URI", "SCRIPT_NAME" };
@@ -1098,16 +1099,20 @@
10981099
pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
10991100
va_start(ap, zFormat);
11001101
if( zFormat[0]=='X' ){
11011102
bDetail = 1;
11021103
zFormat++;
1104
+ }else if( strncmp(zFormat,"SMTP:",5)==0 ){
1105
+ bBrief = 1;
11031106
}
11041107
vfprintf(out, zFormat, ap);
11051108
fprintf(out, " (pid %d)\n", (int)getpid());
11061109
va_end(ap);
11071110
if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase);
1108
- if( bDetail ){
1111
+ if( bBrief ){
1112
+ /* Say nothing more */
1113
+ }else if( bDetail ){
11091114
cgi_print_all(1,3,out);
11101115
}else{
11111116
for(i=0; i<count(azEnv); i++){
11121117
char *p;
11131118
if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){
@@ -1116,11 +1121,11 @@
11161121
}else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
11171122
fprintf(out, "%s=%s\n", azEnv[i], z);
11181123
}
11191124
}
11201125
}
1121
- fclose(out);
1126
+ if( out!=stderr ) fclose(out);
11221127
}
11231128
11241129
/*
11251130
** The following variable becomes true while processing a fatal error
11261131
** or a panic. If additional "recursive-fatal" errors occur while
11271132
--- src/printf.c
+++ src/printf.c
@@ -1077,10 +1077,11 @@
1077 time_t now;
1078 FILE *out;
1079 const char *z;
1080 int i;
1081 int bDetail = 0;
 
1082 va_list ap;
1083 static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
1084 "HTTP_USER_AGENT",
1085 "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
1086 "REQUEST_URI", "SCRIPT_NAME" };
@@ -1098,16 +1099,20 @@
1098 pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
1099 va_start(ap, zFormat);
1100 if( zFormat[0]=='X' ){
1101 bDetail = 1;
1102 zFormat++;
 
 
1103 }
1104 vfprintf(out, zFormat, ap);
1105 fprintf(out, " (pid %d)\n", (int)getpid());
1106 va_end(ap);
1107 if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase);
1108 if( bDetail ){
 
 
1109 cgi_print_all(1,3,out);
1110 }else{
1111 for(i=0; i<count(azEnv); i++){
1112 char *p;
1113 if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){
@@ -1116,11 +1121,11 @@
1116 }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
1117 fprintf(out, "%s=%s\n", azEnv[i], z);
1118 }
1119 }
1120 }
1121 fclose(out);
1122 }
1123
1124 /*
1125 ** The following variable becomes true while processing a fatal error
1126 ** or a panic. If additional "recursive-fatal" errors occur while
1127
--- src/printf.c
+++ src/printf.c
@@ -1077,10 +1077,11 @@
1077 time_t now;
1078 FILE *out;
1079 const char *z;
1080 int i;
1081 int bDetail = 0;
1082 int bBrief = 0;
1083 va_list ap;
1084 static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
1085 "HTTP_USER_AGENT",
1086 "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
1087 "REQUEST_URI", "SCRIPT_NAME" };
@@ -1098,16 +1099,20 @@
1099 pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
1100 va_start(ap, zFormat);
1101 if( zFormat[0]=='X' ){
1102 bDetail = 1;
1103 zFormat++;
1104 }else if( strncmp(zFormat,"SMTP:",5)==0 ){
1105 bBrief = 1;
1106 }
1107 vfprintf(out, zFormat, ap);
1108 fprintf(out, " (pid %d)\n", (int)getpid());
1109 va_end(ap);
1110 if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase);
1111 if( bBrief ){
1112 /* Say nothing more */
1113 }else if( bDetail ){
1114 cgi_print_all(1,3,out);
1115 }else{
1116 for(i=0; i<count(azEnv); i++){
1117 char *p;
1118 if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){
@@ -1116,11 +1121,11 @@
1121 }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
1122 fprintf(out, "%s=%s\n", azEnv[i], z);
1123 }
1124 }
1125 }
1126 if( out!=stderr ) fclose(out);
1127 }
1128
1129 /*
1130 ** The following variable becomes true while processing a fatal error
1131 ** or a panic. If additional "recursive-fatal" errors occur while
1132
+73 -28
--- src/repolist.c
+++ src/repolist.c
@@ -126,12 +126,20 @@
126126
int allRepo; /* True if running "fossil ui all".
127127
** False if a directory scan of base for repos */
128128
Blob html; /* Html for the body of the repository list */
129129
char *zSkinRepo = 0; /* Name of the repository database used for skins */
130130
char *zSkinUrl = 0; /* URL for the skin database */
131
+ const char *zShow; /* Value of FOSSIL_REPOLIST_SHOW environment variable */
132
+ int bShowDesc = 0; /* True to show the description column */
133
+ int bShowLg = 0; /* True to show the login-group column */
131134
132135
assert( g.db==0 );
136
+ zShow = P("FOSSIL_REPOLIST_SHOW");
137
+ if( zShow ){
138
+ bShowDesc = strstr(zShow,"description")!=0;
139
+ bShowLg = strstr(zShow,"login-group")!=0;
140
+ }
133141
blob_init(&html, 0, 0);
134142
if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
135143
/* For the special case of the "repository directory" being "/",
136144
** show all of the repositories named in the ~/.fossil database.
137145
**
@@ -150,10 +158,12 @@
150158
}else{
151159
/* The default case: All repositories under the g.zRepositoryName
152160
** directory.
153161
*/
154162
blob_init(&base, g.zRepositoryName, -1);
163
+ db_close(0);
164
+ assert( g.db==0 );
155165
sqlite3_open(":memory:", &g.db);
156166
db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
157167
db_multi_exec("CREATE TABLE vfile(pathname);");
158168
vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
159169
db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"
@@ -171,19 +181,50 @@
171181
g.localOpen = 0;
172182
return 0;
173183
}else{
174184
Stmt q;
175185
double rNow;
176
- blob_append_sql(&html,
186
+ char zType[16]; /* Column type letters for class "sortable" */
187
+ int nType;
188
+ zType[0] = 't'; /* Repo name */
189
+ zType[1] = 'x'; /* Space between repo-name and project-name */
190
+ zType[2] = 't'; /* Project name */
191
+ nType = 3;
192
+ if( bShowDesc ){
193
+ zType[nType++] = 'x'; /* Space between name and description */
194
+ zType[nType++] = 't'; /* Project description */
195
+ }
196
+ zType[nType++] = 'x'; /* space before age */
197
+ zType[nType++] = 'k'; /* Project age */
198
+ if( bShowLg ){
199
+ zType[nType++] = 'x'; /* space before login-group */
200
+ zType[nType++] = 't'; /* Login Group */
201
+ }
202
+ zType[nType] = 0;
203
+ blob_appendf(&html,
177204
"<table border='0' class='sortable' data-init-sort='1'"
178
- " data-column-types='txtxtxkxt' cellspacing='0' cellpadding='0'><thead>\n"
179
- "<tr><th>Filename<th width='7'>"
180
- "<th width='25%%'>Project Name<th width='10'>"
181
- "<th width='25%%'>Project Description<th width='5'>"
182
- "<th><nobr>Last Modified</nobr><th width='1'>"
183
- "<th><nobr>Login Group</nobr></tr>\n"
184
- "</thead><tbody>\n");
205
+ " data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n"
206
+ "<tr><th>Filename</th><th>&emsp;</th>\n"
207
+ "<th%s><nobr>Project Name</nobr></th>\n",
208
+ zType, (bShowDesc ? " width='25%'" : ""));
209
+ if( bShowDesc ){
210
+ blob_appendf(&html,
211
+ "<th>&emsp;</th>\n"
212
+ "<th width='25%%'><nobr>Project Description</nobr></th>\n"
213
+ );
214
+ }
215
+ blob_appendf(&html,
216
+ "<th>&emsp;</th>"
217
+ "<th><nobr>Last Modified</nobr></th>\n"
218
+ );
219
+ if( bShowLg ){
220
+ blob_appendf(&html,
221
+ "<th>&emsp;</th>"
222
+ "<th><nobr>Login Group</nobr></th></tr>\n"
223
+ );
224
+ }
225
+ blob_appendf(&html,"</thead><tbody>\n");
185226
db_prepare(&q, "SELECT pathname"
186227
" FROM sfile ORDER BY pathname COLLATE nocase;");
187228
rNow = db_double(0, "SELECT julianday('now')");
188229
while( db_step(&q)==SQLITE_ROW ){
189230
const char *zName = db_column_text(&q, 0);
@@ -242,21 +283,21 @@
242283
if( x.rMTime==0.0 ){
243284
/* This repository has no entry in the "event" table.
244285
** Its age will still be maximum, so data-sortkey will work. */
245286
zAge = mprintf("unknown");
246287
}
247
- blob_append_sql(&html, "<tr><td valign='top'><nobr>");
288
+ blob_appendf(&html, "<tr><td valign='top'><nobr>");
248289
if( !file_ends_with_repository_extension(zName,0) ){
249290
/* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
250291
** do not work for repositories whose names do not end in ".fossil".
251292
** So do not hyperlink those cases. */
252
- blob_append_sql(&html,"%h",zName);
293
+ blob_appendf(&html,"%h",zName);
253294
} else if( sqlite3_strglob("*/.*", zName)==0 ){
254295
/* Do not show hyperlinks for hidden repos */
255
- blob_append_sql(&html, "%h (hidden)", zName);
296
+ blob_appendf(&html, "%h (hidden)", zName);
256297
} else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
257
- blob_append_sql(&html,
298
+ blob_appendf(&html,
258299
"<a href='%R/%T/home' target='_blank'>/%h</a>\n",
259300
zUrl, zName);
260301
}else if( file_ends_with_repository_extension(zName,1) ){
261302
/* As described in
262303
** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
@@ -273,56 +314,60 @@
273314
, zDirPart
274315
#if USE_SEE
275316
, zDirPart
276317
#endif
277318
) ){
278
- blob_append_sql(&html,
319
+ blob_appendf(&html,
279320
"<s>%h</s> (directory/repo name collision)\n",
280321
zName);
281322
}else{
282
- blob_append_sql(&html,
323
+ blob_appendf(&html,
283324
"<a href='%R/%T/home' target='_blank'>%h</a>\n",
284325
zUrl, zName);
285326
}
286327
fossil_free(zDirPart);
287328
}else{
288
- blob_append_sql(&html,
329
+ blob_appendf(&html,
289330
"<a href='%R/%T/home' target='_blank'>%h</a>\n",
290331
zUrl, zName);
291332
}
292
- blob_append_sql(&html,"</nobr>");
333
+ blob_appendf(&html,"</nobr></td>\n");
293334
if( x.zProjName ){
294
- blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n",
295
- x.zProjName);
335
+ blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
336
+ x.zProjName);
296337
fossil_free(x.zProjName);
297338
}else{
298
- blob_append_sql(&html, "<td></td><td></td>\n");
339
+ blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
299340
}
300
- if( x.zProjDesc ){
301
- blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n",
341
+ if( !bShowDesc ){
342
+ /* Do nothing */
343
+ }else if( x.zProjDesc ){
344
+ blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
302345
x.zProjDesc);
303346
fossil_free(x.zProjDesc);
304347
}else{
305
- blob_append_sql(&html, "<td></td><td></td>\n");
348
+ blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
306349
}
307
- blob_append_sql(&html,
308
- "<td></td><td data-sortkey='%08x' align='center' valign='top'>"
350
+ blob_appendf(&html,
351
+ "<td>&emsp;</td><td data-sortkey='%08x' align='center' valign='top'>"
309352
"<nobr>%h</nobr></td>\n",
310353
(int)iAge, zAge);
311354
fossil_free(zAge);
312
- if( x.zLoginGroup ){
313
- blob_append_sql(&html, "<td></td><td valign='top'>"
355
+ if( !bShowLg ){
356
+ blob_appendf(&html, "</tr>\n");
357
+ }else if( x.zLoginGroup ){
358
+ blob_appendf(&html, "<td>&emsp;</td><td valign='top'>"
314359
"<nobr>%h</nobr></td></tr>\n",
315360
x.zLoginGroup);
316361
fossil_free(x.zLoginGroup);
317362
}else{
318
- blob_append_sql(&html, "<td></td><td></td></tr>\n");
363
+ blob_appendf(&html, "<td>&emsp;</td><td></td></tr>\n");
319364
}
320365
sqlite3_free(zUrl);
321366
}
322367
db_finalize(&q);
323
- blob_append_sql(&html,"</tbody></table>\n");
368
+ blob_appendf(&html,"</tbody></table>\n");
324369
}
325370
if( zSkinRepo ){
326371
char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl);
327372
g.zBaseURL = 0;
328373
set_base_url(zNewBase);
329374
--- src/repolist.c
+++ src/repolist.c
@@ -126,12 +126,20 @@
126 int allRepo; /* True if running "fossil ui all".
127 ** False if a directory scan of base for repos */
128 Blob html; /* Html for the body of the repository list */
129 char *zSkinRepo = 0; /* Name of the repository database used for skins */
130 char *zSkinUrl = 0; /* URL for the skin database */
 
 
 
131
132 assert( g.db==0 );
 
 
 
 
 
133 blob_init(&html, 0, 0);
134 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
135 /* For the special case of the "repository directory" being "/",
136 ** show all of the repositories named in the ~/.fossil database.
137 **
@@ -150,10 +158,12 @@
150 }else{
151 /* The default case: All repositories under the g.zRepositoryName
152 ** directory.
153 */
154 blob_init(&base, g.zRepositoryName, -1);
 
 
155 sqlite3_open(":memory:", &g.db);
156 db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
157 db_multi_exec("CREATE TABLE vfile(pathname);");
158 vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
159 db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"
@@ -171,19 +181,50 @@
171 g.localOpen = 0;
172 return 0;
173 }else{
174 Stmt q;
175 double rNow;
176 blob_append_sql(&html,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177 "<table border='0' class='sortable' data-init-sort='1'"
178 " data-column-types='txtxtxkxt' cellspacing='0' cellpadding='0'><thead>\n"
179 "<tr><th>Filename<th width='7'>"
180 "<th width='25%%'>Project Name<th width='10'>"
181 "<th width='25%%'>Project Description<th width='5'>"
182 "<th><nobr>Last Modified</nobr><th width='1'>"
183 "<th><nobr>Login Group</nobr></tr>\n"
184 "</thead><tbody>\n");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185 db_prepare(&q, "SELECT pathname"
186 " FROM sfile ORDER BY pathname COLLATE nocase;");
187 rNow = db_double(0, "SELECT julianday('now')");
188 while( db_step(&q)==SQLITE_ROW ){
189 const char *zName = db_column_text(&q, 0);
@@ -242,21 +283,21 @@
242 if( x.rMTime==0.0 ){
243 /* This repository has no entry in the "event" table.
244 ** Its age will still be maximum, so data-sortkey will work. */
245 zAge = mprintf("unknown");
246 }
247 blob_append_sql(&html, "<tr><td valign='top'><nobr>");
248 if( !file_ends_with_repository_extension(zName,0) ){
249 /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
250 ** do not work for repositories whose names do not end in ".fossil".
251 ** So do not hyperlink those cases. */
252 blob_append_sql(&html,"%h",zName);
253 } else if( sqlite3_strglob("*/.*", zName)==0 ){
254 /* Do not show hyperlinks for hidden repos */
255 blob_append_sql(&html, "%h (hidden)", zName);
256 } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
257 blob_append_sql(&html,
258 "<a href='%R/%T/home' target='_blank'>/%h</a>\n",
259 zUrl, zName);
260 }else if( file_ends_with_repository_extension(zName,1) ){
261 /* As described in
262 ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
@@ -273,56 +314,60 @@
273 , zDirPart
274 #if USE_SEE
275 , zDirPart
276 #endif
277 ) ){
278 blob_append_sql(&html,
279 "<s>%h</s> (directory/repo name collision)\n",
280 zName);
281 }else{
282 blob_append_sql(&html,
283 "<a href='%R/%T/home' target='_blank'>%h</a>\n",
284 zUrl, zName);
285 }
286 fossil_free(zDirPart);
287 }else{
288 blob_append_sql(&html,
289 "<a href='%R/%T/home' target='_blank'>%h</a>\n",
290 zUrl, zName);
291 }
292 blob_append_sql(&html,"</nobr>");
293 if( x.zProjName ){
294 blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n",
295 x.zProjName);
296 fossil_free(x.zProjName);
297 }else{
298 blob_append_sql(&html, "<td></td><td></td>\n");
299 }
300 if( x.zProjDesc ){
301 blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n",
 
 
302 x.zProjDesc);
303 fossil_free(x.zProjDesc);
304 }else{
305 blob_append_sql(&html, "<td></td><td></td>\n");
306 }
307 blob_append_sql(&html,
308 "<td></td><td data-sortkey='%08x' align='center' valign='top'>"
309 "<nobr>%h</nobr></td>\n",
310 (int)iAge, zAge);
311 fossil_free(zAge);
312 if( x.zLoginGroup ){
313 blob_append_sql(&html, "<td></td><td valign='top'>"
 
 
314 "<nobr>%h</nobr></td></tr>\n",
315 x.zLoginGroup);
316 fossil_free(x.zLoginGroup);
317 }else{
318 blob_append_sql(&html, "<td></td><td></td></tr>\n");
319 }
320 sqlite3_free(zUrl);
321 }
322 db_finalize(&q);
323 blob_append_sql(&html,"</tbody></table>\n");
324 }
325 if( zSkinRepo ){
326 char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl);
327 g.zBaseURL = 0;
328 set_base_url(zNewBase);
329
--- src/repolist.c
+++ src/repolist.c
@@ -126,12 +126,20 @@
126 int allRepo; /* True if running "fossil ui all".
127 ** False if a directory scan of base for repos */
128 Blob html; /* Html for the body of the repository list */
129 char *zSkinRepo = 0; /* Name of the repository database used for skins */
130 char *zSkinUrl = 0; /* URL for the skin database */
131 const char *zShow; /* Value of FOSSIL_REPOLIST_SHOW environment variable */
132 int bShowDesc = 0; /* True to show the description column */
133 int bShowLg = 0; /* True to show the login-group column */
134
135 assert( g.db==0 );
136 zShow = P("FOSSIL_REPOLIST_SHOW");
137 if( zShow ){
138 bShowDesc = strstr(zShow,"description")!=0;
139 bShowLg = strstr(zShow,"login-group")!=0;
140 }
141 blob_init(&html, 0, 0);
142 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
143 /* For the special case of the "repository directory" being "/",
144 ** show all of the repositories named in the ~/.fossil database.
145 **
@@ -150,10 +158,12 @@
158 }else{
159 /* The default case: All repositories under the g.zRepositoryName
160 ** directory.
161 */
162 blob_init(&base, g.zRepositoryName, -1);
163 db_close(0);
164 assert( g.db==0 );
165 sqlite3_open(":memory:", &g.db);
166 db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
167 db_multi_exec("CREATE TABLE vfile(pathname);");
168 vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
169 db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"
@@ -171,19 +181,50 @@
181 g.localOpen = 0;
182 return 0;
183 }else{
184 Stmt q;
185 double rNow;
186 char zType[16]; /* Column type letters for class "sortable" */
187 int nType;
188 zType[0] = 't'; /* Repo name */
189 zType[1] = 'x'; /* Space between repo-name and project-name */
190 zType[2] = 't'; /* Project name */
191 nType = 3;
192 if( bShowDesc ){
193 zType[nType++] = 'x'; /* Space between name and description */
194 zType[nType++] = 't'; /* Project description */
195 }
196 zType[nType++] = 'x'; /* space before age */
197 zType[nType++] = 'k'; /* Project age */
198 if( bShowLg ){
199 zType[nType++] = 'x'; /* space before login-group */
200 zType[nType++] = 't'; /* Login Group */
201 }
202 zType[nType] = 0;
203 blob_appendf(&html,
204 "<table border='0' class='sortable' data-init-sort='1'"
205 " data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n"
206 "<tr><th>Filename</th><th>&emsp;</th>\n"
207 "<th%s><nobr>Project Name</nobr></th>\n",
208 zType, (bShowDesc ? " width='25%'" : ""));
209 if( bShowDesc ){
210 blob_appendf(&html,
211 "<th>&emsp;</th>\n"
212 "<th width='25%%'><nobr>Project Description</nobr></th>\n"
213 );
214 }
215 blob_appendf(&html,
216 "<th>&emsp;</th>"
217 "<th><nobr>Last Modified</nobr></th>\n"
218 );
219 if( bShowLg ){
220 blob_appendf(&html,
221 "<th>&emsp;</th>"
222 "<th><nobr>Login Group</nobr></th></tr>\n"
223 );
224 }
225 blob_appendf(&html,"</thead><tbody>\n");
226 db_prepare(&q, "SELECT pathname"
227 " FROM sfile ORDER BY pathname COLLATE nocase;");
228 rNow = db_double(0, "SELECT julianday('now')");
229 while( db_step(&q)==SQLITE_ROW ){
230 const char *zName = db_column_text(&q, 0);
@@ -242,21 +283,21 @@
283 if( x.rMTime==0.0 ){
284 /* This repository has no entry in the "event" table.
285 ** Its age will still be maximum, so data-sortkey will work. */
286 zAge = mprintf("unknown");
287 }
288 blob_appendf(&html, "<tr><td valign='top'><nobr>");
289 if( !file_ends_with_repository_extension(zName,0) ){
290 /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
291 ** do not work for repositories whose names do not end in ".fossil".
292 ** So do not hyperlink those cases. */
293 blob_appendf(&html,"%h",zName);
294 } else if( sqlite3_strglob("*/.*", zName)==0 ){
295 /* Do not show hyperlinks for hidden repos */
296 blob_appendf(&html, "%h (hidden)", zName);
297 } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
298 blob_appendf(&html,
299 "<a href='%R/%T/home' target='_blank'>/%h</a>\n",
300 zUrl, zName);
301 }else if( file_ends_with_repository_extension(zName,1) ){
302 /* As described in
303 ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
@@ -273,56 +314,60 @@
314 , zDirPart
315 #if USE_SEE
316 , zDirPart
317 #endif
318 ) ){
319 blob_appendf(&html,
320 "<s>%h</s> (directory/repo name collision)\n",
321 zName);
322 }else{
323 blob_appendf(&html,
324 "<a href='%R/%T/home' target='_blank'>%h</a>\n",
325 zUrl, zName);
326 }
327 fossil_free(zDirPart);
328 }else{
329 blob_appendf(&html,
330 "<a href='%R/%T/home' target='_blank'>%h</a>\n",
331 zUrl, zName);
332 }
333 blob_appendf(&html,"</nobr></td>\n");
334 if( x.zProjName ){
335 blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
336 x.zProjName);
337 fossil_free(x.zProjName);
338 }else{
339 blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
340 }
341 if( !bShowDesc ){
342 /* Do nothing */
343 }else if( x.zProjDesc ){
344 blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
345 x.zProjDesc);
346 fossil_free(x.zProjDesc);
347 }else{
348 blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
349 }
350 blob_appendf(&html,
351 "<td>&emsp;</td><td data-sortkey='%08x' align='center' valign='top'>"
352 "<nobr>%h</nobr></td>\n",
353 (int)iAge, zAge);
354 fossil_free(zAge);
355 if( !bShowLg ){
356 blob_appendf(&html, "</tr>\n");
357 }else if( x.zLoginGroup ){
358 blob_appendf(&html, "<td>&emsp;</td><td valign='top'>"
359 "<nobr>%h</nobr></td></tr>\n",
360 x.zLoginGroup);
361 fossil_free(x.zLoginGroup);
362 }else{
363 blob_appendf(&html, "<td>&emsp;</td><td></td></tr>\n");
364 }
365 sqlite3_free(zUrl);
366 }
367 db_finalize(&q);
368 blob_appendf(&html,"</tbody></table>\n");
369 }
370 if( zSkinRepo ){
371 char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl);
372 g.zBaseURL = 0;
373 set_base_url(zNewBase);
374
+2 -4
--- src/report.c
+++ src/report.c
@@ -583,13 +583,10 @@
583583
zOwner = g.zLogin;
584584
}
585585
}
586586
if( zOwner==0 ) zOwner = g.zLogin;
587587
style_submenu_element("Cancel", "%R/reportlist");
588
- if( rn>0 ){
589
- style_submenu_element("Delete", "%R/rptedit/%d?del1=1", rn);
590
- }
591588
style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format");
592589
if( zErr ){
593590
@ <blockquote class="reportError">%h(zErr)</blockquote>
594591
}
595592
@ <form action="rptedit" method="post"><div>
@@ -897,11 +894,12 @@
897894
898895
/* Output the separator above each entry in a table which has multiple lines
899896
** per database entry.
900897
*/
901898
if( pState->iNewRow>=0 ){
902
- @ <tr><td colspan=%d(pState->nCol)><font size=1>&nbsp;</font></td></tr>
899
+ @ <tr><td colspan="%d(pState->nCol)" style="padding:0px">
900
+ @ <hr style="margin:0px"></td></tr>
903901
}
904902
905903
/* Output the data for this entry from the database
906904
*/
907905
zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
908906
--- src/report.c
+++ src/report.c
@@ -583,13 +583,10 @@
583 zOwner = g.zLogin;
584 }
585 }
586 if( zOwner==0 ) zOwner = g.zLogin;
587 style_submenu_element("Cancel", "%R/reportlist");
588 if( rn>0 ){
589 style_submenu_element("Delete", "%R/rptedit/%d?del1=1", rn);
590 }
591 style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format");
592 if( zErr ){
593 @ <blockquote class="reportError">%h(zErr)</blockquote>
594 }
595 @ <form action="rptedit" method="post"><div>
@@ -897,11 +894,12 @@
897
898 /* Output the separator above each entry in a table which has multiple lines
899 ** per database entry.
900 */
901 if( pState->iNewRow>=0 ){
902 @ <tr><td colspan=%d(pState->nCol)><font size=1>&nbsp;</font></td></tr>
 
903 }
904
905 /* Output the data for this entry from the database
906 */
907 zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
908
--- src/report.c
+++ src/report.c
@@ -583,13 +583,10 @@
583 zOwner = g.zLogin;
584 }
585 }
586 if( zOwner==0 ) zOwner = g.zLogin;
587 style_submenu_element("Cancel", "%R/reportlist");
 
 
 
588 style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format");
589 if( zErr ){
590 @ <blockquote class="reportError">%h(zErr)</blockquote>
591 }
592 @ <form action="rptedit" method="post"><div>
@@ -897,11 +894,12 @@
894
895 /* Output the separator above each entry in a table which has multiple lines
896 ** per database entry.
897 */
898 if( pState->iNewRow>=0 ){
899 @ <tr><td colspan="%d(pState->nCol)" style="padding:0px">
900 @ <hr style="margin:0px"></td></tr>
901 }
902
903 /* Output the data for this entry from the database
904 */
905 zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
906
--- src/security_audit.c
+++ src/security_audit.c
@@ -100,10 +100,11 @@
100100
const char *zReadCap; /* Capabilities of user group "reader" */
101101
const char *zPubPages; /* GLOB pattern for public pages */
102102
const char *zSelfCap; /* Capabilities of self-registered users */
103103
int hasSelfReg = 0; /* True if able to self-register */
104104
const char *zPublicUrl; /* Canonical access URL */
105
+ const char *zVulnReport; /* The vuln-report setting */
105106
Blob cmd;
106107
char *z;
107108
int n, i;
108109
CapabilityString *pCap;
109110
char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
362363
@ <li><p><b>WARNING:</b>
363364
@ The "strict-manifest-syntax" flag is off. This is a security
364365
@ risk. Turn this setting on (its default) to protect the users
365366
@ of this repository.
366367
}
368
+
369
+ zVulnReport = db_get("vuln-report","log");
370
+ if( fossil_strcmp(zVulnReport,"block")!=0
371
+ && fossil_strcmp(zVulnReport,"fatal")!=0
372
+ ){
373
+ @ <li><p><b>WARNING:</b>
374
+ @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
375
+ @ has a value of "%h(zVulnReport)". This disables defenses against
376
+ @ XSS or SQL-injection vulnerabilities caused by coding errors in
377
+ @ custom TH1 scripts. For the best security, change
378
+ @ the value of the vuln-report setting to "block" or "fatal".
379
+ }
367380
368381
/* Obsolete: */
369382
if( hasAnyCap(zAnonCap, "d") ||
370383
hasAnyCap(zDevCap, "d") ||
371384
hasAnyCap(zReadCap, "d") ){
@@ -810,35 +823,39 @@
810823
** WEBPAGE: errorlog
811824
**
812825
** Show the content of the error log. Only the administrator can view
813826
** this page.
814827
**
815
-** y=0x01 Show only hack attempts
816
-** y=0x02 Show only panics and assertion faults
817
-** y=0x04 Show hung backoffice processes
818
-** y=0x08 Show POST requests from a different origin
819
-** y=0x10 Show SQLITE_AUTH and similar
820
-** y=0x40 Show other uncategorized messages
828
+** y=0x001 Show only hack attempts
829
+** y=0x002 Show only panics and assertion faults
830
+** y=0x004 Show hung backoffice processes
831
+** y=0x008 Show POST requests from a different origin
832
+** y=0x010 Show SQLITE_AUTH and similar
833
+** y=0x020 Show SMTP error reports
834
+** y=0x040 Show TH1 vulnerability reports
835
+** y=0x800 Show other uncategorized messages
821836
**
822837
** If y is omitted or is zero, a count of the various message types is
823838
** shown.
824839
*/
825840
void errorlog_page(void){
826841
i64 szFile;
827842
FILE *in;
828843
char *zLog;
829844
const char *zType = P("y");
830
- static const int eAllTypes = 0x5f;
845
+ static const int eAllTypes = 0x87f;
831846
long eType = 0;
832847
int bOutput = 0;
833848
int prevWasTime = 0;
834849
int nHack = 0;
835850
int nPanic = 0;
836851
int nOther = 0;
837852
int nHang = 0;
838853
int nXPost = 0;
839854
int nAuth = 0;
855
+ int nSmtp = 0;
856
+ int nVuln = 0;
840857
char z[10000];
841858
char zTime[10000];
842859
843860
login_check_credentials();
844861
if( !g.perm.Admin ){
@@ -911,11 +928,17 @@
911928
@ <li>POST requests from different origin
912929
}
913930
if( eType & 0x10 ){
914931
@ <li>SQLITE_AUTH and similar errors
915932
}
933
+ if( eType & 0x20 ){
934
+ @ <li>SMTP malfunctions
935
+ }
916936
if( eType & 0x40 ){
937
+ @ <li>TH1 vulnerabilities
938
+ }
939
+ if( eType & 0x800 ){
917940
@ <li>Other uncategorized messages
918941
}
919942
@ </ul>
920943
}
921944
@ <hr>
@@ -929,10 +952,14 @@
929952
nHack++;
930953
}else
931954
if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){
932955
bOutput = (eType & 0x02)!=0;
933956
nPanic++;
957
+ }else
958
+ if( strncmp(z,"SMTP:", 5)==0 ){
959
+ bOutput = (eType & 0x20)!=0;
960
+ nSmtp++;
934961
}else
935962
if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
936963
bOutput = (eType & 0x04)!=0;
937964
nHang++;
938965
}else
@@ -944,12 +971,16 @@
944971
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
945972
){
946973
bOutput = (eType & 0x10)!=0;
947974
nAuth++;
948975
}else
949
- {
976
+ if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
950977
bOutput = (eType & 0x40)!=0;
978
+ nVuln++;
979
+ }else
980
+ {
981
+ bOutput = (eType & 0x800)!=0;
951982
nOther++;
952983
}
953984
if( bOutput ){
954985
@ %h(zTime)\
955986
}
@@ -969,17 +1000,21 @@
9691000
fclose(in);
9701001
if( eType ){
9711002
@ </pre>
9721003
}
9731004
if( eType==0 ){
974
- int nNonHack = nPanic + nHang + nAuth + nOther;
1005
+ int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
9751006
int nTotal = nNonHack + nHack + nXPost;
9761007
@ <p><table border="a" cellspacing="0" cellpadding="5">
9771008
if( nPanic>0 ){
9781009
@ <tr><td align="right">%d(nPanic)</td>
9791010
@ <td><a href="./errorlog?y=2">Panics</a></td>
9801011
}
1012
+ if( nVuln>0 ){
1013
+ @ <tr><td align="right">%d(nVuln)</td>
1014
+ @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1015
+ }
9811016
if( nHack>0 ){
9821017
@ <tr><td align="right">%d(nHack)</td>
9831018
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
9841019
}
9851020
if( nHang>0 ){
@@ -992,19 +1027,23 @@
9921027
}
9931028
if( nAuth>0 ){
9941029
@ <tr><td align="right">%d(nAuth)</td>
9951030
@ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
9961031
}
1032
+ if( nSmtp>0 ){
1033
+ @ <tr><td align="right">%d(nSmtp)</td>
1034
+ @ <td><a href="./errorlog?y=32">SMTP faults</a></td>
1035
+ }
9971036
if( nOther>0 ){
9981037
@ <tr><td align="right">%d(nOther)</td>
999
- @ <td><a href="./errorlog?y=64">Other</a></td>
1038
+ @ <td><a href="./errorlog?y=2048">Other</a></td>
10001039
}
10011040
@ <tr><td align="right">%d(nTotal)</td>
10021041
if( nTotal>0 ){
1003
- @ <td><a href="./errorlog?y=255">All Messages</a></td>
1042
+ @ <td><a href="./errorlog?y=4095">All Messages</a></td>
10041043
}else{
10051044
@ <td>All Messages</td>
10061045
}
10071046
@ </table>
10081047
}
10091048
style_finish_page();
10101049
}
10111050
--- src/security_audit.c
+++ src/security_audit.c
@@ -100,10 +100,11 @@
100 const char *zReadCap; /* Capabilities of user group "reader" */
101 const char *zPubPages; /* GLOB pattern for public pages */
102 const char *zSelfCap; /* Capabilities of self-registered users */
103 int hasSelfReg = 0; /* True if able to self-register */
104 const char *zPublicUrl; /* Canonical access URL */
 
105 Blob cmd;
106 char *z;
107 int n, i;
108 CapabilityString *pCap;
109 char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
362 @ <li><p><b>WARNING:</b>
363 @ The "strict-manifest-syntax" flag is off. This is a security
364 @ risk. Turn this setting on (its default) to protect the users
365 @ of this repository.
366 }
 
 
 
 
 
 
 
 
 
 
 
 
367
368 /* Obsolete: */
369 if( hasAnyCap(zAnonCap, "d") ||
370 hasAnyCap(zDevCap, "d") ||
371 hasAnyCap(zReadCap, "d") ){
@@ -810,35 +823,39 @@
810 ** WEBPAGE: errorlog
811 **
812 ** Show the content of the error log. Only the administrator can view
813 ** this page.
814 **
815 ** y=0x01 Show only hack attempts
816 ** y=0x02 Show only panics and assertion faults
817 ** y=0x04 Show hung backoffice processes
818 ** y=0x08 Show POST requests from a different origin
819 ** y=0x10 Show SQLITE_AUTH and similar
820 ** y=0x40 Show other uncategorized messages
 
 
821 **
822 ** If y is omitted or is zero, a count of the various message types is
823 ** shown.
824 */
825 void errorlog_page(void){
826 i64 szFile;
827 FILE *in;
828 char *zLog;
829 const char *zType = P("y");
830 static const int eAllTypes = 0x5f;
831 long eType = 0;
832 int bOutput = 0;
833 int prevWasTime = 0;
834 int nHack = 0;
835 int nPanic = 0;
836 int nOther = 0;
837 int nHang = 0;
838 int nXPost = 0;
839 int nAuth = 0;
 
 
840 char z[10000];
841 char zTime[10000];
842
843 login_check_credentials();
844 if( !g.perm.Admin ){
@@ -911,11 +928,17 @@
911 @ <li>POST requests from different origin
912 }
913 if( eType & 0x10 ){
914 @ <li>SQLITE_AUTH and similar errors
915 }
 
 
 
916 if( eType & 0x40 ){
 
 
 
917 @ <li>Other uncategorized messages
918 }
919 @ </ul>
920 }
921 @ <hr>
@@ -929,10 +952,14 @@
929 nHack++;
930 }else
931 if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){
932 bOutput = (eType & 0x02)!=0;
933 nPanic++;
 
 
 
 
934 }else
935 if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
936 bOutput = (eType & 0x04)!=0;
937 nHang++;
938 }else
@@ -944,12 +971,16 @@
944 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
945 ){
946 bOutput = (eType & 0x10)!=0;
947 nAuth++;
948 }else
949 {
950 bOutput = (eType & 0x40)!=0;
 
 
 
 
951 nOther++;
952 }
953 if( bOutput ){
954 @ %h(zTime)\
955 }
@@ -969,17 +1000,21 @@
969 fclose(in);
970 if( eType ){
971 @ </pre>
972 }
973 if( eType==0 ){
974 int nNonHack = nPanic + nHang + nAuth + nOther;
975 int nTotal = nNonHack + nHack + nXPost;
976 @ <p><table border="a" cellspacing="0" cellpadding="5">
977 if( nPanic>0 ){
978 @ <tr><td align="right">%d(nPanic)</td>
979 @ <td><a href="./errorlog?y=2">Panics</a></td>
980 }
 
 
 
 
981 if( nHack>0 ){
982 @ <tr><td align="right">%d(nHack)</td>
983 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
984 }
985 if( nHang>0 ){
@@ -992,19 +1027,23 @@
992 }
993 if( nAuth>0 ){
994 @ <tr><td align="right">%d(nAuth)</td>
995 @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
996 }
 
 
 
 
997 if( nOther>0 ){
998 @ <tr><td align="right">%d(nOther)</td>
999 @ <td><a href="./errorlog?y=64">Other</a></td>
1000 }
1001 @ <tr><td align="right">%d(nTotal)</td>
1002 if( nTotal>0 ){
1003 @ <td><a href="./errorlog?y=255">All Messages</a></td>
1004 }else{
1005 @ <td>All Messages</td>
1006 }
1007 @ </table>
1008 }
1009 style_finish_page();
1010 }
1011
--- src/security_audit.c
+++ src/security_audit.c
@@ -100,10 +100,11 @@
100 const char *zReadCap; /* Capabilities of user group "reader" */
101 const char *zPubPages; /* GLOB pattern for public pages */
102 const char *zSelfCap; /* Capabilities of self-registered users */
103 int hasSelfReg = 0; /* True if able to self-register */
104 const char *zPublicUrl; /* Canonical access URL */
105 const char *zVulnReport; /* The vuln-report setting */
106 Blob cmd;
107 char *z;
108 int n, i;
109 CapabilityString *pCap;
110 char **azCSP; /* Parsed content security policy */
@@ -362,10 +363,22 @@
363 @ <li><p><b>WARNING:</b>
364 @ The "strict-manifest-syntax" flag is off. This is a security
365 @ risk. Turn this setting on (its default) to protect the users
366 @ of this repository.
367 }
368
369 zVulnReport = db_get("vuln-report","log");
370 if( fossil_strcmp(zVulnReport,"block")!=0
371 && fossil_strcmp(zVulnReport,"fatal")!=0
372 ){
373 @ <li><p><b>WARNING:</b>
374 @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
375 @ has a value of "%h(zVulnReport)". This disables defenses against
376 @ XSS or SQL-injection vulnerabilities caused by coding errors in
377 @ custom TH1 scripts. For the best security, change
378 @ the value of the vuln-report setting to "block" or "fatal".
379 }
380
381 /* Obsolete: */
382 if( hasAnyCap(zAnonCap, "d") ||
383 hasAnyCap(zDevCap, "d") ||
384 hasAnyCap(zReadCap, "d") ){
@@ -810,35 +823,39 @@
823 ** WEBPAGE: errorlog
824 **
825 ** Show the content of the error log. Only the administrator can view
826 ** this page.
827 **
828 ** y=0x001 Show only hack attempts
829 ** y=0x002 Show only panics and assertion faults
830 ** y=0x004 Show hung backoffice processes
831 ** y=0x008 Show POST requests from a different origin
832 ** y=0x010 Show SQLITE_AUTH and similar
833 ** y=0x020 Show SMTP error reports
834 ** y=0x040 Show TH1 vulnerability reports
835 ** y=0x800 Show other uncategorized messages
836 **
837 ** If y is omitted or is zero, a count of the various message types is
838 ** shown.
839 */
840 void errorlog_page(void){
841 i64 szFile;
842 FILE *in;
843 char *zLog;
844 const char *zType = P("y");
845 static const int eAllTypes = 0x87f;
846 long eType = 0;
847 int bOutput = 0;
848 int prevWasTime = 0;
849 int nHack = 0;
850 int nPanic = 0;
851 int nOther = 0;
852 int nHang = 0;
853 int nXPost = 0;
854 int nAuth = 0;
855 int nSmtp = 0;
856 int nVuln = 0;
857 char z[10000];
858 char zTime[10000];
859
860 login_check_credentials();
861 if( !g.perm.Admin ){
@@ -911,11 +928,17 @@
928 @ <li>POST requests from different origin
929 }
930 if( eType & 0x10 ){
931 @ <li>SQLITE_AUTH and similar errors
932 }
933 if( eType & 0x20 ){
934 @ <li>SMTP malfunctions
935 }
936 if( eType & 0x40 ){
937 @ <li>TH1 vulnerabilities
938 }
939 if( eType & 0x800 ){
940 @ <li>Other uncategorized messages
941 }
942 @ </ul>
943 }
944 @ <hr>
@@ -929,10 +952,14 @@
952 nHack++;
953 }else
954 if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){
955 bOutput = (eType & 0x02)!=0;
956 nPanic++;
957 }else
958 if( strncmp(z,"SMTP:", 5)==0 ){
959 bOutput = (eType & 0x20)!=0;
960 nSmtp++;
961 }else
962 if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
963 bOutput = (eType & 0x04)!=0;
964 nHang++;
965 }else
@@ -944,12 +971,16 @@
971 || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
972 ){
973 bOutput = (eType & 0x10)!=0;
974 nAuth++;
975 }else
976 if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
977 bOutput = (eType & 0x40)!=0;
978 nVuln++;
979 }else
980 {
981 bOutput = (eType & 0x800)!=0;
982 nOther++;
983 }
984 if( bOutput ){
985 @ %h(zTime)\
986 }
@@ -969,17 +1000,21 @@
1000 fclose(in);
1001 if( eType ){
1002 @ </pre>
1003 }
1004 if( eType==0 ){
1005 int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
1006 int nTotal = nNonHack + nHack + nXPost;
1007 @ <p><table border="a" cellspacing="0" cellpadding="5">
1008 if( nPanic>0 ){
1009 @ <tr><td align="right">%d(nPanic)</td>
1010 @ <td><a href="./errorlog?y=2">Panics</a></td>
1011 }
1012 if( nVuln>0 ){
1013 @ <tr><td align="right">%d(nVuln)</td>
1014 @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
1015 }
1016 if( nHack>0 ){
1017 @ <tr><td align="right">%d(nHack)</td>
1018 @ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
1019 }
1020 if( nHang>0 ){
@@ -992,19 +1027,23 @@
1027 }
1028 if( nAuth>0 ){
1029 @ <tr><td align="right">%d(nAuth)</td>
1030 @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
1031 }
1032 if( nSmtp>0 ){
1033 @ <tr><td align="right">%d(nSmtp)</td>
1034 @ <td><a href="./errorlog?y=32">SMTP faults</a></td>
1035 }
1036 if( nOther>0 ){
1037 @ <tr><td align="right">%d(nOther)</td>
1038 @ <td><a href="./errorlog?y=2048">Other</a></td>
1039 }
1040 @ <tr><td align="right">%d(nTotal)</td>
1041 if( nTotal>0 ){
1042 @ <td><a href="./errorlog?y=4095">All Messages</a></td>
1043 }else{
1044 @ <td>All Messages</td>
1045 }
1046 @ </table>
1047 }
1048 style_finish_page();
1049 }
1050
+1 -1
--- src/setup.c
+++ src/setup.c
@@ -216,11 +216,11 @@
216216
setup_menu_entry("Admin Log", "admin_log",
217217
"The admin log records configuration changes to the repository\n"
218218
"in the \"admin_log\" table.\n"
219219
);
220220
}
221
- setup_menu_entry("Artifact Log", "rcvfromlist",
221
+ setup_menu_entry("Xfer Log", "rcvfromlist",
222222
"The artifact log records when new content is added in the\n"
223223
"\"rcvfrom\" table.\n"
224224
);
225225
if( db_get_boolean("access-log",1) ){
226226
setup_menu_entry("User Log", "user_log",
227227
--- src/setup.c
+++ src/setup.c
@@ -216,11 +216,11 @@
216 setup_menu_entry("Admin Log", "admin_log",
217 "The admin log records configuration changes to the repository\n"
218 "in the \"admin_log\" table.\n"
219 );
220 }
221 setup_menu_entry("Artifact Log", "rcvfromlist",
222 "The artifact log records when new content is added in the\n"
223 "\"rcvfrom\" table.\n"
224 );
225 if( db_get_boolean("access-log",1) ){
226 setup_menu_entry("User Log", "user_log",
227
--- src/setup.c
+++ src/setup.c
@@ -216,11 +216,11 @@
216 setup_menu_entry("Admin Log", "admin_log",
217 "The admin log records configuration changes to the repository\n"
218 "in the \"admin_log\" table.\n"
219 );
220 }
221 setup_menu_entry("Xfer Log", "rcvfromlist",
222 "The artifact log records when new content is added in the\n"
223 "\"rcvfrom\" table.\n"
224 );
225 if( db_get_boolean("access-log",1) ){
226 setup_menu_entry("User Log", "user_log",
227
+23 -19
--- src/setupuser.c
+++ src/setupuser.c
@@ -41,21 +41,22 @@
4141
Stmt s;
4242
double rNow;
4343
const char *zWith = P("with");
4444
int bUnusedOnly = P("unused")!=0;
4545
int bUbg = P("ubg")!=0;
46
+ int bHaveAlerts;
4647
4748
login_check_credentials();
4849
if( !g.perm.Admin ){
4950
login_needed(0);
5051
return;
5152
}
52
-
53
+ bHaveAlerts = alert_tables_exist();
5354
style_submenu_element("Add", "setup_uedit");
5455
style_submenu_element("Log", "access_log");
5556
style_submenu_element("Help", "setup_ulist_notes");
56
- if( alert_tables_exist() ){
57
+ if( bHaveAlerts ){
5758
style_submenu_element("Subscribers", "subscribers");
5859
}
5960
style_set_current_feature("setup");
6061
style_header("User List");
6162
if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
@@ -147,31 +148,34 @@
147148
zWith = mprintf(
148149
" AND login NOT IN ("
149150
"SELECT user FROM event WHERE user NOT NULL "
150151
"UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
151152
" AND uid NOT IN (SELECT uid FROM rcvfrom)",
152
- alert_tables_exist() ?
153
+ bHaveAlerts ?
153154
" UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
154155
}else if( zWith && zWith[0] ){
155156
zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
156157
}else{
157158
zWith = "";
158159
}
159160
db_prepare(&s,
160
- "SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," /* 0..4 */
161
- " lower(login) AS sortkey, " /* 5 */
162
- " CASE WHEN info LIKE '%%expires 20%%'"
161
+ /*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch'),"
162
+ /* 5 */"lower(login) AS sortkey, "
163
+ /* 6 */"CASE WHEN info LIKE '%%expires 20%%'"
163164
" THEN substr(info,instr(lower(info),'expires')+8,10)"
164
- " END AS exp," /* 6 */
165
- "atime," /* 7 */
166
- " subscriber.ssub, subscriber.subscriberId," /* 8, 9 */
167
- " user.mtime AS sorttime," /* 10 */
168
- " subscriber.semail" /* 11 */
169
- " FROM user LEFT JOIN lastAccess ON login=uname"
170
- " LEFT JOIN subscriber ON login=suname"
171
- " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
172
- " ORDER BY sorttime DESC", zWith/*safe-for-%s*/
165
+ " END AS exp,"
166
+ /* 7 */"atime,"
167
+ /* 8 */"user.mtime AS sorttime,"
168
+ /*9-11*/"%s"
169
+ " FROM user LEFT JOIN lastAccess ON login=uname"
170
+ " LEFT JOIN subscriber ON login=suname"
171
+ " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
172
+ " ORDER BY sorttime DESC",
173
+ bHaveAlerts
174
+ ? "subscriber.ssub, subscriber.subscriberId, subscriber.semail"
175
+ : "null, null, null",
176
+ zWith/*safe-for-%s*/
173177
);
174178
rNow = db_double(0.0, "SELECT julianday('now');");
175179
while( db_step(&s)==SQLITE_ROW ){
176180
int uid = db_column_int(&s, 0);
177181
const char *zLogin = db_column_text(&s, 1);
@@ -181,12 +185,12 @@
181185
const char *zSortKey = db_column_text(&s,5);
182186
const char *zExp = db_column_text(&s,6);
183187
double rATime = db_column_double(&s,7);
184188
char *zAge = 0;
185189
const char *zSub;
186
- int sid = db_column_int(&s,9);
187
- sqlite3_int64 sorttime = db_column_int64(&s, 10);
190
+ int sid = db_column_int(&s,10);
191
+ sqlite3_int64 sorttime = db_column_int64(&s, 8);
188192
if( rATime>0.0 ){
189193
zAge = human_readable_age(rNow - rATime);
190194
}
191195
if( bUbg ){
192196
@ <tr style='background-color: %h(user_color(zLogin));'>
@@ -198,13 +202,13 @@
198202
@ <td>%h(zCap)
199203
@ <td>%h(zInfo)
200204
@ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
201205
@ <td>%h(zExp?zExp:"")
202206
@ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
203
- if( db_column_type(&s,8)==SQLITE_NULL ){
207
+ if( db_column_type(&s,9)==SQLITE_NULL ){
204208
@ <td>
205
- }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
209
+ }else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){
206210
@ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a>
207211
}else{
208212
const char *zEmail = db_column_text(&s, 11);
209213
char * zAt = zEmail ? mprintf(" &rarr; %h", zEmail) : mprintf("");
210214
@ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt)
211215
--- src/setupuser.c
+++ src/setupuser.c
@@ -41,21 +41,22 @@
41 Stmt s;
42 double rNow;
43 const char *zWith = P("with");
44 int bUnusedOnly = P("unused")!=0;
45 int bUbg = P("ubg")!=0;
 
46
47 login_check_credentials();
48 if( !g.perm.Admin ){
49 login_needed(0);
50 return;
51 }
52
53 style_submenu_element("Add", "setup_uedit");
54 style_submenu_element("Log", "access_log");
55 style_submenu_element("Help", "setup_ulist_notes");
56 if( alert_tables_exist() ){
57 style_submenu_element("Subscribers", "subscribers");
58 }
59 style_set_current_feature("setup");
60 style_header("User List");
61 if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
@@ -147,31 +148,34 @@
147 zWith = mprintf(
148 " AND login NOT IN ("
149 "SELECT user FROM event WHERE user NOT NULL "
150 "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
151 " AND uid NOT IN (SELECT uid FROM rcvfrom)",
152 alert_tables_exist() ?
153 " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
154 }else if( zWith && zWith[0] ){
155 zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
156 }else{
157 zWith = "";
158 }
159 db_prepare(&s,
160 "SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," /* 0..4 */
161 " lower(login) AS sortkey, " /* 5 */
162 " CASE WHEN info LIKE '%%expires 20%%'"
163 " THEN substr(info,instr(lower(info),'expires')+8,10)"
164 " END AS exp," /* 6 */
165 "atime," /* 7 */
166 " subscriber.ssub, subscriber.subscriberId," /* 8, 9 */
167 " user.mtime AS sorttime," /* 10 */
168 " subscriber.semail" /* 11 */
169 " FROM user LEFT JOIN lastAccess ON login=uname"
170 " LEFT JOIN subscriber ON login=suname"
171 " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
172 " ORDER BY sorttime DESC", zWith/*safe-for-%s*/
 
 
 
173 );
174 rNow = db_double(0.0, "SELECT julianday('now');");
175 while( db_step(&s)==SQLITE_ROW ){
176 int uid = db_column_int(&s, 0);
177 const char *zLogin = db_column_text(&s, 1);
@@ -181,12 +185,12 @@
181 const char *zSortKey = db_column_text(&s,5);
182 const char *zExp = db_column_text(&s,6);
183 double rATime = db_column_double(&s,7);
184 char *zAge = 0;
185 const char *zSub;
186 int sid = db_column_int(&s,9);
187 sqlite3_int64 sorttime = db_column_int64(&s, 10);
188 if( rATime>0.0 ){
189 zAge = human_readable_age(rNow - rATime);
190 }
191 if( bUbg ){
192 @ <tr style='background-color: %h(user_color(zLogin));'>
@@ -198,13 +202,13 @@
198 @ <td>%h(zCap)
199 @ <td>%h(zInfo)
200 @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
201 @ <td>%h(zExp?zExp:"")
202 @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
203 if( db_column_type(&s,8)==SQLITE_NULL ){
204 @ <td>
205 }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
206 @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a>
207 }else{
208 const char *zEmail = db_column_text(&s, 11);
209 char * zAt = zEmail ? mprintf(" &rarr; %h", zEmail) : mprintf("");
210 @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt)
211
--- src/setupuser.c
+++ src/setupuser.c
@@ -41,21 +41,22 @@
41 Stmt s;
42 double rNow;
43 const char *zWith = P("with");
44 int bUnusedOnly = P("unused")!=0;
45 int bUbg = P("ubg")!=0;
46 int bHaveAlerts;
47
48 login_check_credentials();
49 if( !g.perm.Admin ){
50 login_needed(0);
51 return;
52 }
53 bHaveAlerts = alert_tables_exist();
54 style_submenu_element("Add", "setup_uedit");
55 style_submenu_element("Log", "access_log");
56 style_submenu_element("Help", "setup_ulist_notes");
57 if( bHaveAlerts ){
58 style_submenu_element("Subscribers", "subscribers");
59 }
60 style_set_current_feature("setup");
61 style_header("User List");
62 if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
@@ -147,31 +148,34 @@
148 zWith = mprintf(
149 " AND login NOT IN ("
150 "SELECT user FROM event WHERE user NOT NULL "
151 "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
152 " AND uid NOT IN (SELECT uid FROM rcvfrom)",
153 bHaveAlerts ?
154 " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
155 }else if( zWith && zWith[0] ){
156 zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
157 }else{
158 zWith = "";
159 }
160 db_prepare(&s,
161 /*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch'),"
162 /* 5 */"lower(login) AS sortkey, "
163 /* 6 */"CASE WHEN info LIKE '%%expires 20%%'"
164 " THEN substr(info,instr(lower(info),'expires')+8,10)"
165 " END AS exp,"
166 /* 7 */"atime,"
167 /* 8 */"user.mtime AS sorttime,"
168 /*9-11*/"%s"
169 " FROM user LEFT JOIN lastAccess ON login=uname"
170 " LEFT JOIN subscriber ON login=suname"
171 " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
172 " ORDER BY sorttime DESC",
173 bHaveAlerts
174 ? "subscriber.ssub, subscriber.subscriberId, subscriber.semail"
175 : "null, null, null",
176 zWith/*safe-for-%s*/
177 );
178 rNow = db_double(0.0, "SELECT julianday('now');");
179 while( db_step(&s)==SQLITE_ROW ){
180 int uid = db_column_int(&s, 0);
181 const char *zLogin = db_column_text(&s, 1);
@@ -181,12 +185,12 @@
185 const char *zSortKey = db_column_text(&s,5);
186 const char *zExp = db_column_text(&s,6);
187 double rATime = db_column_double(&s,7);
188 char *zAge = 0;
189 const char *zSub;
190 int sid = db_column_int(&s,10);
191 sqlite3_int64 sorttime = db_column_int64(&s, 8);
192 if( rATime>0.0 ){
193 zAge = human_readable_age(rNow - rATime);
194 }
195 if( bUbg ){
196 @ <tr style='background-color: %h(user_color(zLogin));'>
@@ -198,13 +202,13 @@
202 @ <td>%h(zCap)
203 @ <td>%h(zInfo)
204 @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
205 @ <td>%h(zExp?zExp:"")
206 @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
207 if( db_column_type(&s,9)==SQLITE_NULL ){
208 @ <td>
209 }else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){
210 @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a>
211 }else{
212 const char *zEmail = db_column_text(&s, 11);
213 char * zAt = zEmail ? mprintf(" &rarr; %h", zEmail) : mprintf("");
214 @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt)
215
+4 -3
--- src/shun.c
+++ src/shun.c
@@ -373,11 +373,11 @@
373373
login_check_credentials();
374374
if( !g.perm.Admin ){
375375
login_needed(0);
376376
return;
377377
}
378
- style_header("Artifact Receipts");
378
+ style_header("Xfer Log");
379379
style_submenu_element("Log-Menu", "setup-logmenu");
380380
if( showAll ){
381381
ofst = 0;
382382
}else{
383383
style_submenu_element("All", "rcvfromlist?all=1");
@@ -415,12 +415,13 @@
415415
" FROM rcvfrom LEFT JOIN user USING(uid)"
416416
" ORDER BY rcvid DESC LIMIT %d OFFSET %d",
417417
showAll ? -1 : perScreen+1, ofst
418418
);
419419
@ <p>Whenever new artifacts are added to the repository, either by
420
- @ push or using the web interface, an entry is made in the RCVFROM table
421
- @ to record the source of that artifact. This log facilitates
420
+ @ push or using the web interface or by "fossil commit" or similar,
421
+ @ an entry is made in the RCVFROM table
422
+ @ to record the source of those artifacts. This log facilitates
422423
@ finding and fixing attempts to inject illicit content into the
423424
@ repository.</p>
424425
@
425426
@ <p>Click on the "rcvid" to show a list of specific artifacts received
426427
@ by a transaction. After identifying illicit artifacts, remove them
427428
--- src/shun.c
+++ src/shun.c
@@ -373,11 +373,11 @@
373 login_check_credentials();
374 if( !g.perm.Admin ){
375 login_needed(0);
376 return;
377 }
378 style_header("Artifact Receipts");
379 style_submenu_element("Log-Menu", "setup-logmenu");
380 if( showAll ){
381 ofst = 0;
382 }else{
383 style_submenu_element("All", "rcvfromlist?all=1");
@@ -415,12 +415,13 @@
415 " FROM rcvfrom LEFT JOIN user USING(uid)"
416 " ORDER BY rcvid DESC LIMIT %d OFFSET %d",
417 showAll ? -1 : perScreen+1, ofst
418 );
419 @ <p>Whenever new artifacts are added to the repository, either by
420 @ push or using the web interface, an entry is made in the RCVFROM table
421 @ to record the source of that artifact. This log facilitates
 
422 @ finding and fixing attempts to inject illicit content into the
423 @ repository.</p>
424 @
425 @ <p>Click on the "rcvid" to show a list of specific artifacts received
426 @ by a transaction. After identifying illicit artifacts, remove them
427
--- src/shun.c
+++ src/shun.c
@@ -373,11 +373,11 @@
373 login_check_credentials();
374 if( !g.perm.Admin ){
375 login_needed(0);
376 return;
377 }
378 style_header("Xfer Log");
379 style_submenu_element("Log-Menu", "setup-logmenu");
380 if( showAll ){
381 ofst = 0;
382 }else{
383 style_submenu_element("All", "rcvfromlist?all=1");
@@ -415,12 +415,13 @@
415 " FROM rcvfrom LEFT JOIN user USING(uid)"
416 " ORDER BY rcvid DESC LIMIT %d OFFSET %d",
417 showAll ? -1 : perScreen+1, ofst
418 );
419 @ <p>Whenever new artifacts are added to the repository, either by
420 @ push or using the web interface or by "fossil commit" or similar,
421 @ an entry is made in the RCVFROM table
422 @ to record the source of those artifacts. This log facilitates
423 @ finding and fixing attempts to inject illicit content into the
424 @ repository.</p>
425 @
426 @ <p>Click on the "rcvid" to show a list of specific artifacts received
427 @ by a transaction. After identifying illicit artifacts, remove them
428
+70 -29
--- src/smtp.c
+++ src/smtp.c
@@ -156,13 +156,15 @@
156156
const char *zDest; /* Domain that will receive the email */
157157
char *zHostname; /* Hostname of SMTP server for zDest */
158158
u32 smtpFlags; /* Flags changing the operation */
159159
FILE *logFile; /* Write session transcript to this log file */
160160
Blob *pTranscript; /* Record session transcript here */
161
- int atEof; /* True after connection closes */
161
+ int bOpen; /* True if connection is Open */
162
+ int bFatal; /* Error is fatal. Do not retry */
162163
char *zErr; /* Error message */
163164
Blob inbuf; /* Input buffer */
165
+ UrlData url; /* Address of the server */
164166
};
165167
166168
/* Allowed values for SmtpSession.smtpFlags */
167169
#define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
168170
#define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
@@ -180,10 +182,32 @@
180182
blob_reset(&pSession->inbuf);
181183
fossil_free(pSession->zHostname);
182184
fossil_free(pSession->zErr);
183185
fossil_free(pSession);
184186
}
187
+
188
+/*
189
+** Set an error message on the SmtpSession
190
+*/
191
+static void smtp_set_error(
192
+ SmtpSession *p, /* The SMTP context */
193
+ int bFatal, /* Fatal error. Reset and retry is pointless */
194
+ const char *zFormat, /* Error message. */
195
+ ...
196
+){
197
+ if( bFatal ) p->bFatal = 1;
198
+ if( p->zErr==0 ){
199
+ va_list ap;
200
+ va_start(ap, zFormat);
201
+ p->zErr = vmprintf(zFormat, ap);
202
+ va_end(ap);
203
+ }
204
+ if( p->bOpen ){
205
+ socket_close();
206
+ p->bOpen = 0;
207
+ }
208
+}
185209
186210
/*
187211
** Allocate a new SmtpSession object.
188212
**
189213
** Both zFrom and zDest must be specified. smtpFlags may not contain
@@ -197,46 +221,39 @@
197221
const char *zDest, /* Domain of the server */
198222
u32 smtpFlags, /* Flags */
199223
int iPort /* TCP port if the SMTP_PORT flags is present */
200224
){
201225
SmtpSession *p;
202
- UrlData url;
203226
204227
p = fossil_malloc( sizeof(*p) );
205228
memset(p, 0, sizeof(*p));
206229
p->zFrom = zFrom;
207230
p->zDest = zDest;
208231
p->smtpFlags = smtpFlags;
209
- memset(&url, 0, sizeof(url));
210
- url.port = 25;
232
+ p->url.port = 25;
211233
blob_init(&p->inbuf, 0, 0);
212234
if( smtpFlags & SMTP_PORT ){
213
- url.port = iPort;
235
+ p->url.port = iPort;
214236
}
215237
if( (smtpFlags & SMTP_DIRECT)!=0 ){
216238
int i;
217239
p->zHostname = fossil_strdup(zDest);
218240
for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
219241
if( p->zHostname[i]==':' ){
220242
p->zHostname[i] = 0;
221
- url.port = atoi(&p->zHostname[i+1]);
243
+ p->url.port = atoi(&p->zHostname[i+1]);
222244
}
223245
}else{
224246
p->zHostname = smtp_mx_host(zDest);
225247
}
226248
if( p->zHostname==0 ){
227
- p->atEof = 1;
228
- p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
249
+ smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest);
229250
return p;
230251
}
231
- url.name = p->zHostname;
252
+ p->url.name = p->zHostname;
232253
socket_global_init();
233
- if( socket_open(&url) ){
234
- p->atEof = 1;
235
- p->zErr = socket_errmsg();
236
- socket_close();
237
- }
254
+ p->bOpen = 0;
238255
return p;
239256
}
240257
241258
/*
242259
** Configure debugging options on SmtpSession. Add all bits in
@@ -261,11 +278,11 @@
261278
static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
262279
Blob b = empty_blob;
263280
va_list ap;
264281
char *z;
265282
int n;
266
- if( p->atEof ) return;
283
+ if( !p->bOpen ) return;
267284
va_start(ap, zFormat);
268285
blob_vappendf(&b, zFormat, ap);
269286
va_end(ap);
270287
z = blob_buffer(&b);
271288
n = blob_size(&b);
@@ -297,11 +314,11 @@
297314
char *z = blob_buffer(&p->inbuf);
298315
int i = blob_tell(&p->inbuf);
299316
int nDelay = 0;
300317
if( i<n && z[n-1]=='\n' ){
301318
blob_line(&p->inbuf, in);
302
- }else if( p->atEof ){
319
+ }else if( !p->bOpen ){
303320
blob_init(in, 0, 0);
304321
}else{
305322
if( n>0 && i>=n ){
306323
blob_truncate(&p->inbuf, 0);
307324
blob_rewind(&p->inbuf);
@@ -320,13 +337,11 @@
320337
if( got==1000 ) continue;
321338
}
322339
nDelay++;
323340
if( nDelay>100 ){
324341
blob_init(in, 0, 0);
325
- p->zErr = mprintf("timeout");
326
- socket_close();
327
- p->atEof = 1;
342
+ smtp_set_error(p, 1, "client times out waiting on server response");
328343
return;
329344
}else{
330345
sqlite3_sleep(100);
331346
}
332347
}while( n<1 || z[n-1]!='\n' );
@@ -360,10 +375,11 @@
360375
){
361376
int n;
362377
char *z;
363378
blob_truncate(in, 0);
364379
smtp_recv_line(p, in);
380
+ blob_trim(in);
365381
z = blob_str(in);
366382
n = blob_size(in);
367383
if( z[0]=='#' ){
368384
*piCode = 0;
369385
*pbMore = 1;
@@ -381,48 +397,57 @@
381397
int smtp_client_quit(SmtpSession *p){
382398
Blob in = BLOB_INITIALIZER;
383399
int iCode = 0;
384400
int bMore = 0;
385401
char *zArg = 0;
386
- if( !p->atEof ){
402
+ if( p->bOpen ){
387403
smtp_send_line(p, "QUIT\r\n");
388404
do{
389405
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
390406
}while( bMore );
391
- p->atEof = 1;
407
+ p->bOpen = 0;
408
+ socket_close();
392409
}
393
- socket_close();
394410
return 0;
395411
}
396412
397413
/*
398414
** Begin a client SMTP session. Wait for the initial 220 then send
399415
** the EHLO and wait for a 250.
400416
**
401417
** Return 0 on success and non-zero for a failure.
402418
*/
403
-int smtp_client_startup(SmtpSession *p){
419
+static int smtp_client_startup(SmtpSession *p){
404420
Blob in = BLOB_INITIALIZER;
405421
int iCode = 0;
406422
int bMore = 0;
407423
char *zArg = 0;
408
- if( p==0 || p->atEof ) return 1;
424
+ if( p==0 || p->bFatal ) return 1;
425
+ if( socket_open(&p->url) ){
426
+ smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg());
427
+ return 1;
428
+ }
429
+ p->bOpen = 1;
409430
do{
410431
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
411432
}while( bMore );
412433
if( iCode!=220 ){
434
+ smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg);
413435
smtp_client_quit(p);
414436
return 1;
415437
}
416438
smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
417439
do{
418440
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
419441
}while( bMore );
420442
if( iCode!=250 ){
443
+ smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg);
421444
smtp_client_quit(p);
422445
return 1;
423446
}
447
+ fossil_free(p->zErr);
448
+ p->zErr = 0;
424449
return 0;
425450
}
426451
427452
/*
428453
** COMMAND: test-smtp-probe
@@ -550,27 +575,40 @@
550575
int iCode = 0;
551576
int bMore = 0;
552577
char *zArg = 0;
553578
Blob in;
554579
blob_init(&in, 0, 0);
580
+ if( !p->bOpen ){
581
+ if( !p->bFatal ) smtp_client_startup(p);
582
+ if( !p->bOpen ) return 1;
583
+ }
555584
smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
556585
do{
557586
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
558587
}while( bMore );
559
- if( iCode!=250 ) return 1;
588
+ if( iCode!=250 ){
589
+ smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg);
590
+ return 1;
591
+ }
560592
for(i=0; i<nTo; i++){
561593
smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
562594
do{
563595
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
564596
}while( bMore );
565
- if( iCode!=250 ) return 1;
597
+ if( iCode!=250 ){
598
+ smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg);
599
+ return 1;
600
+ }
566601
}
567602
smtp_send_line(p, "DATA\r\n");
568603
do{
569604
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
570605
}while( bMore );
571
- if( iCode!=354 ) return 1;
606
+ if( iCode!=354 ){
607
+ smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg);
608
+ return 1;
609
+ }
572610
smtp_send_email_body(zMsg, socket_send, 0);
573611
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
574612
fossil_print("C: # message content\nC: .\n");
575613
}
576614
if( p->smtpFlags & SMTP_TRACE_FILE ){
@@ -580,11 +618,15 @@
580618
blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
581619
}
582620
do{
583621
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
584622
}while( bMore );
585
- if( iCode!=250 ) return 1;
623
+ if( iCode!=250 ){
624
+ smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"",
625
+ iCode, zArg);
626
+ return 1;
627
+ }
586628
return 0;
587629
}
588630
589631
/*
590632
** The input is a base email address of the form "local@domain".
@@ -645,14 +687,13 @@
645687
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
646688
if( p->zErr ){
647689
fossil_fatal("%s", p->zErr);
648690
}
649691
fossil_print("Connection to \"%s\"\n", p->zHostname);
650
- smtp_client_startup(p);
651692
smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
652693
smtp_client_quit(p);
653694
if( p->zErr ){
654695
fossil_fatal("ERROR: %s\n", p->zErr);
655696
}
656697
smtp_session_free(p);
657698
blob_reset(&body);
658699
}
659700
--- src/smtp.c
+++ src/smtp.c
@@ -156,13 +156,15 @@
156 const char *zDest; /* Domain that will receive the email */
157 char *zHostname; /* Hostname of SMTP server for zDest */
158 u32 smtpFlags; /* Flags changing the operation */
159 FILE *logFile; /* Write session transcript to this log file */
160 Blob *pTranscript; /* Record session transcript here */
161 int atEof; /* True after connection closes */
 
162 char *zErr; /* Error message */
163 Blob inbuf; /* Input buffer */
 
164 };
165
166 /* Allowed values for SmtpSession.smtpFlags */
167 #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
168 #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
@@ -180,10 +182,32 @@
180 blob_reset(&pSession->inbuf);
181 fossil_free(pSession->zHostname);
182 fossil_free(pSession->zErr);
183 fossil_free(pSession);
184 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
186 /*
187 ** Allocate a new SmtpSession object.
188 **
189 ** Both zFrom and zDest must be specified. smtpFlags may not contain
@@ -197,46 +221,39 @@
197 const char *zDest, /* Domain of the server */
198 u32 smtpFlags, /* Flags */
199 int iPort /* TCP port if the SMTP_PORT flags is present */
200 ){
201 SmtpSession *p;
202 UrlData url;
203
204 p = fossil_malloc( sizeof(*p) );
205 memset(p, 0, sizeof(*p));
206 p->zFrom = zFrom;
207 p->zDest = zDest;
208 p->smtpFlags = smtpFlags;
209 memset(&url, 0, sizeof(url));
210 url.port = 25;
211 blob_init(&p->inbuf, 0, 0);
212 if( smtpFlags & SMTP_PORT ){
213 url.port = iPort;
214 }
215 if( (smtpFlags & SMTP_DIRECT)!=0 ){
216 int i;
217 p->zHostname = fossil_strdup(zDest);
218 for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
219 if( p->zHostname[i]==':' ){
220 p->zHostname[i] = 0;
221 url.port = atoi(&p->zHostname[i+1]);
222 }
223 }else{
224 p->zHostname = smtp_mx_host(zDest);
225 }
226 if( p->zHostname==0 ){
227 p->atEof = 1;
228 p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
229 return p;
230 }
231 url.name = p->zHostname;
232 socket_global_init();
233 if( socket_open(&url) ){
234 p->atEof = 1;
235 p->zErr = socket_errmsg();
236 socket_close();
237 }
238 return p;
239 }
240
241 /*
242 ** Configure debugging options on SmtpSession. Add all bits in
@@ -261,11 +278,11 @@
261 static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
262 Blob b = empty_blob;
263 va_list ap;
264 char *z;
265 int n;
266 if( p->atEof ) return;
267 va_start(ap, zFormat);
268 blob_vappendf(&b, zFormat, ap);
269 va_end(ap);
270 z = blob_buffer(&b);
271 n = blob_size(&b);
@@ -297,11 +314,11 @@
297 char *z = blob_buffer(&p->inbuf);
298 int i = blob_tell(&p->inbuf);
299 int nDelay = 0;
300 if( i<n && z[n-1]=='\n' ){
301 blob_line(&p->inbuf, in);
302 }else if( p->atEof ){
303 blob_init(in, 0, 0);
304 }else{
305 if( n>0 && i>=n ){
306 blob_truncate(&p->inbuf, 0);
307 blob_rewind(&p->inbuf);
@@ -320,13 +337,11 @@
320 if( got==1000 ) continue;
321 }
322 nDelay++;
323 if( nDelay>100 ){
324 blob_init(in, 0, 0);
325 p->zErr = mprintf("timeout");
326 socket_close();
327 p->atEof = 1;
328 return;
329 }else{
330 sqlite3_sleep(100);
331 }
332 }while( n<1 || z[n-1]!='\n' );
@@ -360,10 +375,11 @@
360 ){
361 int n;
362 char *z;
363 blob_truncate(in, 0);
364 smtp_recv_line(p, in);
 
365 z = blob_str(in);
366 n = blob_size(in);
367 if( z[0]=='#' ){
368 *piCode = 0;
369 *pbMore = 1;
@@ -381,48 +397,57 @@
381 int smtp_client_quit(SmtpSession *p){
382 Blob in = BLOB_INITIALIZER;
383 int iCode = 0;
384 int bMore = 0;
385 char *zArg = 0;
386 if( !p->atEof ){
387 smtp_send_line(p, "QUIT\r\n");
388 do{
389 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
390 }while( bMore );
391 p->atEof = 1;
 
392 }
393 socket_close();
394 return 0;
395 }
396
397 /*
398 ** Begin a client SMTP session. Wait for the initial 220 then send
399 ** the EHLO and wait for a 250.
400 **
401 ** Return 0 on success and non-zero for a failure.
402 */
403 int smtp_client_startup(SmtpSession *p){
404 Blob in = BLOB_INITIALIZER;
405 int iCode = 0;
406 int bMore = 0;
407 char *zArg = 0;
408 if( p==0 || p->atEof ) return 1;
 
 
 
 
 
409 do{
410 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
411 }while( bMore );
412 if( iCode!=220 ){
 
413 smtp_client_quit(p);
414 return 1;
415 }
416 smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
417 do{
418 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
419 }while( bMore );
420 if( iCode!=250 ){
 
421 smtp_client_quit(p);
422 return 1;
423 }
 
 
424 return 0;
425 }
426
427 /*
428 ** COMMAND: test-smtp-probe
@@ -550,27 +575,40 @@
550 int iCode = 0;
551 int bMore = 0;
552 char *zArg = 0;
553 Blob in;
554 blob_init(&in, 0, 0);
 
 
 
 
555 smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
556 do{
557 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
558 }while( bMore );
559 if( iCode!=250 ) return 1;
 
 
 
560 for(i=0; i<nTo; i++){
561 smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
562 do{
563 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
564 }while( bMore );
565 if( iCode!=250 ) return 1;
 
 
 
566 }
567 smtp_send_line(p, "DATA\r\n");
568 do{
569 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
570 }while( bMore );
571 if( iCode!=354 ) return 1;
 
 
 
572 smtp_send_email_body(zMsg, socket_send, 0);
573 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
574 fossil_print("C: # message content\nC: .\n");
575 }
576 if( p->smtpFlags & SMTP_TRACE_FILE ){
@@ -580,11 +618,15 @@
580 blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
581 }
582 do{
583 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
584 }while( bMore );
585 if( iCode!=250 ) return 1;
 
 
 
 
586 return 0;
587 }
588
589 /*
590 ** The input is a base email address of the form "local@domain".
@@ -645,14 +687,13 @@
645 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
646 if( p->zErr ){
647 fossil_fatal("%s", p->zErr);
648 }
649 fossil_print("Connection to \"%s\"\n", p->zHostname);
650 smtp_client_startup(p);
651 smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
652 smtp_client_quit(p);
653 if( p->zErr ){
654 fossil_fatal("ERROR: %s\n", p->zErr);
655 }
656 smtp_session_free(p);
657 blob_reset(&body);
658 }
659
--- src/smtp.c
+++ src/smtp.c
@@ -156,13 +156,15 @@
156 const char *zDest; /* Domain that will receive the email */
157 char *zHostname; /* Hostname of SMTP server for zDest */
158 u32 smtpFlags; /* Flags changing the operation */
159 FILE *logFile; /* Write session transcript to this log file */
160 Blob *pTranscript; /* Record session transcript here */
161 int bOpen; /* True if connection is Open */
162 int bFatal; /* Error is fatal. Do not retry */
163 char *zErr; /* Error message */
164 Blob inbuf; /* Input buffer */
165 UrlData url; /* Address of the server */
166 };
167
168 /* Allowed values for SmtpSession.smtpFlags */
169 #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
170 #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
@@ -180,10 +182,32 @@
182 blob_reset(&pSession->inbuf);
183 fossil_free(pSession->zHostname);
184 fossil_free(pSession->zErr);
185 fossil_free(pSession);
186 }
187
188 /*
189 ** Set an error message on the SmtpSession
190 */
191 static void smtp_set_error(
192 SmtpSession *p, /* The SMTP context */
193 int bFatal, /* Fatal error. Reset and retry is pointless */
194 const char *zFormat, /* Error message. */
195 ...
196 ){
197 if( bFatal ) p->bFatal = 1;
198 if( p->zErr==0 ){
199 va_list ap;
200 va_start(ap, zFormat);
201 p->zErr = vmprintf(zFormat, ap);
202 va_end(ap);
203 }
204 if( p->bOpen ){
205 socket_close();
206 p->bOpen = 0;
207 }
208 }
209
210 /*
211 ** Allocate a new SmtpSession object.
212 **
213 ** Both zFrom and zDest must be specified. smtpFlags may not contain
@@ -197,46 +221,39 @@
221 const char *zDest, /* Domain of the server */
222 u32 smtpFlags, /* Flags */
223 int iPort /* TCP port if the SMTP_PORT flags is present */
224 ){
225 SmtpSession *p;
 
226
227 p = fossil_malloc( sizeof(*p) );
228 memset(p, 0, sizeof(*p));
229 p->zFrom = zFrom;
230 p->zDest = zDest;
231 p->smtpFlags = smtpFlags;
232 p->url.port = 25;
 
233 blob_init(&p->inbuf, 0, 0);
234 if( smtpFlags & SMTP_PORT ){
235 p->url.port = iPort;
236 }
237 if( (smtpFlags & SMTP_DIRECT)!=0 ){
238 int i;
239 p->zHostname = fossil_strdup(zDest);
240 for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
241 if( p->zHostname[i]==':' ){
242 p->zHostname[i] = 0;
243 p->url.port = atoi(&p->zHostname[i+1]);
244 }
245 }else{
246 p->zHostname = smtp_mx_host(zDest);
247 }
248 if( p->zHostname==0 ){
249 smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest);
 
250 return p;
251 }
252 p->url.name = p->zHostname;
253 socket_global_init();
254 p->bOpen = 0;
 
 
 
 
255 return p;
256 }
257
258 /*
259 ** Configure debugging options on SmtpSession. Add all bits in
@@ -261,11 +278,11 @@
278 static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
279 Blob b = empty_blob;
280 va_list ap;
281 char *z;
282 int n;
283 if( !p->bOpen ) return;
284 va_start(ap, zFormat);
285 blob_vappendf(&b, zFormat, ap);
286 va_end(ap);
287 z = blob_buffer(&b);
288 n = blob_size(&b);
@@ -297,11 +314,11 @@
314 char *z = blob_buffer(&p->inbuf);
315 int i = blob_tell(&p->inbuf);
316 int nDelay = 0;
317 if( i<n && z[n-1]=='\n' ){
318 blob_line(&p->inbuf, in);
319 }else if( !p->bOpen ){
320 blob_init(in, 0, 0);
321 }else{
322 if( n>0 && i>=n ){
323 blob_truncate(&p->inbuf, 0);
324 blob_rewind(&p->inbuf);
@@ -320,13 +337,11 @@
337 if( got==1000 ) continue;
338 }
339 nDelay++;
340 if( nDelay>100 ){
341 blob_init(in, 0, 0);
342 smtp_set_error(p, 1, "client times out waiting on server response");
 
 
343 return;
344 }else{
345 sqlite3_sleep(100);
346 }
347 }while( n<1 || z[n-1]!='\n' );
@@ -360,10 +375,11 @@
375 ){
376 int n;
377 char *z;
378 blob_truncate(in, 0);
379 smtp_recv_line(p, in);
380 blob_trim(in);
381 z = blob_str(in);
382 n = blob_size(in);
383 if( z[0]=='#' ){
384 *piCode = 0;
385 *pbMore = 1;
@@ -381,48 +397,57 @@
397 int smtp_client_quit(SmtpSession *p){
398 Blob in = BLOB_INITIALIZER;
399 int iCode = 0;
400 int bMore = 0;
401 char *zArg = 0;
402 if( p->bOpen ){
403 smtp_send_line(p, "QUIT\r\n");
404 do{
405 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
406 }while( bMore );
407 p->bOpen = 0;
408 socket_close();
409 }
 
410 return 0;
411 }
412
413 /*
414 ** Begin a client SMTP session. Wait for the initial 220 then send
415 ** the EHLO and wait for a 250.
416 **
417 ** Return 0 on success and non-zero for a failure.
418 */
419 static int smtp_client_startup(SmtpSession *p){
420 Blob in = BLOB_INITIALIZER;
421 int iCode = 0;
422 int bMore = 0;
423 char *zArg = 0;
424 if( p==0 || p->bFatal ) return 1;
425 if( socket_open(&p->url) ){
426 smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg());
427 return 1;
428 }
429 p->bOpen = 1;
430 do{
431 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
432 }while( bMore );
433 if( iCode!=220 ){
434 smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg);
435 smtp_client_quit(p);
436 return 1;
437 }
438 smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
439 do{
440 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
441 }while( bMore );
442 if( iCode!=250 ){
443 smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg);
444 smtp_client_quit(p);
445 return 1;
446 }
447 fossil_free(p->zErr);
448 p->zErr = 0;
449 return 0;
450 }
451
452 /*
453 ** COMMAND: test-smtp-probe
@@ -550,27 +575,40 @@
575 int iCode = 0;
576 int bMore = 0;
577 char *zArg = 0;
578 Blob in;
579 blob_init(&in, 0, 0);
580 if( !p->bOpen ){
581 if( !p->bFatal ) smtp_client_startup(p);
582 if( !p->bOpen ) return 1;
583 }
584 smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
585 do{
586 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
587 }while( bMore );
588 if( iCode!=250 ){
589 smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg);
590 return 1;
591 }
592 for(i=0; i<nTo; i++){
593 smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
594 do{
595 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
596 }while( bMore );
597 if( iCode!=250 ){
598 smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg);
599 return 1;
600 }
601 }
602 smtp_send_line(p, "DATA\r\n");
603 do{
604 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
605 }while( bMore );
606 if( iCode!=354 ){
607 smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg);
608 return 1;
609 }
610 smtp_send_email_body(zMsg, socket_send, 0);
611 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
612 fossil_print("C: # message content\nC: .\n");
613 }
614 if( p->smtpFlags & SMTP_TRACE_FILE ){
@@ -580,11 +618,15 @@
618 blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
619 }
620 do{
621 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
622 }while( bMore );
623 if( iCode!=250 ){
624 smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"",
625 iCode, zArg);
626 return 1;
627 }
628 return 0;
629 }
630
631 /*
632 ** The input is a base email address of the form "local@domain".
@@ -645,14 +687,13 @@
687 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
688 if( p->zErr ){
689 fossil_fatal("%s", p->zErr);
690 }
691 fossil_print("Connection to \"%s\"\n", p->zHostname);
 
692 smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
693 smtp_client_quit(p);
694 if( p->zErr ){
695 fossil_fatal("ERROR: %s\n", p->zErr);
696 }
697 smtp_session_free(p);
698 blob_reset(&body);
699 }
700
+8 -2
--- src/stash.c
+++ src/stash.c
@@ -259,10 +259,11 @@
259259
const char *zComment; /* Comment to add to the stash */
260260
int stashid; /* ID of the new stash */
261261
int vid; /* Current check-out */
262262
263263
zComment = find_option("comment", "m", 1);
264
+ (void)fossil_text_editor();
264265
verify_all_options();
265266
if( zComment==0 ){
266267
Blob prompt; /* Prompt for stash comment */
267268
Blob comment; /* User comment reply */
268269
#if defined(_WIN32) || defined(__CYGWIN__)
@@ -508,19 +509,24 @@
508509
** COMMAND: stash
509510
**
510511
** Usage: %fossil stash SUBCOMMAND ARGS...
511512
**
512513
** > fossil stash
513
-** > fossil stash save ?-m|--comment COMMENT? ?FILES...?
514
-** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...?
514
+** > fossil stash save ?FILES...?
515
+** > fossil stash snapshot ?FILES...?
515516
**
516517
** Save the current changes in the working tree as a new stash.
517518
** Then revert the changes back to the last check-in. If FILES
518519
** are listed, then only stash and revert the named files. The
519520
** "save" verb can be omitted if and only if there are no other
520521
** arguments. The "snapshot" verb works the same as "save" but
521522
** omits the revert, keeping the check-out unchanged.
523
+**
524
+** Options:
525
+** --editor NAME Use the NAME editor to enter comment
526
+** -m|--comment COMMENT Comment text for the new stash
527
+**
522528
**
523529
** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM?
524530
**
525531
** List all changes sets currently stashed. Show information about
526532
** individual files in each changeset if -v or --verbose is used.
527533
--- src/stash.c
+++ src/stash.c
@@ -259,10 +259,11 @@
259 const char *zComment; /* Comment to add to the stash */
260 int stashid; /* ID of the new stash */
261 int vid; /* Current check-out */
262
263 zComment = find_option("comment", "m", 1);
 
264 verify_all_options();
265 if( zComment==0 ){
266 Blob prompt; /* Prompt for stash comment */
267 Blob comment; /* User comment reply */
268 #if defined(_WIN32) || defined(__CYGWIN__)
@@ -508,19 +509,24 @@
508 ** COMMAND: stash
509 **
510 ** Usage: %fossil stash SUBCOMMAND ARGS...
511 **
512 ** > fossil stash
513 ** > fossil stash save ?-m|--comment COMMENT? ?FILES...?
514 ** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...?
515 **
516 ** Save the current changes in the working tree as a new stash.
517 ** Then revert the changes back to the last check-in. If FILES
518 ** are listed, then only stash and revert the named files. The
519 ** "save" verb can be omitted if and only if there are no other
520 ** arguments. The "snapshot" verb works the same as "save" but
521 ** omits the revert, keeping the check-out unchanged.
 
 
 
 
 
522 **
523 ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM?
524 **
525 ** List all changes sets currently stashed. Show information about
526 ** individual files in each changeset if -v or --verbose is used.
527
--- src/stash.c
+++ src/stash.c
@@ -259,10 +259,11 @@
259 const char *zComment; /* Comment to add to the stash */
260 int stashid; /* ID of the new stash */
261 int vid; /* Current check-out */
262
263 zComment = find_option("comment", "m", 1);
264 (void)fossil_text_editor();
265 verify_all_options();
266 if( zComment==0 ){
267 Blob prompt; /* Prompt for stash comment */
268 Blob comment; /* User comment reply */
269 #if defined(_WIN32) || defined(__CYGWIN__)
@@ -508,19 +509,24 @@
509 ** COMMAND: stash
510 **
511 ** Usage: %fossil stash SUBCOMMAND ARGS...
512 **
513 ** > fossil stash
514 ** > fossil stash save ?FILES...?
515 ** > fossil stash snapshot ?FILES...?
516 **
517 ** Save the current changes in the working tree as a new stash.
518 ** Then revert the changes back to the last check-in. If FILES
519 ** are listed, then only stash and revert the named files. The
520 ** "save" verb can be omitted if and only if there are no other
521 ** arguments. The "snapshot" verb works the same as "save" but
522 ** omits the revert, keeping the check-out unchanged.
523 **
524 ** Options:
525 ** --editor NAME Use the NAME editor to enter comment
526 ** -m|--comment COMMENT Comment text for the new stash
527 **
528 **
529 ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM?
530 **
531 ** List all changes sets currently stashed. Show information about
532 ** individual files in each changeset if -v or --verbose is used.
533
+3 -2
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744744
** is evaluated before the header is rendered).
745745
*/
746746
Th_MaybeStore("default_csp", zDfltCsp);
747747
fossil_free(zDfltCsp);
748748
Th_Store("nonce", zNonce);
749
- Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750
- Th_Store("project_description", db_get("project-description",""));
749
+ Th_StoreUnsafe("project_name",
750
+ db_get("project-name","Unnamed Fossil Project"));
751
+ Th_StoreUnsafe("project_description", db_get("project-description",""));
751752
if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752753
Th_Store("baseurl", g.zBaseURL);
753754
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754755
Th_Store("home", g.zTop);
755756
Th_Store("index_page", db_get("index-page","/home"));
756757
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744 ** is evaluated before the header is rendered).
745 */
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750 Th_Store("project_description", db_get("project-description",""));
 
751 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752 Th_Store("baseurl", g.zBaseURL);
753 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754 Th_Store("home", g.zTop);
755 Th_Store("index_page", db_get("index-page","/home"));
756
--- src/style.c
+++ src/style.c
@@ -744,12 +744,13 @@
744 ** is evaluated before the header is rendered).
745 */
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_StoreUnsafe("project_name",
750 db_get("project-name","Unnamed Fossil Project"));
751 Th_StoreUnsafe("project_description", db_get("project-description",""));
752 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
753 Th_Store("baseurl", g.zBaseURL);
754 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
755 Th_Store("home", g.zTop);
756 Th_Store("index_page", db_get("index-page","/home"));
757
+87 -54
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
77
#include "config.h"
88
#include "th.h"
99
#include <string.h>
1010
#include <assert.h>
1111
12
+/*
13
+** External routines
14
+*/
15
+void fossil_panic(const char*,...);
16
+void fossil_errorlog(const char*,...);
17
+
1218
/*
1319
** Values used for element values in the tcl_platform array.
1420
*/
1521
1622
#if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
197203
*/
198204
struct Buffer {
199205
char *zBuf;
200206
int nBuf;
201207
int nBufAlloc;
208
+ int bTaint;
202209
};
203210
typedef struct Buffer Buffer;
204211
static void thBufferInit(Buffer *);
205212
static void thBufferFree(Th_Interp *interp, Buffer *);
206213
@@ -209,10 +216,18 @@
209216
** be NULL as long as the number of bytes to copy is zero.
210217
*/
211218
static void th_memcpy(void *dest, const void *src, size_t n){
212219
if( n>0 ) memcpy(dest,src,n);
213220
}
221
+
222
+/*
223
+** An oversized string has been encountered. Do not try to recover.
224
+** Panic the process.
225
+*/
226
+void Th_OversizeString(void){
227
+ fossil_panic("string too large. maximum size 286MB.");
228
+}
214229
215230
/*
216231
** Append nAdd bytes of content copied from zAdd to the end of buffer
217232
** pBuffer. If there is not enough space currently allocated, resize
218233
** the allocation to make space.
@@ -219,40 +234,46 @@
219234
*/
220235
static void thBufferWriteResize(
221236
Th_Interp *interp,
222237
Buffer *pBuffer,
223238
const char *zAdd,
224
- int nAdd
239
+ int nAddX
225240
){
241
+ int nAdd = TH1_LEN(nAddX);
226242
int nNew = (pBuffer->nBuf+nAdd)*2+32;
227243
#if defined(TH_MEMDEBUG)
228244
char *zNew = (char *)Th_Malloc(interp, nNew);
245
+ TH1_SIZECHECK(nNew);
229246
th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
230247
Th_Free(interp, pBuffer->zBuf);
231248
pBuffer->zBuf = zNew;
232249
#else
233250
int nOld = pBuffer->nBufAlloc;
251
+ TH1_SIZECHECK(nNew);
234252
pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
235253
memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
236254
#endif
237255
pBuffer->nBufAlloc = nNew;
238256
th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
239257
pBuffer->nBuf += nAdd;
258
+ TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
240259
}
241260
static void thBufferWriteFast(
242261
Th_Interp *interp,
243262
Buffer *pBuffer,
244263
const char *zAdd,
245
- int nAdd
264
+ int nAddX
246265
){
266
+ int nAdd = TH1_LEN(nAddX);
247267
if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
248
- thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
268
+ thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
249269
}else{
250270
if( pBuffer->zBuf ){
251271
memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
252272
}
253273
pBuffer->nBuf += nAdd;
274
+ TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
254275
}
255276
}
256277
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
257278
258279
/*
@@ -704,24 +725,25 @@
704725
int nWord
705726
){
706727
int rc = TH_OK;
707728
Buffer output;
708729
int i;
730
+ int nn = TH1_LEN(nWord);
709731
710732
thBufferInit(&output);
711733
712
- if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
713
- thBufferWrite(interp, &output, &zWord[1], nWord-2);
734
+ if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
735
+ thBufferWrite(interp, &output, &zWord[1], nn-2);
714736
}else{
715737
716738
/* If the word is surrounded by double-quotes strip these away. */
717
- if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
739
+ if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
718740
zWord++;
719
- nWord -= 2;
741
+ nn -= 2;
720742
}
721743
722
- for(i=0; rc==TH_OK && i<nWord; i++){
744
+ for(i=0; rc==TH_OK && i<nn; i++){
723745
int nGet;
724746
725747
int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
726748
int (*xSubst)(Th_Interp *, const char*, int) = 0;
727749
@@ -743,11 +765,11 @@
743765
thBufferAddChar(interp, &output, zWord[i]);
744766
continue; /* Go to the next iteration of the for(...) loop */
745767
}
746768
}
747769
748
- rc = xGet(interp, &zWord[i], nWord-i, &nGet);
770
+ rc = xGet(interp, &zWord[i], nn-i, &nGet);
749771
if( rc==TH_OK ){
750772
rc = xSubst(interp, &zWord[i], nGet);
751773
}
752774
if( rc==TH_OK ){
753775
const char *zRes;
@@ -758,11 +780,11 @@
758780
}
759781
}
760782
}
761783
762784
if( rc==TH_OK ){
763
- Th_SetResult(interp, output.zBuf, output.nBuf);
785
+ Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
764786
}
765787
thBufferFree(interp, &output);
766788
return rc;
767789
}
768790
@@ -826,11 +848,11 @@
826848
Buffer strbuf;
827849
Buffer lenbuf;
828850
int nCount = 0;
829851
830852
const char *zInput = zList;
831
- int nInput = nList;
853
+ int nInput = TH1_LEN(nList);
832854
833855
thBufferInit(&strbuf);
834856
thBufferInit(&lenbuf);
835857
836858
while( nInput>0 ){
@@ -837,19 +859,19 @@
837859
const char *zWord;
838860
int nWord;
839861
840862
thNextSpace(interp, zInput, nInput, &nWord);
841863
zInput += nWord;
842
- nInput = nList-(zInput-zList);
864
+ nInput = TH1_LEN(nList)-(zInput-zList);
843865
844866
if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
845867
|| TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
846868
){
847869
goto finish;
848870
}
849
- zInput = &zInput[nWord];
850
- nInput = nList-(zInput-zList);
871
+ zInput = &zInput[TH1_LEN(nWord)];
872
+ nInput = TH1_LEN(nList)-(zInput-zList);
851873
if( nWord>0 ){
852874
zWord = Th_GetResult(interp, &nWord);
853875
thBufferWrite(interp, &strbuf, zWord, nWord);
854876
thBufferAddChar(interp, &strbuf, 0);
855877
thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
872894
zElem = (char *)&anElem[nCount];
873895
th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
874896
th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
875897
for(i=0; i<nCount;i++){
876898
azElem[i] = zElem;
877
- zElem += (anElem[i] + 1);
899
+ zElem += (TH1_LEN(anElem[i]) + 1);
878900
}
879901
*pazElem = azElem;
880902
*panElem = anElem;
881903
}
882904
if( pnCount ){
@@ -894,12 +916,17 @@
894916
** in the current stack frame.
895917
*/
896918
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
897919
int rc = TH_OK;
898920
const char *zInput = zProgram;
899
- int nInput = nProgram;
921
+ int nInput = TH1_LEN(nProgram);
900922
923
+ if( TH1_TAINTED(nProgram)
924
+ && Th_ReportTaint(interp, "script", zProgram, nProgram)
925
+ ){
926
+ return TH_ERROR;
927
+ }
901928
while( rc==TH_OK && nInput ){
902929
Th_HashEntry *pEntry;
903930
int nSpace;
904931
const char *zFirst;
905932
@@ -949,13 +976,13 @@
949976
if( rc!=TH_OK ) continue;
950977
951978
if( argc>0 ){
952979
953980
/* Look up the command name in the command hash-table. */
954
- pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
981
+ pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
955982
if( !pEntry ){
956
- Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
983
+ Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
957984
rc = TH_ERROR;
958985
}
959986
960987
/* Call the command procedure. */
961988
if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
10531080
}else{
10541081
int nInput = nProgram;
10551082
10561083
if( nInput<0 ){
10571084
nInput = th_strlen(zProgram);
1085
+ }else{
1086
+ nInput = TH1_LEN(nInput);
10581087
}
10591088
rc = thEvalLocal(interp, zProgram, nInput);
10601089
}
10611090
10621091
interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
10951124
int isGlobal = 0;
10961125
int i;
10971126
10981127
if( nVarname<0 ){
10991128
nVarname = th_strlen(zVarname);
1129
+ }else{
1130
+ nVarname = TH1_LEN(nVarname);
11001131
}
11011132
nOuter = nVarname;
11021133
11031134
/* If the variable name starts with "::", then do the lookup is in the
11041135
** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
12711302
}
12721303
12731304
return Th_SetResult(interp, pValue->zData, pValue->nData);
12741305
}
12751306
1276
-/*
1277
-** If interp has a variable with the given name, its value is returned
1278
-** and its length is returned via *nOut if nOut is not NULL. If
1279
-** interp has no such var then NULL is returned without setting any
1280
-** error state and *nOut, if not NULL, is set to -1. The returned value
1281
-** is owned by the interpreter and may be invalidated the next time
1282
-** the interpreter is modified.
1283
-*/
1284
-const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
1285
- int *nOut){
1286
- Th_Variable *pValue;
1287
-
1288
- pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
1289
- if( !pValue || !pValue->zData ){
1290
- if( nOut!=0 ) *nOut = -1;
1291
- return NULL;
1292
- }
1293
- if( nOut!=0 ) *nOut = pValue->nData;
1294
- return pValue->zData;
1295
-}
1296
-
12971307
/*
12981308
** Return true if variable (zVar, nVar) exists.
12991309
*/
13001310
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
13011311
Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
13241334
int nVar,
13251335
const char *zValue,
13261336
int nValue
13271337
){
13281338
Th_Variable *pValue;
1339
+ int nn;
13291340
1341
+ nVar = TH1_LEN(nVar);
13301342
pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
13311343
if( !pValue ){
13321344
return TH_ERROR;
13331345
}
13341346
13351347
if( nValue<0 ){
1336
- nValue = th_strlen(zValue);
1348
+ nn = th_strlen(zValue);
1349
+ }else{
1350
+ nn = TH1_LEN(nValue);
13371351
}
13381352
if( pValue->zData ){
13391353
Th_Free(interp, pValue->zData);
13401354
pValue->zData = 0;
13411355
}
13421356
1343
- assert(zValue || nValue==0);
1344
- pValue->zData = Th_Malloc(interp, nValue+1);
1345
- pValue->zData[nValue] = '\0';
1346
- th_memcpy(pValue->zData, zValue, nValue);
1357
+ assert(zValue || nn==0);
1358
+ pValue->zData = Th_Malloc(interp, nn+1);
1359
+ pValue->zData[nn] = '\0';
1360
+ th_memcpy(pValue->zData, zValue, nn);
13471361
pValue->nData = nValue;
13481362
13491363
return TH_OK;
13501364
}
13511365
@@ -1458,10 +1472,12 @@
14581472
*/
14591473
char *th_strdup(Th_Interp *interp, const char *z, int n){
14601474
char *zRes;
14611475
if( n<0 ){
14621476
n = th_strlen(z);
1477
+ }else{
1478
+ n = TH1_LEN(n);
14631479
}
14641480
zRes = Th_Malloc(interp, n+1);
14651481
th_memcpy(zRes, z, n);
14661482
zRes[n] = '\0';
14671483
return zRes;
@@ -1519,13 +1535,14 @@
15191535
n = th_strlen(z);
15201536
}
15211537
15221538
if( z && n>0 ){
15231539
char *zResult;
1524
- zResult = Th_Malloc(pInterp, n+1);
1525
- th_memcpy(zResult, z, n);
1526
- zResult[n] = '\0';
1540
+ int nn = TH1_LEN(n);
1541
+ zResult = Th_Malloc(pInterp, nn+1);
1542
+ th_memcpy(zResult, z, nn);
1543
+ zResult[nn] = '\0';
15271544
pInterp->zResult = zResult;
15281545
pInterp->nResult = n;
15291546
}
15301547
15311548
return TH_OK;
@@ -1777,15 +1794,19 @@
17771794
int hasSpecialChar = 0; /* Whitespace or {}[]'" */
17781795
int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
17791796
int nBrace = 0;
17801797
17811798
output.zBuf = *pzList;
1782
- output.nBuf = *pnList;
1799
+ output.nBuf = TH1_LEN(*pnList);
17831800
output.nBufAlloc = output.nBuf;
1801
+ output.bTaint = 0;
1802
+ TH1_XFER_TAINT(output.bTaint, *pnList);
17841803
17851804
if( nElem<0 ){
17861805
nElem = th_strlen(zElem);
1806
+ }else{
1807
+ nElem = TH1_LEN(nElem);
17871808
}
17881809
if( output.nBuf>0 ){
17891810
thBufferAddChar(interp, &output, ' ');
17901811
}
17911812
@@ -1834,24 +1855,28 @@
18341855
int *pnStr, /* IN/OUT: Current length of *pzStr */
18351856
const char *zElem, /* Data to append */
18361857
int nElem /* Length of nElem */
18371858
){
18381859
char *zNew;
1839
- int nNew;
1860
+ long long int nNew;
1861
+ int nn;
18401862
18411863
if( nElem<0 ){
1842
- nElem = th_strlen(zElem);
1864
+ nn = th_strlen(zElem);
1865
+ }else{
1866
+ nn = TH1_LEN(nElem);
18431867
}
18441868
1845
- nNew = *pnStr + nElem;
1869
+ nNew = TH1_LEN(*pnStr) + nn;
1870
+ TH1_SIZECHECK(nNew);
18461871
zNew = Th_Malloc(interp, nNew);
18471872
th_memcpy(zNew, *pzStr, *pnStr);
1848
- th_memcpy(&zNew[*pnStr], zElem, nElem);
1873
+ th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
18491874
18501875
Th_Free(interp, *pzStr);
18511876
*pzStr = zNew;
1852
- *pnStr = nNew;
1877
+ *pnStr = (int)nNew;
18531878
18541879
return TH_OK;
18551880
}
18561881
18571882
/*
@@ -2106,16 +2131,18 @@
21062131
/* Evaluate left and right arguments, if they exist. */
21072132
if( pExpr->pLeft ){
21082133
rc = exprEval(interp, pExpr->pLeft);
21092134
if( rc==TH_OK ){
21102135
zLeft = Th_TakeResult(interp, &nLeft);
2136
+ nLeft = TH1_LEN(nLeft);
21112137
}
21122138
}
21132139
if( rc==TH_OK && pExpr->pRight ){
21142140
rc = exprEval(interp, pExpr->pRight);
21152141
if( rc==TH_OK ){
21162142
zRight = Th_TakeResult(interp, &nRight);
2143
+ nRight = TH1_LEN(nRight);
21172144
}
21182145
}
21192146
21202147
/* Convert arguments to their required forms. */
21212148
if( rc==TH_OK ){
@@ -2456,10 +2483,12 @@
24562483
int nToken = 0;
24572484
Expr **apToken = 0;
24582485
24592486
if( nExpr<0 ){
24602487
nExpr = th_strlen(zExpr);
2488
+ }else{
2489
+ nExpr = TH1_LEN(nExpr);
24612490
}
24622491
24632492
/* Parse the expression to a list of tokens. */
24642493
rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
24652494
@@ -2567,10 +2596,12 @@
25672596
Th_HashEntry *pRet;
25682597
Th_HashEntry **ppRet;
25692598
25702599
if( nKey<0 ){
25712600
nKey = th_strlen(zKey);
2601
+ }else{
2602
+ nKey = TH1_LEN(nKey);
25722603
}
25732604
25742605
for(i=0; i<nKey; i++){
25752606
iKey = (iKey<<3) ^ iKey ^ zKey[i];
25762607
}
@@ -2800,10 +2831,12 @@
28002831
int base = 10;
28012832
int (*isdigit)(char) = th_isdigit;
28022833
28032834
if( n<0 ){
28042835
n = th_strlen(z);
2836
+ }else{
2837
+ n = TH1_LEN(n);
28052838
}
28062839
28072840
if( n>1 && (z[0]=='-' || z[0]=='+') ){
28082841
i = 1;
28092842
}
@@ -2859,11 +2892,11 @@
28592892
const char *z,
28602893
int n,
28612894
double *pfOut
28622895
){
28632896
if( !sqlite3IsNumber((const char *)z, 0) ){
2864
- Th_ErrorMessage(interp, "expected number, got: \"", z, n);
2897
+ Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
28652898
return TH_ERROR;
28662899
}
28672900
28682901
sqlite3AtoF((const char *)z, pfOut);
28692902
return TH_OK;
28702903
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
 
 
 
 
 
 
12 /*
13 ** Values used for element values in the tcl_platform array.
14 */
15
16 #if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
197 */
198 struct Buffer {
199 char *zBuf;
200 int nBuf;
201 int nBufAlloc;
 
202 };
203 typedef struct Buffer Buffer;
204 static void thBufferInit(Buffer *);
205 static void thBufferFree(Th_Interp *interp, Buffer *);
206
@@ -209,10 +216,18 @@
209 ** be NULL as long as the number of bytes to copy is zero.
210 */
211 static void th_memcpy(void *dest, const void *src, size_t n){
212 if( n>0 ) memcpy(dest,src,n);
213 }
 
 
 
 
 
 
 
 
214
215 /*
216 ** Append nAdd bytes of content copied from zAdd to the end of buffer
217 ** pBuffer. If there is not enough space currently allocated, resize
218 ** the allocation to make space.
@@ -219,40 +234,46 @@
219 */
220 static void thBufferWriteResize(
221 Th_Interp *interp,
222 Buffer *pBuffer,
223 const char *zAdd,
224 int nAdd
225 ){
 
226 int nNew = (pBuffer->nBuf+nAdd)*2+32;
227 #if defined(TH_MEMDEBUG)
228 char *zNew = (char *)Th_Malloc(interp, nNew);
 
229 th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
230 Th_Free(interp, pBuffer->zBuf);
231 pBuffer->zBuf = zNew;
232 #else
233 int nOld = pBuffer->nBufAlloc;
 
234 pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
235 memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
236 #endif
237 pBuffer->nBufAlloc = nNew;
238 th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
239 pBuffer->nBuf += nAdd;
 
240 }
241 static void thBufferWriteFast(
242 Th_Interp *interp,
243 Buffer *pBuffer,
244 const char *zAdd,
245 int nAdd
246 ){
 
247 if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
248 thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
249 }else{
250 if( pBuffer->zBuf ){
251 memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
252 }
253 pBuffer->nBuf += nAdd;
 
254 }
255 }
256 #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
257
258 /*
@@ -704,24 +725,25 @@
704 int nWord
705 ){
706 int rc = TH_OK;
707 Buffer output;
708 int i;
 
709
710 thBufferInit(&output);
711
712 if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
713 thBufferWrite(interp, &output, &zWord[1], nWord-2);
714 }else{
715
716 /* If the word is surrounded by double-quotes strip these away. */
717 if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
718 zWord++;
719 nWord -= 2;
720 }
721
722 for(i=0; rc==TH_OK && i<nWord; i++){
723 int nGet;
724
725 int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
726 int (*xSubst)(Th_Interp *, const char*, int) = 0;
727
@@ -743,11 +765,11 @@
743 thBufferAddChar(interp, &output, zWord[i]);
744 continue; /* Go to the next iteration of the for(...) loop */
745 }
746 }
747
748 rc = xGet(interp, &zWord[i], nWord-i, &nGet);
749 if( rc==TH_OK ){
750 rc = xSubst(interp, &zWord[i], nGet);
751 }
752 if( rc==TH_OK ){
753 const char *zRes;
@@ -758,11 +780,11 @@
758 }
759 }
760 }
761
762 if( rc==TH_OK ){
763 Th_SetResult(interp, output.zBuf, output.nBuf);
764 }
765 thBufferFree(interp, &output);
766 return rc;
767 }
768
@@ -826,11 +848,11 @@
826 Buffer strbuf;
827 Buffer lenbuf;
828 int nCount = 0;
829
830 const char *zInput = zList;
831 int nInput = nList;
832
833 thBufferInit(&strbuf);
834 thBufferInit(&lenbuf);
835
836 while( nInput>0 ){
@@ -837,19 +859,19 @@
837 const char *zWord;
838 int nWord;
839
840 thNextSpace(interp, zInput, nInput, &nWord);
841 zInput += nWord;
842 nInput = nList-(zInput-zList);
843
844 if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
845 || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
846 ){
847 goto finish;
848 }
849 zInput = &zInput[nWord];
850 nInput = nList-(zInput-zList);
851 if( nWord>0 ){
852 zWord = Th_GetResult(interp, &nWord);
853 thBufferWrite(interp, &strbuf, zWord, nWord);
854 thBufferAddChar(interp, &strbuf, 0);
855 thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
872 zElem = (char *)&anElem[nCount];
873 th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
874 th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
875 for(i=0; i<nCount;i++){
876 azElem[i] = zElem;
877 zElem += (anElem[i] + 1);
878 }
879 *pazElem = azElem;
880 *panElem = anElem;
881 }
882 if( pnCount ){
@@ -894,12 +916,17 @@
894 ** in the current stack frame.
895 */
896 static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
897 int rc = TH_OK;
898 const char *zInput = zProgram;
899 int nInput = nProgram;
900
 
 
 
 
 
901 while( rc==TH_OK && nInput ){
902 Th_HashEntry *pEntry;
903 int nSpace;
904 const char *zFirst;
905
@@ -949,13 +976,13 @@
949 if( rc!=TH_OK ) continue;
950
951 if( argc>0 ){
952
953 /* Look up the command name in the command hash-table. */
954 pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
955 if( !pEntry ){
956 Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
957 rc = TH_ERROR;
958 }
959
960 /* Call the command procedure. */
961 if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
1053 }else{
1054 int nInput = nProgram;
1055
1056 if( nInput<0 ){
1057 nInput = th_strlen(zProgram);
 
 
1058 }
1059 rc = thEvalLocal(interp, zProgram, nInput);
1060 }
1061
1062 interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
1095 int isGlobal = 0;
1096 int i;
1097
1098 if( nVarname<0 ){
1099 nVarname = th_strlen(zVarname);
 
 
1100 }
1101 nOuter = nVarname;
1102
1103 /* If the variable name starts with "::", then do the lookup is in the
1104 ** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
1271 }
1272
1273 return Th_SetResult(interp, pValue->zData, pValue->nData);
1274 }
1275
1276 /*
1277 ** If interp has a variable with the given name, its value is returned
1278 ** and its length is returned via *nOut if nOut is not NULL. If
1279 ** interp has no such var then NULL is returned without setting any
1280 ** error state and *nOut, if not NULL, is set to -1. The returned value
1281 ** is owned by the interpreter and may be invalidated the next time
1282 ** the interpreter is modified.
1283 */
1284 const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
1285 int *nOut){
1286 Th_Variable *pValue;
1287
1288 pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
1289 if( !pValue || !pValue->zData ){
1290 if( nOut!=0 ) *nOut = -1;
1291 return NULL;
1292 }
1293 if( nOut!=0 ) *nOut = pValue->nData;
1294 return pValue->zData;
1295 }
1296
1297 /*
1298 ** Return true if variable (zVar, nVar) exists.
1299 */
1300 int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1301 Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
1324 int nVar,
1325 const char *zValue,
1326 int nValue
1327 ){
1328 Th_Variable *pValue;
 
1329
 
1330 pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
1331 if( !pValue ){
1332 return TH_ERROR;
1333 }
1334
1335 if( nValue<0 ){
1336 nValue = th_strlen(zValue);
 
 
1337 }
1338 if( pValue->zData ){
1339 Th_Free(interp, pValue->zData);
1340 pValue->zData = 0;
1341 }
1342
1343 assert(zValue || nValue==0);
1344 pValue->zData = Th_Malloc(interp, nValue+1);
1345 pValue->zData[nValue] = '\0';
1346 th_memcpy(pValue->zData, zValue, nValue);
1347 pValue->nData = nValue;
1348
1349 return TH_OK;
1350 }
1351
@@ -1458,10 +1472,12 @@
1458 */
1459 char *th_strdup(Th_Interp *interp, const char *z, int n){
1460 char *zRes;
1461 if( n<0 ){
1462 n = th_strlen(z);
 
 
1463 }
1464 zRes = Th_Malloc(interp, n+1);
1465 th_memcpy(zRes, z, n);
1466 zRes[n] = '\0';
1467 return zRes;
@@ -1519,13 +1535,14 @@
1519 n = th_strlen(z);
1520 }
1521
1522 if( z && n>0 ){
1523 char *zResult;
1524 zResult = Th_Malloc(pInterp, n+1);
1525 th_memcpy(zResult, z, n);
1526 zResult[n] = '\0';
 
1527 pInterp->zResult = zResult;
1528 pInterp->nResult = n;
1529 }
1530
1531 return TH_OK;
@@ -1777,15 +1794,19 @@
1777 int hasSpecialChar = 0; /* Whitespace or {}[]'" */
1778 int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
1779 int nBrace = 0;
1780
1781 output.zBuf = *pzList;
1782 output.nBuf = *pnList;
1783 output.nBufAlloc = output.nBuf;
 
 
1784
1785 if( nElem<0 ){
1786 nElem = th_strlen(zElem);
 
 
1787 }
1788 if( output.nBuf>0 ){
1789 thBufferAddChar(interp, &output, ' ');
1790 }
1791
@@ -1834,24 +1855,28 @@
1834 int *pnStr, /* IN/OUT: Current length of *pzStr */
1835 const char *zElem, /* Data to append */
1836 int nElem /* Length of nElem */
1837 ){
1838 char *zNew;
1839 int nNew;
 
1840
1841 if( nElem<0 ){
1842 nElem = th_strlen(zElem);
 
 
1843 }
1844
1845 nNew = *pnStr + nElem;
 
1846 zNew = Th_Malloc(interp, nNew);
1847 th_memcpy(zNew, *pzStr, *pnStr);
1848 th_memcpy(&zNew[*pnStr], zElem, nElem);
1849
1850 Th_Free(interp, *pzStr);
1851 *pzStr = zNew;
1852 *pnStr = nNew;
1853
1854 return TH_OK;
1855 }
1856
1857 /*
@@ -2106,16 +2131,18 @@
2106 /* Evaluate left and right arguments, if they exist. */
2107 if( pExpr->pLeft ){
2108 rc = exprEval(interp, pExpr->pLeft);
2109 if( rc==TH_OK ){
2110 zLeft = Th_TakeResult(interp, &nLeft);
 
2111 }
2112 }
2113 if( rc==TH_OK && pExpr->pRight ){
2114 rc = exprEval(interp, pExpr->pRight);
2115 if( rc==TH_OK ){
2116 zRight = Th_TakeResult(interp, &nRight);
 
2117 }
2118 }
2119
2120 /* Convert arguments to their required forms. */
2121 if( rc==TH_OK ){
@@ -2456,10 +2483,12 @@
2456 int nToken = 0;
2457 Expr **apToken = 0;
2458
2459 if( nExpr<0 ){
2460 nExpr = th_strlen(zExpr);
 
 
2461 }
2462
2463 /* Parse the expression to a list of tokens. */
2464 rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
2465
@@ -2567,10 +2596,12 @@
2567 Th_HashEntry *pRet;
2568 Th_HashEntry **ppRet;
2569
2570 if( nKey<0 ){
2571 nKey = th_strlen(zKey);
 
 
2572 }
2573
2574 for(i=0; i<nKey; i++){
2575 iKey = (iKey<<3) ^ iKey ^ zKey[i];
2576 }
@@ -2800,10 +2831,12 @@
2800 int base = 10;
2801 int (*isdigit)(char) = th_isdigit;
2802
2803 if( n<0 ){
2804 n = th_strlen(z);
 
 
2805 }
2806
2807 if( n>1 && (z[0]=='-' || z[0]=='+') ){
2808 i = 1;
2809 }
@@ -2859,11 +2892,11 @@
2859 const char *z,
2860 int n,
2861 double *pfOut
2862 ){
2863 if( !sqlite3IsNumber((const char *)z, 0) ){
2864 Th_ErrorMessage(interp, "expected number, got: \"", z, n);
2865 return TH_ERROR;
2866 }
2867
2868 sqlite3AtoF((const char *)z, pfOut);
2869 return TH_OK;
2870
--- src/th.c
+++ src/th.c
@@ -7,10 +7,16 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
12 /*
13 ** External routines
14 */
15 void fossil_panic(const char*,...);
16 void fossil_errorlog(const char*,...);
17
18 /*
19 ** Values used for element values in the tcl_platform array.
20 */
21
22 #if !defined(TH_ENGINE)
@@ -197,10 +203,11 @@
203 */
204 struct Buffer {
205 char *zBuf;
206 int nBuf;
207 int nBufAlloc;
208 int bTaint;
209 };
210 typedef struct Buffer Buffer;
211 static void thBufferInit(Buffer *);
212 static void thBufferFree(Th_Interp *interp, Buffer *);
213
@@ -209,10 +216,18 @@
216 ** be NULL as long as the number of bytes to copy is zero.
217 */
218 static void th_memcpy(void *dest, const void *src, size_t n){
219 if( n>0 ) memcpy(dest,src,n);
220 }
221
222 /*
223 ** An oversized string has been encountered. Do not try to recover.
224 ** Panic the process.
225 */
226 void Th_OversizeString(void){
227 fossil_panic("string too large. maximum size 286MB.");
228 }
229
230 /*
231 ** Append nAdd bytes of content copied from zAdd to the end of buffer
232 ** pBuffer. If there is not enough space currently allocated, resize
233 ** the allocation to make space.
@@ -219,40 +234,46 @@
234 */
235 static void thBufferWriteResize(
236 Th_Interp *interp,
237 Buffer *pBuffer,
238 const char *zAdd,
239 int nAddX
240 ){
241 int nAdd = TH1_LEN(nAddX);
242 int nNew = (pBuffer->nBuf+nAdd)*2+32;
243 #if defined(TH_MEMDEBUG)
244 char *zNew = (char *)Th_Malloc(interp, nNew);
245 TH1_SIZECHECK(nNew);
246 th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
247 Th_Free(interp, pBuffer->zBuf);
248 pBuffer->zBuf = zNew;
249 #else
250 int nOld = pBuffer->nBufAlloc;
251 TH1_SIZECHECK(nNew);
252 pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
253 memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
254 #endif
255 pBuffer->nBufAlloc = nNew;
256 th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
257 pBuffer->nBuf += nAdd;
258 TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
259 }
260 static void thBufferWriteFast(
261 Th_Interp *interp,
262 Buffer *pBuffer,
263 const char *zAdd,
264 int nAddX
265 ){
266 int nAdd = TH1_LEN(nAddX);
267 if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
268 thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
269 }else{
270 if( pBuffer->zBuf ){
271 memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
272 }
273 pBuffer->nBuf += nAdd;
274 TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
275 }
276 }
277 #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
278
279 /*
@@ -704,24 +725,25 @@
725 int nWord
726 ){
727 int rc = TH_OK;
728 Buffer output;
729 int i;
730 int nn = TH1_LEN(nWord);
731
732 thBufferInit(&output);
733
734 if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
735 thBufferWrite(interp, &output, &zWord[1], nn-2);
736 }else{
737
738 /* If the word is surrounded by double-quotes strip these away. */
739 if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
740 zWord++;
741 nn -= 2;
742 }
743
744 for(i=0; rc==TH_OK && i<nn; i++){
745 int nGet;
746
747 int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
748 int (*xSubst)(Th_Interp *, const char*, int) = 0;
749
@@ -743,11 +765,11 @@
765 thBufferAddChar(interp, &output, zWord[i]);
766 continue; /* Go to the next iteration of the for(...) loop */
767 }
768 }
769
770 rc = xGet(interp, &zWord[i], nn-i, &nGet);
771 if( rc==TH_OK ){
772 rc = xSubst(interp, &zWord[i], nGet);
773 }
774 if( rc==TH_OK ){
775 const char *zRes;
@@ -758,11 +780,11 @@
780 }
781 }
782 }
783
784 if( rc==TH_OK ){
785 Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
786 }
787 thBufferFree(interp, &output);
788 return rc;
789 }
790
@@ -826,11 +848,11 @@
848 Buffer strbuf;
849 Buffer lenbuf;
850 int nCount = 0;
851
852 const char *zInput = zList;
853 int nInput = TH1_LEN(nList);
854
855 thBufferInit(&strbuf);
856 thBufferInit(&lenbuf);
857
858 while( nInput>0 ){
@@ -837,19 +859,19 @@
859 const char *zWord;
860 int nWord;
861
862 thNextSpace(interp, zInput, nInput, &nWord);
863 zInput += nWord;
864 nInput = TH1_LEN(nList)-(zInput-zList);
865
866 if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
867 || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
868 ){
869 goto finish;
870 }
871 zInput = &zInput[TH1_LEN(nWord)];
872 nInput = TH1_LEN(nList)-(zInput-zList);
873 if( nWord>0 ){
874 zWord = Th_GetResult(interp, &nWord);
875 thBufferWrite(interp, &strbuf, zWord, nWord);
876 thBufferAddChar(interp, &strbuf, 0);
877 thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
@@ -872,11 +894,11 @@
894 zElem = (char *)&anElem[nCount];
895 th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
896 th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
897 for(i=0; i<nCount;i++){
898 azElem[i] = zElem;
899 zElem += (TH1_LEN(anElem[i]) + 1);
900 }
901 *pazElem = azElem;
902 *panElem = anElem;
903 }
904 if( pnCount ){
@@ -894,12 +916,17 @@
916 ** in the current stack frame.
917 */
918 static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
919 int rc = TH_OK;
920 const char *zInput = zProgram;
921 int nInput = TH1_LEN(nProgram);
922
923 if( TH1_TAINTED(nProgram)
924 && Th_ReportTaint(interp, "script", zProgram, nProgram)
925 ){
926 return TH_ERROR;
927 }
928 while( rc==TH_OK && nInput ){
929 Th_HashEntry *pEntry;
930 int nSpace;
931 const char *zFirst;
932
@@ -949,13 +976,13 @@
976 if( rc!=TH_OK ) continue;
977
978 if( argc>0 ){
979
980 /* Look up the command name in the command hash-table. */
981 pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
982 if( !pEntry ){
983 Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
984 rc = TH_ERROR;
985 }
986
987 /* Call the command procedure. */
988 if( rc==TH_OK ){
@@ -1053,10 +1080,12 @@
1080 }else{
1081 int nInput = nProgram;
1082
1083 if( nInput<0 ){
1084 nInput = th_strlen(zProgram);
1085 }else{
1086 nInput = TH1_LEN(nInput);
1087 }
1088 rc = thEvalLocal(interp, zProgram, nInput);
1089 }
1090
1091 interp->pFrame = pSavedFrame;
@@ -1095,10 +1124,12 @@
1124 int isGlobal = 0;
1125 int i;
1126
1127 if( nVarname<0 ){
1128 nVarname = th_strlen(zVarname);
1129 }else{
1130 nVarname = TH1_LEN(nVarname);
1131 }
1132 nOuter = nVarname;
1133
1134 /* If the variable name starts with "::", then do the lookup is in the
1135 ** uppermost (global) frame.
@@ -1271,31 +1302,10 @@
1302 }
1303
1304 return Th_SetResult(interp, pValue->zData, pValue->nData);
1305 }
1306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1307 /*
1308 ** Return true if variable (zVar, nVar) exists.
1309 */
1310 int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
1311 Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
@@ -1324,28 +1334,32 @@
1334 int nVar,
1335 const char *zValue,
1336 int nValue
1337 ){
1338 Th_Variable *pValue;
1339 int nn;
1340
1341 nVar = TH1_LEN(nVar);
1342 pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
1343 if( !pValue ){
1344 return TH_ERROR;
1345 }
1346
1347 if( nValue<0 ){
1348 nn = th_strlen(zValue);
1349 }else{
1350 nn = TH1_LEN(nValue);
1351 }
1352 if( pValue->zData ){
1353 Th_Free(interp, pValue->zData);
1354 pValue->zData = 0;
1355 }
1356
1357 assert(zValue || nn==0);
1358 pValue->zData = Th_Malloc(interp, nn+1);
1359 pValue->zData[nn] = '\0';
1360 th_memcpy(pValue->zData, zValue, nn);
1361 pValue->nData = nValue;
1362
1363 return TH_OK;
1364 }
1365
@@ -1458,10 +1472,12 @@
1472 */
1473 char *th_strdup(Th_Interp *interp, const char *z, int n){
1474 char *zRes;
1475 if( n<0 ){
1476 n = th_strlen(z);
1477 }else{
1478 n = TH1_LEN(n);
1479 }
1480 zRes = Th_Malloc(interp, n+1);
1481 th_memcpy(zRes, z, n);
1482 zRes[n] = '\0';
1483 return zRes;
@@ -1519,13 +1535,14 @@
1535 n = th_strlen(z);
1536 }
1537
1538 if( z && n>0 ){
1539 char *zResult;
1540 int nn = TH1_LEN(n);
1541 zResult = Th_Malloc(pInterp, nn+1);
1542 th_memcpy(zResult, z, nn);
1543 zResult[nn] = '\0';
1544 pInterp->zResult = zResult;
1545 pInterp->nResult = n;
1546 }
1547
1548 return TH_OK;
@@ -1777,15 +1794,19 @@
1794 int hasSpecialChar = 0; /* Whitespace or {}[]'" */
1795 int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
1796 int nBrace = 0;
1797
1798 output.zBuf = *pzList;
1799 output.nBuf = TH1_LEN(*pnList);
1800 output.nBufAlloc = output.nBuf;
1801 output.bTaint = 0;
1802 TH1_XFER_TAINT(output.bTaint, *pnList);
1803
1804 if( nElem<0 ){
1805 nElem = th_strlen(zElem);
1806 }else{
1807 nElem = TH1_LEN(nElem);
1808 }
1809 if( output.nBuf>0 ){
1810 thBufferAddChar(interp, &output, ' ');
1811 }
1812
@@ -1834,24 +1855,28 @@
1855 int *pnStr, /* IN/OUT: Current length of *pzStr */
1856 const char *zElem, /* Data to append */
1857 int nElem /* Length of nElem */
1858 ){
1859 char *zNew;
1860 long long int nNew;
1861 int nn;
1862
1863 if( nElem<0 ){
1864 nn = th_strlen(zElem);
1865 }else{
1866 nn = TH1_LEN(nElem);
1867 }
1868
1869 nNew = TH1_LEN(*pnStr) + nn;
1870 TH1_SIZECHECK(nNew);
1871 zNew = Th_Malloc(interp, nNew);
1872 th_memcpy(zNew, *pzStr, *pnStr);
1873 th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
1874
1875 Th_Free(interp, *pzStr);
1876 *pzStr = zNew;
1877 *pnStr = (int)nNew;
1878
1879 return TH_OK;
1880 }
1881
1882 /*
@@ -2106,16 +2131,18 @@
2131 /* Evaluate left and right arguments, if they exist. */
2132 if( pExpr->pLeft ){
2133 rc = exprEval(interp, pExpr->pLeft);
2134 if( rc==TH_OK ){
2135 zLeft = Th_TakeResult(interp, &nLeft);
2136 nLeft = TH1_LEN(nLeft);
2137 }
2138 }
2139 if( rc==TH_OK && pExpr->pRight ){
2140 rc = exprEval(interp, pExpr->pRight);
2141 if( rc==TH_OK ){
2142 zRight = Th_TakeResult(interp, &nRight);
2143 nRight = TH1_LEN(nRight);
2144 }
2145 }
2146
2147 /* Convert arguments to their required forms. */
2148 if( rc==TH_OK ){
@@ -2456,10 +2483,12 @@
2483 int nToken = 0;
2484 Expr **apToken = 0;
2485
2486 if( nExpr<0 ){
2487 nExpr = th_strlen(zExpr);
2488 }else{
2489 nExpr = TH1_LEN(nExpr);
2490 }
2491
2492 /* Parse the expression to a list of tokens. */
2493 rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
2494
@@ -2567,10 +2596,12 @@
2596 Th_HashEntry *pRet;
2597 Th_HashEntry **ppRet;
2598
2599 if( nKey<0 ){
2600 nKey = th_strlen(zKey);
2601 }else{
2602 nKey = TH1_LEN(nKey);
2603 }
2604
2605 for(i=0; i<nKey; i++){
2606 iKey = (iKey<<3) ^ iKey ^ zKey[i];
2607 }
@@ -2800,10 +2831,12 @@
2831 int base = 10;
2832 int (*isdigit)(char) = th_isdigit;
2833
2834 if( n<0 ){
2835 n = th_strlen(z);
2836 }else{
2837 n = TH1_LEN(n);
2838 }
2839
2840 if( n>1 && (z[0]=='-' || z[0]=='+') ){
2841 i = 1;
2842 }
@@ -2859,11 +2892,11 @@
2892 const char *z,
2893 int n,
2894 double *pfOut
2895 ){
2896 if( !sqlite3IsNumber((const char *)z, 0) ){
2897 Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
2898 return TH_ERROR;
2899 }
2900
2901 sqlite3AtoF((const char *)z, pfOut);
2902 return TH_OK;
2903
+53 -14
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
1
-
21
/* This header file defines the external interface to the custom Scripting
32
** Language (TH) interpreter. TH is very similar to Tcl but is not an
43
** exact clone.
4
+**
5
+** TH1 was original developed to run SQLite tests on SymbianOS. This version
6
+** of TH1 was repurposed as a scripted language for Fossil, and was heavily
7
+** modified for that purpose, beginning in early 2008.
8
+**
9
+** More recently, TH1 has been enhanced to distinguish between regular text
10
+** and "tainted" text. "Tainted" text is text that might have originated
11
+** from an outside source and hence might not be trustworthy. To prevent
12
+** cross-site scripting (XSS) and SQL-injections and similar attacks,
13
+** tainted text should not be used for the following purposes:
14
+**
15
+** * executed as TH1 script or expression.
16
+** * output as HTML or Javascript
17
+** * used as part of an SQL query
18
+**
19
+** Tainted text can be converted into a safe form using commands like
20
+** "htmlize". And some commands ("query" and "expr") know how to use
21
+** potentially tainted variable values directly, and thus can bypass
22
+** the restrictions above.
23
+**
24
+** Whether a string is clean or tainted is determined by its length integer.
25
+** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
26
+** (about 268MB - more than sufficient for the purposes of Fossil). The top
27
+** bit of the length integer is the sign bit, of course. The next three bits
28
+** are reserved. One of those, the 0x10000000 bit, marks tainted strings.
529
*/
30
+#define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */
31
+#define TH1_TAINT_BIT 0x10000000 /* The taint bit */
32
+#define TH1_SIGN 0x80000000
33
+
34
+/* Convert an integer into a string length. Negative values remain negative */
35
+#define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X))
36
+
37
+/* Return true if the string is tainted */
38
+#define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0)
39
+
40
+/* Remove taint from a string */
41
+#define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT)
42
+
43
+/* Add taint to a string */
44
+#define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT)
45
+
46
+/* If B is tainted, make A tainted too */
47
+#define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B))
48
+
49
+/* Check to see if a string is too big for TH1 */
50
+#define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();}
51
+void Th_OversizeString(void);
652
753
/*
854
** Before creating an interpreter, the application must allocate and
955
** populate an instance of the following structure. It must remain valid
1056
** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
2470
** Create and delete interpreters.
2571
*/
2672
Th_Interp * Th_CreateInterp(Th_Vtab *);
2773
void Th_DeleteInterp(Th_Interp *);
2874
75
+/*
76
+** Report taint in the string zStr,nStr. That string represents "zTitle"
77
+** If non-zero is returned error out of the caller.
78
+*/
79
+int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);
80
+
2981
/*
3082
** Evaluate an TH program in the stack frame identified by parameter
3183
** iFrame, according to the following rules:
3284
**
3385
** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
56108
int Th_GetVar(Th_Interp *, const char *, int);
57109
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
58110
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
59111
int Th_UnsetVar(Th_Interp *, const char *, int);
60112
61
-/*
62
-** If interp has a variable with the given name, its value is returned
63
-** and its length is returned via *nOut if nOut is not NULL. If
64
-** interp has no such var then NULL is returned without setting any
65
-** error state and *nOut, if not NULL, is set to 0. The returned value
66
-** is owned by the interpreter and may be invalidated the next time
67
-** the interpreter is modified.
68
-**
69
-** zVarName must be NUL-terminated.
70
-*/
71
-const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
72
- int *nOut);
73
-
74113
typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
75114
76115
/*
77116
** Register new commands.
78117
*/
79118
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
1
2 /* This header file defines the external interface to the custom Scripting
3 ** Language (TH) interpreter. TH is very similar to Tcl but is not an
4 ** exact clone.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
7 /*
8 ** Before creating an interpreter, the application must allocate and
9 ** populate an instance of the following structure. It must remain valid
10 ** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
24 ** Create and delete interpreters.
25 */
26 Th_Interp * Th_CreateInterp(Th_Vtab *);
27 void Th_DeleteInterp(Th_Interp *);
28
 
 
 
 
 
 
29 /*
30 ** Evaluate an TH program in the stack frame identified by parameter
31 ** iFrame, according to the following rules:
32 **
33 ** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
56 int Th_GetVar(Th_Interp *, const char *, int);
57 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
58 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
59 int Th_UnsetVar(Th_Interp *, const char *, int);
60
61 /*
62 ** If interp has a variable with the given name, its value is returned
63 ** and its length is returned via *nOut if nOut is not NULL. If
64 ** interp has no such var then NULL is returned without setting any
65 ** error state and *nOut, if not NULL, is set to 0. The returned value
66 ** is owned by the interpreter and may be invalidated the next time
67 ** the interpreter is modified.
68 **
69 ** zVarName must be NUL-terminated.
70 */
71 const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
72 int *nOut);
73
74 typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
75
76 /*
77 ** Register new commands.
78 */
79
--- src/th.h
+++ src/th.h
@@ -1,10 +1,56 @@
 
1 /* This header file defines the external interface to the custom Scripting
2 ** Language (TH) interpreter. TH is very similar to Tcl but is not an
3 ** exact clone.
4 **
5 ** TH1 was original developed to run SQLite tests on SymbianOS. This version
6 ** of TH1 was repurposed as a scripted language for Fossil, and was heavily
7 ** modified for that purpose, beginning in early 2008.
8 **
9 ** More recently, TH1 has been enhanced to distinguish between regular text
10 ** and "tainted" text. "Tainted" text is text that might have originated
11 ** from an outside source and hence might not be trustworthy. To prevent
12 ** cross-site scripting (XSS) and SQL-injections and similar attacks,
13 ** tainted text should not be used for the following purposes:
14 **
15 ** * executed as TH1 script or expression.
16 ** * output as HTML or Javascript
17 ** * used as part of an SQL query
18 **
19 ** Tainted text can be converted into a safe form using commands like
20 ** "htmlize". And some commands ("query" and "expr") know how to use
21 ** potentially tainted variable values directly, and thus can bypass
22 ** the restrictions above.
23 **
24 ** Whether a string is clean or tainted is determined by its length integer.
25 ** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
26 ** (about 268MB - more than sufficient for the purposes of Fossil). The top
27 ** bit of the length integer is the sign bit, of course. The next three bits
28 ** are reserved. One of those, the 0x10000000 bit, marks tainted strings.
29 */
30 #define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */
31 #define TH1_TAINT_BIT 0x10000000 /* The taint bit */
32 #define TH1_SIGN 0x80000000
33
34 /* Convert an integer into a string length. Negative values remain negative */
35 #define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X))
36
37 /* Return true if the string is tainted */
38 #define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0)
39
40 /* Remove taint from a string */
41 #define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT)
42
43 /* Add taint to a string */
44 #define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT)
45
46 /* If B is tainted, make A tainted too */
47 #define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B))
48
49 /* Check to see if a string is too big for TH1 */
50 #define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();}
51 void Th_OversizeString(void);
52
53 /*
54 ** Before creating an interpreter, the application must allocate and
55 ** populate an instance of the following structure. It must remain valid
56 ** for the lifetime of the interpreter.
@@ -24,10 +70,16 @@
70 ** Create and delete interpreters.
71 */
72 Th_Interp * Th_CreateInterp(Th_Vtab *);
73 void Th_DeleteInterp(Th_Interp *);
74
75 /*
76 ** Report taint in the string zStr,nStr. That string represents "zTitle"
77 ** If non-zero is returned error out of the caller.
78 */
79 int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);
80
81 /*
82 ** Evaluate an TH program in the stack frame identified by parameter
83 ** iFrame, according to the following rules:
84 **
85 ** * If iFrame is 0, this means the current frame.
@@ -56,23 +108,10 @@
108 int Th_GetVar(Th_Interp *, const char *, int);
109 int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
110 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
111 int Th_UnsetVar(Th_Interp *, const char *, int);
112
 
 
 
 
 
 
 
 
 
 
 
 
 
113 typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);
114
115 /*
116 ** Register new commands.
117 */
118
+92 -53
--- src/th_lang.c
+++ src/th_lang.c
@@ -39,11 +39,11 @@
3939
4040
rc = Th_Eval(interp, 0, argv[1], -1);
4141
if( argc==3 ){
4242
int nResult;
4343
const char *zResult = Th_GetResult(interp, &nResult);
44
- Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
44
+ Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
4545
}
4646
4747
Th_SetResultInt(interp, rc);
4848
return TH_OK;
4949
}
@@ -180,20 +180,24 @@
180180
int nVar;
181181
char **azValue = 0;
182182
int *anValue;
183183
int nValue;
184184
int ii, jj;
185
+ int bTaint = 0;
185186
186187
if( argc!=4 ){
187188
return Th_WrongNumArgs(interp, "foreach varlist list script");
188189
}
189190
rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
190191
if( rc ) return rc;
192
+ TH1_XFER_TAINT(bTaint, argl[2]);
191193
rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
192194
for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
193195
for(jj=0; jj<nVar; jj++){
194
- Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
196
+ int x = anValue[ii+jj];
197
+ TH1_XFER_TAINT(x, bTaint);
198
+ Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
195199
}
196200
rc = eval_loopbody(interp, argv[3], argl[3]);
197201
}
198202
if( rc==TH_BREAK ) rc = TH_OK;
199203
Th_Free(interp, azVar);
@@ -215,15 +219,18 @@
215219
int *argl
216220
){
217221
char *zList = 0;
218222
int nList = 0;
219223
int i;
224
+ int bTaint = 0;
220225
221226
for(i=1; i<argc; i++){
227
+ TH1_XFER_TAINT(bTaint,argl[i]);
222228
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
223229
}
224230
231
+ TH1_XFER_TAINT(nList, bTaint);
225232
Th_SetResult(interp, zList, nList);
226233
Th_Free(interp, zList);
227234
228235
return TH_OK;
229236
}
@@ -244,23 +251,27 @@
244251
int *argl
245252
){
246253
char *zList = 0;
247254
int nList = 0;
248255
int i, rc;
256
+ int bTaint = 0;
249257
250258
if( argc<2 ){
251259
return Th_WrongNumArgs(interp, "lappend var ...");
252260
}
253261
rc = Th_GetVar(interp, argv[1], argl[1]);
254262
if( rc==TH_OK ){
255263
zList = Th_TakeResult(interp, &nList);
256264
}
257265
266
+ TH1_XFER_TAINT(bTaint, nList);
258267
for(i=2; i<argc; i++){
268
+ TH1_XFER_TAINT(bTaint, argl[i]);
259269
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
260270
}
261271
272
+ TH1_XFER_TAINT(nList, bTaint);
262273
Th_SetVar(interp, argv[1], argl[1], zList, nList);
263274
Th_SetResult(interp, zList, nList);
264275
Th_Free(interp, zList);
265276
266277
return TH_OK;
@@ -283,23 +294,27 @@
283294
int rc;
284295
285296
char **azElem;
286297
int *anElem;
287298
int nCount;
299
+ int bTaint = 0;
288300
289301
if( argc!=3 ){
290302
return Th_WrongNumArgs(interp, "lindex list index");
291303
}
292304
293305
if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
294306
return TH_ERROR;
295307
}
296308
309
+ TH1_XFER_TAINT(bTaint, argl[1]);
297310
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
298311
if( rc==TH_OK ){
299312
if( iElem<nCount && iElem>=0 ){
300
- Th_SetResult(interp, azElem[iElem], anElem[iElem]);
313
+ int sz = anElem[iElem];
314
+ TH1_XFER_TAINT(sz, bTaint);
315
+ Th_SetResult(interp, azElem[iElem], sz);
301316
}else{
302317
Th_SetResult(interp, 0, 0);
303318
}
304319
Th_Free(interp, azElem);
305320
}
@@ -356,13 +371,14 @@
356371
return Th_WrongNumArgs(interp, "lsearch list string");
357372
}
358373
359374
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
360375
if( rc==TH_OK ){
376
+ int nn = TH1_LEN(argl[2]);
361377
Th_SetResultInt(interp, -1);
362378
for(i=0; i<nCount; i++){
363
- if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
379
+ if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
364380
Th_SetResultInt(interp, i);
365381
break;
366382
}
367383
}
368384
Th_Free(interp, azElem);
@@ -561,28 +577,31 @@
561577
int nUsage = 0; /* Number of bytes at zUsage */
562578
563579
if( argc!=4 ){
564580
return Th_WrongNumArgs(interp, "proc name arglist code");
565581
}
566
- if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
582
+ if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
583
+ &azParam, &anParam, &nParam) ){
567584
return TH_ERROR;
568585
}
569586
570587
/* Allocate the new ProcDefn structure. */
571588
nByte = sizeof(ProcDefn) + /* ProcDefn structure */
572589
(sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
573590
(sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
574
- argl[3] + /* zProgram */
575
- argl[2]; /* Space for copies of parameter names and default values */
591
+ TH1_LEN(argl[3]) + /* zProgram */
592
+ TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
576593
p = (ProcDefn *)Th_Malloc(interp, nByte);
577594
578595
/* If the last parameter in the parameter list is "args", then set the
579596
** ProcDefn.hasArgs flag. The "args" parameter does not require an
580597
** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
581598
*/
582599
if( nParam>0 ){
583
- if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
600
+ if( TH1_LEN(anParam[nParam-1])==4
601
+ && 0==memcmp(azParam[nParam-1], "args", 4)
602
+ ){
584603
p->hasArgs = 1;
585604
nParam--;
586605
}
587606
}
588607
@@ -590,12 +609,12 @@
590609
p->azParam = (char **)&p[1];
591610
p->anParam = (int *)&p->azParam[nParam];
592611
p->azDefault = (char **)&p->anParam[nParam];
593612
p->anDefault = (int *)&p->azDefault[nParam];
594613
p->zProgram = (char *)&p->anDefault[nParam];
595
- memcpy(p->zProgram, argv[3], argl[3]);
596
- p->nProgram = argl[3];
614
+ memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
615
+ p->nProgram = TH1_LEN(argl[3]);
597616
zSpace = &p->zProgram[p->nProgram];
598617
599618
for(i=0; i<nParam; i++){
600619
char **az;
601620
int *an;
@@ -672,11 +691,12 @@
672691
int *argl
673692
){
674693
if( argc!=3 ){
675694
return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
676695
}
677
- return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
696
+ return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
697
+ argv[2], TH1_LEN(argl[2]));
678698
}
679699
680700
/*
681701
** TH Syntax:
682702
**
@@ -746,13 +766,13 @@
746766
if( argc!=4 ){
747767
return Th_WrongNumArgs(interp, "string compare str1 str2");
748768
}
749769
750770
zLeft = argv[2];
751
- nLeft = argl[2];
771
+ nLeft = TH1_LEN(argl[2]);
752772
zRight = argv[3];
753
- nRight = argl[3];
773
+ nRight = TH1_LEN(argl[3]);
754774
755775
for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
756776
iRes = zLeft[i]-zRight[i];
757777
}
758778
if( iRes==0 ){
@@ -779,12 +799,12 @@
779799
780800
if( argc!=4 ){
781801
return Th_WrongNumArgs(interp, "string first needle haystack");
782802
}
783803
784
- nNeedle = argl[2];
785
- nHaystack = argl[3];
804
+ nNeedle = TH1_LEN(argl[2]);
805
+ nHaystack = TH1_LEN(argl[3]);
786806
787807
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
788808
const char *zNeedle = argv[2];
789809
const char *zHaystack = argv[3];
790810
int i;
@@ -812,20 +832,22 @@
812832
813833
if( argc!=4 ){
814834
return Th_WrongNumArgs(interp, "string index string index");
815835
}
816836
817
- if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
818
- iIndex = argl[2]-1;
837
+ if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
838
+ iIndex = TH1_LEN(argl[2])-1;
819839
}else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
820840
Th_ErrorMessage(
821841
interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
822842
return TH_ERROR;
823843
}
824844
825
- if( iIndex>=0 && iIndex<argl[2] ){
826
- return Th_SetResult(interp, &argv[2][iIndex], 1);
845
+ if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
846
+ int sz = 1;
847
+ TH1_XFER_TAINT(sz, argl[2]);
848
+ return Th_SetResult(interp, &argv[2][iIndex], sz);
827849
}else{
828850
return Th_SetResult(interp, 0, 0);
829851
}
830852
}
831853
@@ -838,41 +860,44 @@
838860
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
839861
){
840862
if( argc!=4 ){
841863
return Th_WrongNumArgs(interp, "string is class string");
842864
}
843
- if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
865
+ if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
844866
int i;
845867
int iRes = 1;
846868
847
- for(i=0; i<argl[3]; i++){
869
+ for(i=0; i<TH1_LEN(argl[3]); i++){
848870
if( !th_isalnum(argv[3][i]) ){
849871
iRes = 0;
850872
}
851873
}
852874
853875
return Th_SetResultInt(interp, iRes);
854
- }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
876
+ }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
855877
double fVal;
856878
if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
857879
return Th_SetResultInt(interp, 1);
858880
}
859881
return Th_SetResultInt(interp, 0);
860
- }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
882
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
861883
int iVal;
862884
if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
863885
return Th_SetResultInt(interp, 1);
864886
}
865887
return Th_SetResultInt(interp, 0);
866
- }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
888
+ }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
867889
if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
868890
return Th_SetResultInt(interp, 1);
869891
}
870892
return Th_SetResultInt(interp, 0);
893
+ }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
894
+ return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
871895
}else{
872896
Th_ErrorMessage(interp,
873
- "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
897
+ "Expected alnum, double, integer, list, or tainted, got:",
898
+ argv[2], TH1_LEN(argl[2]));
874899
return TH_ERROR;
875900
}
876901
}
877902
878903
/*
@@ -889,12 +914,12 @@
889914
890915
if( argc!=4 ){
891916
return Th_WrongNumArgs(interp, "string last needle haystack");
892917
}
893918
894
- nNeedle = argl[2];
895
- nHaystack = argl[3];
919
+ nNeedle = TH1_LEN(argl[2]);
920
+ nHaystack = TH1_LEN(argl[3]);
896921
897922
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
898923
const char *zNeedle = argv[2];
899924
const char *zHaystack = argv[3];
900925
int i;
@@ -919,11 +944,11 @@
919944
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
920945
){
921946
if( argc!=3 ){
922947
return Th_WrongNumArgs(interp, "string length string");
923948
}
924
- return Th_SetResultInt(interp, argl[2]);
949
+ return Th_SetResultInt(interp, TH1_LEN(argl[2]));
925950
}
926951
927952
/*
928953
** TH Syntax:
929954
**
@@ -938,12 +963,12 @@
938963
char *zPat, *zStr;
939964
int rc;
940965
if( argc!=4 ){
941966
return Th_WrongNumArgs(interp, "string match pattern string");
942967
}
943
- zPat = fossil_strndup(argv[2],argl[2]);
944
- zStr = fossil_strndup(argv[3],argl[3]);
968
+ zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
969
+ zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
945970
rc = sqlite3_strglob(zPat,zStr);
946971
fossil_free(zPat);
947972
fossil_free(zStr);
948973
return Th_SetResultInt(interp, !rc);
949974
}
@@ -956,31 +981,34 @@
956981
static int string_range_command(
957982
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
958983
){
959984
int iStart;
960985
int iEnd;
986
+ int sz;
961987
962988
if( argc!=5 ){
963989
return Th_WrongNumArgs(interp, "string range string first last");
964990
}
965991
966
- if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
967
- iEnd = argl[2];
992
+ if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
993
+ iEnd = TH1_LEN(argl[2]);
968994
}else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
969995
Th_ErrorMessage(
970
- interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
996
+ interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
971997
return TH_ERROR;
972998
}
973999
if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
9741000
return TH_ERROR;
9751001
}
9761002
9771003
if( iStart<0 ) iStart = 0;
978
- if( iEnd>=argl[2] ) iEnd = argl[2]-1;
1004
+ if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
9791005
if( iStart>iEnd ) iEnd = iStart-1;
1006
+ sz = iEnd - iStart + 1;
1007
+ TH1_XFER_TAINT(sz, argl[2]);
9801008
981
- return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
1009
+ return Th_SetResult(interp, &argv[2][iStart], sz);
9821010
}
9831011
9841012
/*
9851013
** TH Syntax:
9861014
**
@@ -989,27 +1017,33 @@
9891017
static int string_repeat_command(
9901018
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
9911019
){
9921020
int n;
9931021
int i;
994
- int nByte;
1022
+ int sz;
1023
+ long long int nByte;
9951024
char *zByte;
9961025
9971026
if( argc!=4 ){
9981027
return Th_WrongNumArgs(interp, "string repeat string n");
9991028
}
10001029
if( Th_ToInt(interp, argv[3], argl[3], &n) ){
10011030
return TH_ERROR;
10021031
}
10031032
1004
- nByte = argl[2] * n;
1033
+ nByte = n;
1034
+ sz = TH1_LEN(argl[2]);
1035
+ nByte *= sz;
1036
+ TH1_SIZECHECK(nByte+1);
10051037
zByte = Th_Malloc(interp, nByte+1);
1006
- for(i=0; i<nByte; i+=argl[2]){
1007
- memcpy(&zByte[i], argv[2], argl[2]);
1038
+ for(i=0; i<nByte; i+=sz){
1039
+ memcpy(&zByte[i], argv[2], sz);
10081040
}
10091041
1010
- Th_SetResult(interp, zByte, nByte);
1042
+ n = nByte;
1043
+ TH1_XFER_TAINT(n, argl[2]);
1044
+ Th_SetResult(interp, zByte, n);
10111045
Th_Free(interp, zByte);
10121046
return TH_OK;
10131047
}
10141048
10151049
/*
@@ -1027,17 +1061,18 @@
10271061
10281062
if( argc!=3 ){
10291063
return Th_WrongNumArgs(interp, "string trim string");
10301064
}
10311065
z = argv[2];
1032
- n = argl[2];
1033
- if( argl[1]<5 || argv[1][4]=='l' ){
1066
+ n = TH1_LEN(argl[2]);
1067
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
10341068
while( n && th_isspace(z[0]) ){ z++; n--; }
10351069
}
1036
- if( argl[1]<5 || argv[1][4]=='r' ){
1070
+ if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
10371071
while( n && th_isspace(z[n-1]) ){ n--; }
10381072
}
1073
+ TH1_XFER_TAINT(n, argl[2]);
10391074
Th_SetResult(interp, z, n);
10401075
return TH_OK;
10411076
}
10421077
10431078
/*
@@ -1051,11 +1086,11 @@
10511086
int rc;
10521087
10531088
if( argc!=3 ){
10541089
return Th_WrongNumArgs(interp, "info exists var");
10551090
}
1056
- rc = Th_ExistsVar(interp, argv[2], argl[2]);
1091
+ rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
10571092
Th_SetResultInt(interp, rc);
10581093
return TH_OK;
10591094
}
10601095
10611096
/*
@@ -1117,11 +1152,11 @@
11171152
int rc;
11181153
11191154
if( argc!=3 ){
11201155
return Th_WrongNumArgs(interp, "array exists var");
11211156
}
1122
- rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
1157
+ rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
11231158
Th_SetResultInt(interp, rc);
11241159
return TH_OK;
11251160
}
11261161
11271162
/*
@@ -1137,11 +1172,11 @@
11371172
int nElem = 0;
11381173
11391174
if( argc!=3 ){
11401175
return Th_WrongNumArgs(interp, "array names varname");
11411176
}
1142
- rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
1177
+ rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
11431178
if( rc!=TH_OK ){
11441179
return rc;
11451180
}
11461181
Th_SetResult(interp, zElem, nElem);
11471182
if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1196,11 @@
11611196
int *argl
11621197
){
11631198
if( argc!=2 ){
11641199
return Th_WrongNumArgs(interp, "unset var");
11651200
}
1166
- return Th_UnsetVar(interp, argv[1], argl[1]);
1201
+ return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
11671202
}
11681203
11691204
int Th_CallSubCommand(
11701205
Th_Interp *interp,
11711206
void *ctx,
@@ -1176,19 +1211,22 @@
11761211
){
11771212
if( argc>1 ){
11781213
int i;
11791214
for(i=0; aSub[i].zName; i++){
11801215
const char *zName = aSub[i].zName;
1181
- if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
1216
+ if( th_strlen(zName)==TH1_LEN(argl[1])
1217
+ && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
11821218
return aSub[i].xProc(interp, ctx, argc, argv, argl);
11831219
}
11841220
}
11851221
}
11861222
if(argc<2){
1187
- Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
1223
+ Th_ErrorMessage(interp, "Expected sub-command for",
1224
+ argv[0], TH1_LEN(argl[0]));
11881225
}else{
1189
- Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
1226
+ Th_ErrorMessage(interp, "Expected sub-command, got:",
1227
+ argv[1], TH1_LEN(argl[1]));
11901228
}
11911229
return TH_ERROR;
11921230
}
11931231
11941232
/*
@@ -1319,11 +1357,11 @@
13191357
int iFrame = -1;
13201358
13211359
if( argc!=2 && argc!=3 ){
13221360
return Th_WrongNumArgs(interp, "uplevel ?level? script...");
13231361
}
1324
- if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
1362
+ if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13251363
return TH_ERROR;
13261364
}
13271365
return Th_Eval(interp, iFrame, argv[argc-1], -1);
13281366
}
13291367
@@ -1342,19 +1380,20 @@
13421380
int iVar = 1;
13431381
int iFrame = -1;
13441382
int rc = TH_OK;
13451383
int i;
13461384
1347
- if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
1385
+ if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
13481386
iVar++;
13491387
}
13501388
if( argc==iVar || (argc-iVar)%2 ){
13511389
return Th_WrongNumArgs(interp,
13521390
"upvar frame othervar myvar ?othervar myvar...?");
13531391
}
13541392
for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1355
- rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]);
1393
+ rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1394
+ iFrame, argv[i], TH1_LEN(argl[i]));
13561395
}
13571396
return rc;
13581397
}
13591398
13601399
/*
13611400
--- src/th_lang.c
+++ src/th_lang.c
@@ -39,11 +39,11 @@
39
40 rc = Th_Eval(interp, 0, argv[1], -1);
41 if( argc==3 ){
42 int nResult;
43 const char *zResult = Th_GetResult(interp, &nResult);
44 Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
45 }
46
47 Th_SetResultInt(interp, rc);
48 return TH_OK;
49 }
@@ -180,20 +180,24 @@
180 int nVar;
181 char **azValue = 0;
182 int *anValue;
183 int nValue;
184 int ii, jj;
 
185
186 if( argc!=4 ){
187 return Th_WrongNumArgs(interp, "foreach varlist list script");
188 }
189 rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
190 if( rc ) return rc;
 
191 rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
192 for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
193 for(jj=0; jj<nVar; jj++){
194 Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
 
 
195 }
196 rc = eval_loopbody(interp, argv[3], argl[3]);
197 }
198 if( rc==TH_BREAK ) rc = TH_OK;
199 Th_Free(interp, azVar);
@@ -215,15 +219,18 @@
215 int *argl
216 ){
217 char *zList = 0;
218 int nList = 0;
219 int i;
 
220
221 for(i=1; i<argc; i++){
 
222 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
223 }
224
 
225 Th_SetResult(interp, zList, nList);
226 Th_Free(interp, zList);
227
228 return TH_OK;
229 }
@@ -244,23 +251,27 @@
244 int *argl
245 ){
246 char *zList = 0;
247 int nList = 0;
248 int i, rc;
 
249
250 if( argc<2 ){
251 return Th_WrongNumArgs(interp, "lappend var ...");
252 }
253 rc = Th_GetVar(interp, argv[1], argl[1]);
254 if( rc==TH_OK ){
255 zList = Th_TakeResult(interp, &nList);
256 }
257
 
258 for(i=2; i<argc; i++){
 
259 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
260 }
261
 
262 Th_SetVar(interp, argv[1], argl[1], zList, nList);
263 Th_SetResult(interp, zList, nList);
264 Th_Free(interp, zList);
265
266 return TH_OK;
@@ -283,23 +294,27 @@
283 int rc;
284
285 char **azElem;
286 int *anElem;
287 int nCount;
 
288
289 if( argc!=3 ){
290 return Th_WrongNumArgs(interp, "lindex list index");
291 }
292
293 if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
294 return TH_ERROR;
295 }
296
 
297 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
298 if( rc==TH_OK ){
299 if( iElem<nCount && iElem>=0 ){
300 Th_SetResult(interp, azElem[iElem], anElem[iElem]);
 
 
301 }else{
302 Th_SetResult(interp, 0, 0);
303 }
304 Th_Free(interp, azElem);
305 }
@@ -356,13 +371,14 @@
356 return Th_WrongNumArgs(interp, "lsearch list string");
357 }
358
359 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
360 if( rc==TH_OK ){
 
361 Th_SetResultInt(interp, -1);
362 for(i=0; i<nCount; i++){
363 if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
364 Th_SetResultInt(interp, i);
365 break;
366 }
367 }
368 Th_Free(interp, azElem);
@@ -561,28 +577,31 @@
561 int nUsage = 0; /* Number of bytes at zUsage */
562
563 if( argc!=4 ){
564 return Th_WrongNumArgs(interp, "proc name arglist code");
565 }
566 if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
 
567 return TH_ERROR;
568 }
569
570 /* Allocate the new ProcDefn structure. */
571 nByte = sizeof(ProcDefn) + /* ProcDefn structure */
572 (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
573 (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
574 argl[3] + /* zProgram */
575 argl[2]; /* Space for copies of parameter names and default values */
576 p = (ProcDefn *)Th_Malloc(interp, nByte);
577
578 /* If the last parameter in the parameter list is "args", then set the
579 ** ProcDefn.hasArgs flag. The "args" parameter does not require an
580 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
581 */
582 if( nParam>0 ){
583 if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
 
 
584 p->hasArgs = 1;
585 nParam--;
586 }
587 }
588
@@ -590,12 +609,12 @@
590 p->azParam = (char **)&p[1];
591 p->anParam = (int *)&p->azParam[nParam];
592 p->azDefault = (char **)&p->anParam[nParam];
593 p->anDefault = (int *)&p->azDefault[nParam];
594 p->zProgram = (char *)&p->anDefault[nParam];
595 memcpy(p->zProgram, argv[3], argl[3]);
596 p->nProgram = argl[3];
597 zSpace = &p->zProgram[p->nProgram];
598
599 for(i=0; i<nParam; i++){
600 char **az;
601 int *an;
@@ -672,11 +691,12 @@
672 int *argl
673 ){
674 if( argc!=3 ){
675 return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
676 }
677 return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
 
678 }
679
680 /*
681 ** TH Syntax:
682 **
@@ -746,13 +766,13 @@
746 if( argc!=4 ){
747 return Th_WrongNumArgs(interp, "string compare str1 str2");
748 }
749
750 zLeft = argv[2];
751 nLeft = argl[2];
752 zRight = argv[3];
753 nRight = argl[3];
754
755 for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
756 iRes = zLeft[i]-zRight[i];
757 }
758 if( iRes==0 ){
@@ -779,12 +799,12 @@
779
780 if( argc!=4 ){
781 return Th_WrongNumArgs(interp, "string first needle haystack");
782 }
783
784 nNeedle = argl[2];
785 nHaystack = argl[3];
786
787 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
788 const char *zNeedle = argv[2];
789 const char *zHaystack = argv[3];
790 int i;
@@ -812,20 +832,22 @@
812
813 if( argc!=4 ){
814 return Th_WrongNumArgs(interp, "string index string index");
815 }
816
817 if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
818 iIndex = argl[2]-1;
819 }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
820 Th_ErrorMessage(
821 interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
822 return TH_ERROR;
823 }
824
825 if( iIndex>=0 && iIndex<argl[2] ){
826 return Th_SetResult(interp, &argv[2][iIndex], 1);
 
 
827 }else{
828 return Th_SetResult(interp, 0, 0);
829 }
830 }
831
@@ -838,41 +860,44 @@
838 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
839 ){
840 if( argc!=4 ){
841 return Th_WrongNumArgs(interp, "string is class string");
842 }