Fossil SCM

Merge from trunk.

brickviking 2025-05-04 02:35 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 }
843 if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
844 int i;
845 int iRes = 1;
846
847 for(i=0; i<argl[3]; i++){
848 if( !th_isalnum(argv[3][i]) ){
849 iRes = 0;
850 }
851 }
852
853 return Th_SetResultInt(interp, iRes);
854 }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
855 double fVal;
856 if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
857 return Th_SetResultInt(interp, 1);
858 }
859 return Th_SetResultInt(interp, 0);
860 }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
861 int iVal;
862 if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
863 return Th_SetResultInt(interp, 1);
864 }
865 return Th_SetResultInt(interp, 0);
866 }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
867 if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
868 return Th_SetResultInt(interp, 1);
869 }
870 return Th_SetResultInt(interp, 0);
 
 
871 }else{
872 Th_ErrorMessage(interp,
873 "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
 
874 return TH_ERROR;
875 }
876 }
877
878 /*
@@ -889,12 +914,12 @@
889
890 if( argc!=4 ){
891 return Th_WrongNumArgs(interp, "string last needle haystack");
892 }
893
894 nNeedle = argl[2];
895 nHaystack = argl[3];
896
897 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
898 const char *zNeedle = argv[2];
899 const char *zHaystack = argv[3];
900 int i;
@@ -919,11 +944,11 @@
919 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
920 ){
921 if( argc!=3 ){
922 return Th_WrongNumArgs(interp, "string length string");
923 }
924 return Th_SetResultInt(interp, argl[2]);
925 }
926
927 /*
928 ** TH Syntax:
929 **
@@ -938,12 +963,12 @@
938 char *zPat, *zStr;
939 int rc;
940 if( argc!=4 ){
941 return Th_WrongNumArgs(interp, "string match pattern string");
942 }
943 zPat = fossil_strndup(argv[2],argl[2]);
944 zStr = fossil_strndup(argv[3],argl[3]);
945 rc = sqlite3_strglob(zPat,zStr);
946 fossil_free(zPat);
947 fossil_free(zStr);
948 return Th_SetResultInt(interp, !rc);
949 }
@@ -956,31 +981,34 @@
956 static int string_range_command(
957 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
958 ){
959 int iStart;
960 int iEnd;
 
961
962 if( argc!=5 ){
963 return Th_WrongNumArgs(interp, "string range string first last");
964 }
965
966 if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
967 iEnd = argl[2];
968 }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
969 Th_ErrorMessage(
970 interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
971 return TH_ERROR;
972 }
973 if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
974 return TH_ERROR;
975 }
976
977 if( iStart<0 ) iStart = 0;
978 if( iEnd>=argl[2] ) iEnd = argl[2]-1;
979 if( iStart>iEnd ) iEnd = iStart-1;
 
 
980
981 return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
982 }
983
984 /*
985 ** TH Syntax:
986 **
@@ -989,27 +1017,33 @@
989 static int string_repeat_command(
990 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
991 ){
992 int n;
993 int i;
994 int nByte;
 
995 char *zByte;
996
997 if( argc!=4 ){
998 return Th_WrongNumArgs(interp, "string repeat string n");
999 }
1000 if( Th_ToInt(interp, argv[3], argl[3], &n) ){
1001 return TH_ERROR;
1002 }
1003
1004 nByte = argl[2] * n;
 
 
 
1005 zByte = Th_Malloc(interp, nByte+1);
1006 for(i=0; i<nByte; i+=argl[2]){
1007 memcpy(&zByte[i], argv[2], argl[2]);
1008 }
1009
1010 Th_SetResult(interp, zByte, nByte);
 
 
1011 Th_Free(interp, zByte);
1012 return TH_OK;
1013 }
1014
1015 /*
@@ -1027,17 +1061,18 @@
1027
1028 if( argc!=3 ){
1029 return Th_WrongNumArgs(interp, "string trim string");
1030 }
1031 z = argv[2];
1032 n = argl[2];
1033 if( argl[1]<5 || argv[1][4]=='l' ){
1034 while( n && th_isspace(z[0]) ){ z++; n--; }
1035 }
1036 if( argl[1]<5 || argv[1][4]=='r' ){
1037 while( n && th_isspace(z[n-1]) ){ n--; }
1038 }
 
1039 Th_SetResult(interp, z, n);
1040 return TH_OK;
1041 }
1042
1043 /*
@@ -1051,11 +1086,11 @@
1051 int rc;
1052
1053 if( argc!=3 ){
1054 return Th_WrongNumArgs(interp, "info exists var");
1055 }
1056 rc = Th_ExistsVar(interp, argv[2], argl[2]);
1057 Th_SetResultInt(interp, rc);
1058 return TH_OK;
1059 }
1060
1061 /*
@@ -1117,11 +1152,11 @@
1117 int rc;
1118
1119 if( argc!=3 ){
1120 return Th_WrongNumArgs(interp, "array exists var");
1121 }
1122 rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
1123 Th_SetResultInt(interp, rc);
1124 return TH_OK;
1125 }
1126
1127 /*
@@ -1137,11 +1172,11 @@
1137 int nElem = 0;
1138
1139 if( argc!=3 ){
1140 return Th_WrongNumArgs(interp, "array names varname");
1141 }
1142 rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
1143 if( rc!=TH_OK ){
1144 return rc;
1145 }
1146 Th_SetResult(interp, zElem, nElem);
1147 if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1196,11 @@
1161 int *argl
1162 ){
1163 if( argc!=2 ){
1164 return Th_WrongNumArgs(interp, "unset var");
1165 }
1166 return Th_UnsetVar(interp, argv[1], argl[1]);
1167 }
1168
1169 int Th_CallSubCommand(
1170 Th_Interp *interp,
1171 void *ctx,
@@ -1176,19 +1211,22 @@
1176 ){
1177 if( argc>1 ){
1178 int i;
1179 for(i=0; aSub[i].zName; i++){
1180 const char *zName = aSub[i].zName;
1181 if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
 
1182 return aSub[i].xProc(interp, ctx, argc, argv, argl);
1183 }
1184 }
1185 }
1186 if(argc<2){
1187 Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
 
1188 }else{
1189 Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
 
1190 }
1191 return TH_ERROR;
1192 }
1193
1194 /*
@@ -1319,11 +1357,11 @@
1319 int iFrame = -1;
1320
1321 if( argc!=2 && argc!=3 ){
1322 return Th_WrongNumArgs(interp, "uplevel ?level? script...");
1323 }
1324 if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
1325 return TH_ERROR;
1326 }
1327 return Th_Eval(interp, iFrame, argv[argc-1], -1);
1328 }
1329
@@ -1342,19 +1380,20 @@
1342 int iVar = 1;
1343 int iFrame = -1;
1344 int rc = TH_OK;
1345 int i;
1346
1347 if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
1348 iVar++;
1349 }
1350 if( argc==iVar || (argc-iVar)%2 ){
1351 return Th_WrongNumArgs(interp,
1352 "upvar frame othervar myvar ?othervar myvar...?");
1353 }
1354 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]);
 
1356 }
1357 return rc;
1358 }
1359
1360 /*
1361
--- 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], TH1_LEN(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 int bTaint = 0;
186
187 if( argc!=4 ){
188 return Th_WrongNumArgs(interp, "foreach varlist list script");
189 }
190 rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
191 if( rc ) return rc;
192 TH1_XFER_TAINT(bTaint, argl[2]);
193 rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
194 for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
195 for(jj=0; jj<nVar; 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);
199 }
200 rc = eval_loopbody(interp, argv[3], argl[3]);
201 }
202 if( rc==TH_BREAK ) rc = TH_OK;
203 Th_Free(interp, azVar);
@@ -215,15 +219,18 @@
219 int *argl
220 ){
221 char *zList = 0;
222 int nList = 0;
223 int i;
224 int bTaint = 0;
225
226 for(i=1; i<argc; i++){
227 TH1_XFER_TAINT(bTaint,argl[i]);
228 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
229 }
230
231 TH1_XFER_TAINT(nList, bTaint);
232 Th_SetResult(interp, zList, nList);
233 Th_Free(interp, zList);
234
235 return TH_OK;
236 }
@@ -244,23 +251,27 @@
251 int *argl
252 ){
253 char *zList = 0;
254 int nList = 0;
255 int i, rc;
256 int bTaint = 0;
257
258 if( argc<2 ){
259 return Th_WrongNumArgs(interp, "lappend var ...");
260 }
261 rc = Th_GetVar(interp, argv[1], argl[1]);
262 if( rc==TH_OK ){
263 zList = Th_TakeResult(interp, &nList);
264 }
265
266 TH1_XFER_TAINT(bTaint, nList);
267 for(i=2; i<argc; i++){
268 TH1_XFER_TAINT(bTaint, argl[i]);
269 Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
270 }
271
272 TH1_XFER_TAINT(nList, bTaint);
273 Th_SetVar(interp, argv[1], argl[1], zList, nList);
274 Th_SetResult(interp, zList, nList);
275 Th_Free(interp, zList);
276
277 return TH_OK;
@@ -283,23 +294,27 @@
294 int rc;
295
296 char **azElem;
297 int *anElem;
298 int nCount;
299 int bTaint = 0;
300
301 if( argc!=3 ){
302 return Th_WrongNumArgs(interp, "lindex list index");
303 }
304
305 if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
306 return TH_ERROR;
307 }
308
309 TH1_XFER_TAINT(bTaint, argl[1]);
310 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
311 if( rc==TH_OK ){
312 if( iElem<nCount && iElem>=0 ){
313 int sz = anElem[iElem];
314 TH1_XFER_TAINT(sz, bTaint);
315 Th_SetResult(interp, azElem[iElem], sz);
316 }else{
317 Th_SetResult(interp, 0, 0);
318 }
319 Th_Free(interp, azElem);
320 }
@@ -356,13 +371,14 @@
371 return Th_WrongNumArgs(interp, "lsearch list string");
372 }
373
374 rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
375 if( rc==TH_OK ){
376 int nn = TH1_LEN(argl[2]);
377 Th_SetResultInt(interp, -1);
378 for(i=0; i<nCount; i++){
379 if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
380 Th_SetResultInt(interp, i);
381 break;
382 }
383 }
384 Th_Free(interp, azElem);
@@ -561,28 +577,31 @@
577 int nUsage = 0; /* Number of bytes at zUsage */
578
579 if( argc!=4 ){
580 return Th_WrongNumArgs(interp, "proc name arglist code");
581 }
582 if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
583 &azParam, &anParam, &nParam) ){
584 return TH_ERROR;
585 }
586
587 /* Allocate the new ProcDefn structure. */
588 nByte = sizeof(ProcDefn) + /* ProcDefn structure */
589 (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
590 (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
591 TH1_LEN(argl[3]) + /* zProgram */
592 TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
593 p = (ProcDefn *)Th_Malloc(interp, nByte);
594
595 /* If the last parameter in the parameter list is "args", then set the
596 ** ProcDefn.hasArgs flag. The "args" parameter does not require an
597 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
598 */
599 if( nParam>0 ){
600 if( TH1_LEN(anParam[nParam-1])==4
601 && 0==memcmp(azParam[nParam-1], "args", 4)
602 ){
603 p->hasArgs = 1;
604 nParam--;
605 }
606 }
607
@@ -590,12 +609,12 @@
609 p->azParam = (char **)&p[1];
610 p->anParam = (int *)&p->azParam[nParam];
611 p->azDefault = (char **)&p->anParam[nParam];
612 p->anDefault = (int *)&p->azDefault[nParam];
613 p->zProgram = (char *)&p->anDefault[nParam];
614 memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
615 p->nProgram = TH1_LEN(argl[3]);
616 zSpace = &p->zProgram[p->nProgram];
617
618 for(i=0; i<nParam; i++){
619 char **az;
620 int *an;
@@ -672,11 +691,12 @@
691 int *argl
692 ){
693 if( argc!=3 ){
694 return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
695 }
696 return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
697 argv[2], TH1_LEN(argl[2]));
698 }
699
700 /*
701 ** TH Syntax:
702 **
@@ -746,13 +766,13 @@
766 if( argc!=4 ){
767 return Th_WrongNumArgs(interp, "string compare str1 str2");
768 }
769
770 zLeft = argv[2];
771 nLeft = TH1_LEN(argl[2]);
772 zRight = argv[3];
773 nRight = TH1_LEN(argl[3]);
774
775 for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
776 iRes = zLeft[i]-zRight[i];
777 }
778 if( iRes==0 ){
@@ -779,12 +799,12 @@
799
800 if( argc!=4 ){
801 return Th_WrongNumArgs(interp, "string first needle haystack");
802 }
803
804 nNeedle = TH1_LEN(argl[2]);
805 nHaystack = TH1_LEN(argl[3]);
806
807 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
808 const char *zNeedle = argv[2];
809 const char *zHaystack = argv[3];
810 int i;
@@ -812,20 +832,22 @@
832
833 if( argc!=4 ){
834 return Th_WrongNumArgs(interp, "string index string index");
835 }
836
837 if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
838 iIndex = TH1_LEN(argl[2])-1;
839 }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
840 Th_ErrorMessage(
841 interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
842 return TH_ERROR;
843 }
844
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);
849 }else{
850 return Th_SetResult(interp, 0, 0);
851 }
852 }
853
@@ -838,41 +860,44 @@
860 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
861 ){
862 if( argc!=4 ){
863 return Th_WrongNumArgs(interp, "string is class string");
864 }
865 if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
866 int i;
867 int iRes = 1;
868
869 for(i=0; i<TH1_LEN(argl[3]); i++){
870 if( !th_isalnum(argv[3][i]) ){
871 iRes = 0;
872 }
873 }
874
875 return Th_SetResultInt(interp, iRes);
876 }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
877 double fVal;
878 if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
879 return Th_SetResultInt(interp, 1);
880 }
881 return Th_SetResultInt(interp, 0);
882 }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
883 int iVal;
884 if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
885 return Th_SetResultInt(interp, 1);
886 }
887 return Th_SetResultInt(interp, 0);
888 }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
889 if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
890 return Th_SetResultInt(interp, 1);
891 }
892 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]));
895 }else{
896 Th_ErrorMessage(interp,
897 "Expected alnum, double, integer, list, or tainted, got:",
898 argv[2], TH1_LEN(argl[2]));
899 return TH_ERROR;
900 }
901 }
902
903 /*
@@ -889,12 +914,12 @@
914
915 if( argc!=4 ){
916 return Th_WrongNumArgs(interp, "string last needle haystack");
917 }
918
919 nNeedle = TH1_LEN(argl[2]);
920 nHaystack = TH1_LEN(argl[3]);
921
922 if( nNeedle && nHaystack && nNeedle<=nHaystack ){
923 const char *zNeedle = argv[2];
924 const char *zHaystack = argv[3];
925 int i;
@@ -919,11 +944,11 @@
944 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
945 ){
946 if( argc!=3 ){
947 return Th_WrongNumArgs(interp, "string length string");
948 }
949 return Th_SetResultInt(interp, TH1_LEN(argl[2]));
950 }
951
952 /*
953 ** TH Syntax:
954 **
@@ -938,12 +963,12 @@
963 char *zPat, *zStr;
964 int rc;
965 if( argc!=4 ){
966 return Th_WrongNumArgs(interp, "string match pattern string");
967 }
968 zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
969 zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
970 rc = sqlite3_strglob(zPat,zStr);
971 fossil_free(zPat);
972 fossil_free(zStr);
973 return Th_SetResultInt(interp, !rc);
974 }
@@ -956,31 +981,34 @@
981 static int string_range_command(
982 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
983 ){
984 int iStart;
985 int iEnd;
986 int sz;
987
988 if( argc!=5 ){
989 return Th_WrongNumArgs(interp, "string range string first last");
990 }
991
992 if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
993 iEnd = TH1_LEN(argl[2]);
994 }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
995 Th_ErrorMessage(
996 interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
997 return TH_ERROR;
998 }
999 if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
1000 return TH_ERROR;
1001 }
1002
1003 if( iStart<0 ) iStart = 0;
1004 if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
1005 if( iStart>iEnd ) iEnd = iStart-1;
1006 sz = iEnd - iStart + 1;
1007 TH1_XFER_TAINT(sz, argl[2]);
1008
1009 return Th_SetResult(interp, &argv[2][iStart], sz);
1010 }
1011
1012 /*
1013 ** TH Syntax:
1014 **
@@ -989,27 +1017,33 @@
1017 static int string_repeat_command(
1018 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
1019 ){
1020 int n;
1021 int i;
1022 int sz;
1023 long long int nByte;
1024 char *zByte;
1025
1026 if( argc!=4 ){
1027 return Th_WrongNumArgs(interp, "string repeat string n");
1028 }
1029 if( Th_ToInt(interp, argv[3], argl[3], &n) ){
1030 return TH_ERROR;
1031 }
1032
1033 nByte = n;
1034 sz = TH1_LEN(argl[2]);
1035 nByte *= sz;
1036 TH1_SIZECHECK(nByte+1);
1037 zByte = Th_Malloc(interp, nByte+1);
1038 for(i=0; i<nByte; i+=sz){
1039 memcpy(&zByte[i], argv[2], sz);
1040 }
1041
1042 n = nByte;
1043 TH1_XFER_TAINT(n, argl[2]);
1044 Th_SetResult(interp, zByte, n);
1045 Th_Free(interp, zByte);
1046 return TH_OK;
1047 }
1048
1049 /*
@@ -1027,17 +1061,18 @@
1061
1062 if( argc!=3 ){
1063 return Th_WrongNumArgs(interp, "string trim string");
1064 }
1065 z = argv[2];
1066 n = TH1_LEN(argl[2]);
1067 if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
1068 while( n && th_isspace(z[0]) ){ z++; n--; }
1069 }
1070 if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
1071 while( n && th_isspace(z[n-1]) ){ n--; }
1072 }
1073 TH1_XFER_TAINT(n, argl[2]);
1074 Th_SetResult(interp, z, n);
1075 return TH_OK;
1076 }
1077
1078 /*
@@ -1051,11 +1086,11 @@
1086 int rc;
1087
1088 if( argc!=3 ){
1089 return Th_WrongNumArgs(interp, "info exists var");
1090 }
1091 rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
1092 Th_SetResultInt(interp, rc);
1093 return TH_OK;
1094 }
1095
1096 /*
@@ -1117,11 +1152,11 @@
1152 int rc;
1153
1154 if( argc!=3 ){
1155 return Th_WrongNumArgs(interp, "array exists var");
1156 }
1157 rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
1158 Th_SetResultInt(interp, rc);
1159 return TH_OK;
1160 }
1161
1162 /*
@@ -1137,11 +1172,11 @@
1172 int nElem = 0;
1173
1174 if( argc!=3 ){
1175 return Th_WrongNumArgs(interp, "array names varname");
1176 }
1177 rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
1178 if( rc!=TH_OK ){
1179 return rc;
1180 }
1181 Th_SetResult(interp, zElem, nElem);
1182 if( zElem ) Th_Free(interp, zElem);
@@ -1161,11 +1196,11 @@
1196 int *argl
1197 ){
1198 if( argc!=2 ){
1199 return Th_WrongNumArgs(interp, "unset var");
1200 }
1201 return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
1202 }
1203
1204 int Th_CallSubCommand(
1205 Th_Interp *interp,
1206 void *ctx,
@@ -1176,19 +1211,22 @@
1211 ){
1212 if( argc>1 ){
1213 int i;
1214 for(i=0; aSub[i].zName; i++){
1215 const char *zName = aSub[i].zName;
1216 if( th_strlen(zName)==TH1_LEN(argl[1])
1217 && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
1218 return aSub[i].xProc(interp, ctx, argc, argv, argl);
1219 }
1220 }
1221 }
1222 if(argc<2){
1223 Th_ErrorMessage(interp, "Expected sub-command for",
1224 argv[0], TH1_LEN(argl[0]));
1225 }else{
1226 Th_ErrorMessage(interp, "Expected sub-command, got:",
1227 argv[1], TH1_LEN(argl[1]));
1228 }
1229 return TH_ERROR;
1230 }
1231
1232 /*
@@ -1319,11 +1357,11 @@
1357 int iFrame = -1;
1358
1359 if( argc!=2 && argc!=3 ){
1360 return Th_WrongNumArgs(interp, "uplevel ?level? script...");
1361 }
1362 if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1363 return TH_ERROR;
1364 }
1365 return Th_Eval(interp, iFrame, argv[argc-1], -1);
1366 }
1367
@@ -1342,19 +1380,20 @@
1380 int iVar = 1;
1381 int iFrame = -1;
1382 int rc = TH_OK;
1383 int i;
1384
1385 if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
1386 iVar++;
1387 }
1388 if( argc==iVar || (argc-iVar)%2 ){
1389 return Th_WrongNumArgs(interp,
1390 "upvar frame othervar myvar ?othervar myvar...?");
1391 }
1392 for(i=iVar; rc==TH_OK && i<argc; i=i+2){
1393 rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
1394 iFrame, argv[i], TH1_LEN(argl[i]));
1395 }
1396 return rc;
1397 }
1398
1399 /*
1400
+209 -89
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
3131
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
3232
#define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
3333
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
3434
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
3535
#define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36
-#define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37
- /* output. */
38
-#define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
36
+#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
3937
4038
/*
4139
** Useful and/or "well-known" combinations of flag values.
4240
*/
4341
#define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
262260
){
263261
char *zOut;
264262
if( argc!=2 ){
265263
return Th_WrongNumArgs(interp, "httpize STRING");
266264
}
267
- zOut = httpize((char*)argv[1], argl[1]);
265
+ zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
268266
Th_SetResult(interp, zOut, -1);
269267
free(zOut);
270268
return TH_OK;
271269
}
272270
@@ -291,51 +289,12 @@
291289
if( argc<2 || argc>3 ){
292290
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293291
}
294292
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295293
if( g.thTrace ){
296
- Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
297
- }
298
- return rc;
299
-}
300
-
301
-/*
302
-** TH1 command: enable_htmlify ?BOOLEAN?
303
-**
304
-** Enable or disable the HTML escaping done by all output which
305
-** originates from TH1 (via sendText()).
306
-**
307
-** If passed no arguments it instead returns 0 or 1 to indicate the
308
-** current state.
309
-*/
310
-static int enableHtmlifyCmd(
311
- Th_Interp *interp,
312
- void *p,
313
- int argc,
314
- const char **argv,
315
- int *argl
316
-){
317
- int rc = 0, buul;
318
- if( argc>3 ){
319
- return Th_WrongNumArgs(interp,
320
- "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321
- }
322
- buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
323
- Th_SetResultInt(g.interp, buul);
324
- if(argc>1){
325
- if( g.thTrace ){
326
- Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
327
- argl[1],argv[1],buul);
328
- }
329
- rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
330
- if(!rc){
331
- if(buul){
332
- g.th1Flags &= ~TH_INIT_NO_ENCODE;
333
- }else{
334
- g.th1Flags |= TH_INIT_NO_ENCODE;
335
- }
336
- }
294
+ Th_Trace("enable_output {%.*s} -> %d<br>\n",
295
+ TH1_LEN(argl[1]),argv[1],enableOutput);
337296
}
338297
return rc;
339298
}
340299
341300
/*
@@ -375,25 +334,25 @@
375334
376335
/*
377336
** Send text to the appropriate output: If pOut is not NULL, it is
378337
** appended there, else to the console or to the CGI reply buffer.
379338
** Escape all characters with special meaning to HTML if the encode
380
-** parameter is true, with the exception that that flag is ignored if
381
-** g.th1Flags has the TH_INIT_NO_ENCODE flag.
339
+** parameter is true.
382340
**
383341
** If pOut is NULL and the global pThOut is not then that blob
384342
** is used for output.
385343
*/
386
-static void sendText(Blob * pOut, const char *z, int n, int encode){
344
+static void sendText(Blob *pOut, const char *z, int n, int encode){
387345
if(0==pOut && pThOut!=0){
388346
pOut = pThOut;
389347
}
390
- if(TH_INIT_NO_ENCODE & g.th1Flags){
391
- encode = 0;
392
- }
393348
if( enableOutput && n ){
394
- if( n<0 ) n = strlen(z);
349
+ if( n<0 ){
350
+ n = strlen(z);
351
+ }else{
352
+ n = TH1_LEN(n);
353
+ }
395354
if( encode ){
396355
z = htmlize(z, n);
397356
n = strlen(z);
398357
}
399358
if(pOut!=0){
@@ -525,14 +484,22 @@
525484
void *pConvert,
526485
int argc,
527486
const char **argv,
528487
int *argl
529488
){
489
+ int encode = *(unsigned int*)pConvert;
490
+ int n;
530491
if( argc!=2 ){
531492
return Th_WrongNumArgs(interp, "puts STRING");
532493
}
533
- sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
494
+ n = argl[1];
495
+ if( encode==0 && n>0 && TH1_TAINTED(n) ){
496
+ if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497
+ return TH_ERROR;
498
+ }
499
+ }
500
+ sendText(0,(char*)argv[1], TH1_LEN(n), encode);
534501
return TH_OK;
535502
}
536503
537504
/*
538505
** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
557524
}
558525
if( argc==3 ){
559526
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560527
return TH_ERROR;
561528
}
529
+ }
530
+ if( TH1_TAINTED(argl[1])
531
+ && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532
+ ){
533
+ return TH_ERROR;
562534
}
563535
if( withMethod ){
564536
cgi_redirect_with_method(argv[1]);
565537
}else{
566538
cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
660632
int nValue = 0;
661633
if( argc!=2 ){
662634
return Th_WrongNumArgs(interp, "markdown STRING");
663635
}
664636
blob_zero(&src);
665
- blob_init(&src, (char*)argv[1], argl[1]);
637
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
666638
blob_zero(&title); blob_zero(&body);
667639
markdown_to_html(&src, &title, &body);
668640
Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669641
Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670642
Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
690662
if( argc!=2 ){
691663
return Th_WrongNumArgs(interp, "wiki STRING");
692664
}
693665
if( enableOutput ){
694666
Blob src;
695
- blob_init(&src, (char*)argv[1], argl[1]);
667
+ blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
696668
wiki_convert(&src, 0, flags);
697669
blob_reset(&src);
698670
}
699671
return TH_OK;
700672
}
@@ -735,11 +707,11 @@
735707
){
736708
char *zOut;
737709
if( argc!=2 ){
738710
return Th_WrongNumArgs(interp, "htmlize STRING");
739711
}
740
- zOut = htmlize((char*)argv[1], argl[1]);
712
+ zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
741713
Th_SetResult(interp, zOut, -1);
742714
free(zOut);
743715
return TH_OK;
744716
}
745717
@@ -757,11 +729,11 @@
757729
){
758730
char *zOut;
759731
if( argc!=2 ){
760732
return Th_WrongNumArgs(interp, "encode64 STRING");
761733
}
762
- zOut = encode64((char*)argv[1], argl[1]);
734
+ zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
763735
Th_SetResult(interp, zOut, -1);
764736
free(zOut);
765737
return TH_OK;
766738
}
767739
@@ -778,11 +750,11 @@
778750
int argc,
779751
const char **argv,
780752
int *argl
781753
){
782754
char *zOut;
783
- if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
755
+ if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
784756
zOut = db_text("??", "SELECT datetime('now',toLocal())");
785757
}else{
786758
zOut = db_text("??", "SELECT datetime('now')");
787759
}
788760
Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
810782
if( argc<2 ){
811783
return Th_WrongNumArgs(interp, "hascap STRING ...");
812784
}
813785
for(i=1; rc==1 && i<argc; i++){
814786
if( g.thTrace ){
815
- Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
787
+ Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
816788
}
817
- rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
789
+ rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
818790
}
819791
if( g.thTrace ){
820792
Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821793
Th_Free(interp, zCapList);
822794
}
@@ -858,11 +830,11 @@
858830
int i;
859831
860832
if( argc!=2 ){
861833
return Th_WrongNumArgs(interp, "capexpr EXPR");
862834
}
863
- rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
835
+ rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
864836
if( rc ) return rc;
865837
rc = 0;
866838
for(i=0; i<nCap; i++){
867839
if( azCap[i][0]=='!' ){
868840
rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
921893
if( argc<2 ){
922894
return Th_WrongNumArgs(interp, "hascap STRING ...");
923895
}
924896
for(i=1; i<argc && rc; i++){
925897
int match = 0;
926
- for(j=0; j<argl[i]; j++){
898
+ int nn = TH1_LEN(argl[i]);
899
+ for(j=0; j<nn; j++){
927900
switch( argv[i][j] ){
928901
case 'c': match |= searchCap & SRCH_CKIN; break;
929902
case 'd': match |= searchCap & SRCH_DOC; break;
930903
case 't': match |= searchCap & SRCH_TKT; break;
931904
case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
932905
}
933906
}
934907
if( !match ) rc = 0;
935908
}
936909
if( g.thTrace ){
937
- Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
910
+ Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
938911
}
939912
Th_SetResultInt(interp, rc);
940913
return TH_OK;
941914
}
942915
@@ -1051,11 +1024,11 @@
10511024
#endif
10521025
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
10531026
rc = 1;
10541027
}
10551028
if( g.thTrace ){
1056
- Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1029
+ Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
10571030
}
10581031
Th_SetResultInt(interp, rc);
10591032
return TH_OK;
10601033
}
10611034
@@ -1104,18 +1077,20 @@
11041077
const char **argv,
11051078
int *argl
11061079
){
11071080
int rc = 0;
11081081
int i;
1082
+ int nn;
11091083
if( argc!=2 ){
11101084
return Th_WrongNumArgs(interp, "anycap STRING");
11111085
}
1112
- for(i=0; rc==0 && i<argl[1]; i++){
1086
+ nn = TH1_LEN(argl[1]);
1087
+ for(i=0; rc==0 && i<nn; i++){
11131088
rc = login_has_capability((char*)&argv[1][i],1,0);
11141089
}
11151090
if( g.thTrace ){
1116
- Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1091
+ Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
11171092
}
11181093
Th_SetResultInt(interp, rc);
11191094
return TH_OK;
11201095
}
11211096
@@ -1140,22 +1115,23 @@
11401115
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
11411116
}
11421117
if( enableOutput ){
11431118
int height;
11441119
Blob name;
1145
- int nValue;
1120
+ int nValue = 0;
11461121
const char *zValue;
11471122
char *z, *zH;
11481123
int nElem;
11491124
int *aszElem;
11501125
char **azElem;
11511126
int i;
11521127
11531128
if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1154
- Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
1155
- blob_init(&name, (char*)argv[1], argl[1]);
1129
+ Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130
+ blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
11561131
zValue = Th_Fetch(blob_str(&name), &nValue);
1132
+ nValue = TH1_LEN(nValue);
11571133
zH = htmlize(blob_buffer(&name), blob_size(&name));
11581134
z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
11591135
free(zH);
11601136
sendText(0,z, -1, 0);
11611137
free(z);
@@ -1247,11 +1223,11 @@
12471223
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
12481224
}
12491225
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
12501226
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
12511227
z = argv[1];
1252
- size = argl[1];
1228
+ size = TH1_LEN(argl[1]);
12531229
for(n=1, i=0; i<size; i++){
12541230
if( z[i]=='\n' ){
12551231
n++;
12561232
if( n>=iMax ) break;
12571233
}
@@ -1407,11 +1383,12 @@
14071383
return TH_OK;
14081384
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
14091385
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
14101386
return TH_OK;
14111387
}else{
1412
- Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
1388
+ Th_ErrorMessage(interp, "unsupported global state:",
1389
+ argv[1], TH1_LEN(argl[1]));
14131390
return TH_ERROR;
14141391
}
14151392
}
14161393
14171394
/*
@@ -1426,17 +1403,21 @@
14261403
int argc,
14271404
const char **argv,
14281405
int *argl
14291406
){
14301407
const char *zDefault = 0;
1408
+ const char *zVal;
1409
+ int sz;
14311410
if( argc!=2 && argc!=3 ){
14321411
return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
14331412
}
14341413
if( argc==3 ){
14351414
zDefault = argv[2];
14361415
}
1437
- Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
1416
+ zVal = cgi_parameter(argv[1], zDefault);
1417
+ sz = th_strlen(zVal);
1418
+ Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
14381419
return TH_OK;
14391420
}
14401421
14411422
/*
14421423
** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
18481829
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
18491830
Th_SetResult(interp, zUTime, -1);
18501831
return TH_OK;
18511832
}
18521833
1834
+/*
1835
+** TH1 command: taint STRING
1836
+**
1837
+** Return a copy of STRING that is marked as tainted.
1838
+*/
1839
+static int taintCmd(
1840
+ Th_Interp *interp,
1841
+ void *p,
1842
+ int argc,
1843
+ const char **argv,
1844
+ int *argl
1845
+){
1846
+ if( argc!=2 ){
1847
+ return Th_WrongNumArgs(interp, "STRING");
1848
+ }
1849
+ Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850
+ return TH_OK;
1851
+}
1852
+
1853
+/*
1854
+** TH1 command: untaint STRING
1855
+**
1856
+** Return a copy of STRING that is marked as untainted.
1857
+*/
1858
+static int untaintCmd(
1859
+ Th_Interp *interp,
1860
+ void *p,
1861
+ int argc,
1862
+ const char **argv,
1863
+ int *argl
1864
+){
1865
+ if( argc!=2 ){
1866
+ return Th_WrongNumArgs(interp, "STRING");
1867
+ }
1868
+ Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869
+ return TH_OK;
1870
+}
18531871
18541872
/*
18551873
** TH1 command: randhex N
18561874
**
18571875
** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
19231941
int res = TH_OK;
19241942
int nVar;
19251943
char *zErr = 0;
19261944
int noComplain = 0;
19271945
1928
- if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
1946
+ if( argc>3 && TH1_LEN(argl[1])==11
1947
+ && strncmp(argv[1], "-nocomplain", 11)==0
1948
+ ){
19291949
argc--;
19301950
argv++;
19311951
argl++;
19321952
noComplain = 1;
19331953
}
@@ -1939,15 +1959,22 @@
19391959
Th_ErrorMessage(interp, "database is not open", 0, 0);
19401960
return TH_ERROR;
19411961
}
19421962
zSql = argv[1];
19431963
nSql = argl[1];
1964
+ if( TH1_TAINTED(nSql) ){
1965
+ if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966
+ return TH_ERROR;
1967
+ }
1968
+ nSql = TH1_LEN(nSql);
1969
+ }
1970
+
19441971
while( res==TH_OK && nSql>0 ){
19451972
zErr = 0;
19461973
report_restrict_sql(&zErr);
19471974
g.dbIgnoreErrors++;
1948
- rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1975
+ rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
19491976
g.dbIgnoreErrors--;
19501977
report_unrestrict_sql();
19511978
if( rc!=0 || zErr!=0 ){
19521979
if( noComplain ) return TH_OK;
19531980
Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
19641991
int szVar = zVar ? th_strlen(zVar) : 0;
19651992
if( szVar>1 && zVar[0]=='$'
19661993
&& Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
19671994
int nVal;
19681995
const char *zVal = Th_GetResult(interp, &nVal);
1969
- sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1996
+ sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
19701997
}
19711998
}
19721999
while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
19732000
int nCol = sqlite3_column_count(pStmt);
19742001
for(i=0; i<nCol; i++){
19752002
const char *zCol = sqlite3_column_name(pStmt, i);
19762003
int szCol = th_strlen(zCol);
19772004
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
19782005
int szVal = sqlite3_column_bytes(pStmt, i);
1979
- Th_SetVar(interp, zCol, szCol, zVal, szVal);
2006
+ Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
19802007
}
19812008
if( g.thTrace ){
1982
- Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
2009
+ Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
19832010
}
1984
- res = Th_Eval(interp, 0, argv[2], argl[2]);
2011
+ res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
19852012
if( g.thTrace ){
19862013
int nTrRes;
19872014
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
19882015
Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989
- Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
2016
+ Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
19902017
}
19912018
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
19922019
}
19932020
rc = sqlite3_finalize(pStmt);
19942021
if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
20382065
Th_SetResult(interp, 0, 0);
20392066
rc = TH_OK;
20402067
}
20412068
if( g.thTrace ){
20422069
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043
- argl[nArg], argv[nArg], rc);
2070
+ TH1_LEN(argl[nArg]), argv[nArg], rc);
20442071
}
20452072
return rc;
20462073
}
20472074
20482075
/*
@@ -2121,11 +2148,11 @@
21212148
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
21222149
}
21232150
zErr = re_compile(&pRe, argv[nArg], noCase);
21242151
if( !zErr ){
21252152
Th_SetResultInt(interp, re_match(pRe,
2126
- (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2153
+ (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
21272154
rc = TH_OK;
21282155
}else{
21292156
Th_SetResult(interp, zErr, -1);
21302157
rc = TH_ERROR;
21312158
}
@@ -2160,11 +2187,11 @@
21602187
UrlData urlData;
21612188
21622189
if( argc<2 || argc>5 ){
21632190
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
21642191
}
2165
- if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2192
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
21662193
fAsynchronous = 1; nArg++;
21672194
}
21682195
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
21692196
if( nArg+1!=argc && nArg+2!=argc ){
21702197
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
21892216
return TH_ERROR;
21902217
}
21912218
re_free(pRe);
21922219
blob_zero(&payload);
21932220
if( nArg+2==argc ){
2194
- blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2221
+ blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
21952222
zType = "POST";
21962223
}else{
21972224
zType = "GET";
21982225
}
21992226
if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
22682295
if( argc!=2 ){
22692296
return Th_WrongNumArgs(interp, "captureTh1 STRING");
22702297
}
22712298
pOrig = Th_SetOutputBlob(&out);
22722299
zStr = argv[1];
2273
- nStr = argl[1];
2300
+ nStr = TH1_LEN(argl[1]);
22742301
rc = Th_Eval(g.interp, 0, zStr, nStr);
22752302
Th_SetOutputBlob(pOrig);
22762303
if(0==rc){
22772304
Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
22782305
}
@@ -2356,11 +2383,10 @@
23562383
{"copybtn", copybtnCmd, 0},
23572384
{"date", dateCmd, 0},
23582385
{"decorate", wikiCmd, (void*)&aFlags[2]},
23592386
{"defHeader", defHeaderCmd, 0},
23602387
{"dir", dirCmd, 0},
2361
- {"enable_htmlify",enableHtmlifyCmd, 0},
23622388
{"enable_output", enableOutputCmd, 0},
23632389
{"encode64", encode64Cmd, 0},
23642390
{"getParameter", getParameterCmd, 0},
23652391
{"glob_match", globMatchCmd, 0},
23662392
{"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
23872413
{"setting", settingCmd, 0},
23882414
{"styleFooter", styleFooterCmd, 0},
23892415
{"styleHeader", styleHeaderCmd, 0},
23902416
{"styleScript", styleScriptCmd, 0},
23912417
{"submenu", submenuCmd, 0},
2418
+ {"taint", taintCmd, 0},
23922419
{"tclReady", tclReadyCmd, 0},
23932420
{"trace", traceCmd, 0},
23942421
{"stime", stimeCmd, 0},
2422
+ {"untaint", untaintCmd, 0},
23952423
{"unversioned", unversionedCmd, 0},
23962424
{"utime", utimeCmd, 0},
23972425
{"verifyCsrf", verifyCsrfCmd, 0},
23982426
{"verifyLogin", verifyLoginCmd, 0},
23992427
{"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
24942522
Th_Trace("set %h {%h}<br>\n", zName, zValue);
24952523
}
24962524
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
24972525
}
24982526
}
2527
+
2528
+/*
2529
+** Store a string value in a variable in the interpreter
2530
+** with the "taint" marking, so that TH1 knows that this
2531
+** variable contains content under the control of the remote
2532
+** user and presents a risk of XSS or SQL-injection attacks.
2533
+*/
2534
+void Th_StoreUnsafe(const char *zName, const char *zValue){
2535
+ Th_FossilInit(TH_INIT_DEFAULT);
2536
+ if( zValue ){
2537
+ if( g.thTrace ){
2538
+ Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539
+ }
2540
+ Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541
+ }
2542
+}
24992543
25002544
/*
25012545
** Appends an element to a TH1 list value. This function is called by the
25022546
** transfer subsystem; therefore, it must be very careful to avoid doing
25032547
** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
26802724
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
26812725
/*
26822726
** Make sure that the TH1 script error was not caused by a "missing"
26832727
** command hook handler as that is not actually an error condition.
26842728
*/
2729
+ nResult = TH1_LEN(nResult);
26852730
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
26862731
sendError(0,zResult, nResult, 0);
26872732
}else{
26882733
/*
26892734
** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
27672812
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
27682813
/*
27692814
** Make sure that the TH1 script error was not caused by a "missing"
27702815
** webpage hook handler as that is not actually an error condition.
27712816
*/
2817
+ nResult = TH1_LEN(nResult);
27722818
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
27732819
sendError(0,zResult, nResult, 1);
27742820
}else{
27752821
/*
27762822
** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
28942940
}
28952941
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
28962942
z += i+1+n;
28972943
i = 0;
28982944
zResult = (char*)Th_GetResult(g.interp, &n);
2899
- sendText(pOut,(char*)zResult, n, encode);
2945
+ if( !TH1_TAINTED(n)
2946
+ || encode
2947
+ || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948
+ ){
2949
+ sendText(pOut,(char*)zResult, n, encode);
2950
+ }
29002951
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
29012952
sendText(pOut,z, i, 0);
29022953
z += i+5;
29032954
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
29042955
if( g.thTrace ){
@@ -2907,11 +2958,11 @@
29072958
rc = Th_Eval(g.interp, 0, (const char*)z, i);
29082959
if( g.thTrace ){
29092960
int nTrRes;
29102961
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
29112962
Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912
- Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2963
+ Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
29132964
}
29142965
if( rc!=TH_OK ) break;
29152966
z += i;
29162967
if( z[0] ){ z += 6; }
29172968
i = 0;
@@ -2949,14 +3000,81 @@
29493000
** e.g. via the "render" script function binding, need to use the
29503001
** pThOut blob in order to avoid out-of-order output if
29513002
** Th_SetOutputBlob() has been called. If it has not been called,
29523003
** pThOut will be 0, which will redirect the output to CGI/stdout,
29533004
** as appropriate. We need to pass on g.th1Flags for the case of
2954
- ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
2955
- ** inadvertently toggled off by a recursive call.
3005
+ ** recursive calls.
29563006
*/;
29573007
}
3008
+
3009
+/*
3010
+** SETTING: vuln-report width=8 default=log
3011
+**
3012
+** This setting controls Fossil's behavior when it encounters a potential
3013
+** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014
+** scripts. Choices are:
3015
+**
3016
+** off Do nothing. Ignore the vulnerability.
3017
+**
3018
+** log Write a report of the problem into the error log.
3019
+**
3020
+** block Like "log" but also prevent the offending TH1 command
3021
+** from running.
3022
+**
3023
+** fatal Render an error message page instead of the requested
3024
+** page.
3025
+*/
3026
+
3027
+/*
3028
+** Report misuse of a tainted string in TH1.
3029
+**
3030
+** The behavior depends on the vuln-report setting. If "off", this routine
3031
+** is a no-op. Otherwise, right a message into the error log. If
3032
+** vuln-report is "log", that is all that happens. But for any other
3033
+** value of vuln-report, a fatal error is raised.
3034
+*/
3035
+int Th_ReportTaint(
3036
+ Th_Interp *interp, /* Report error here, if an error is reported */
3037
+ const char *zWhere, /* Where the tainted string appears */
3038
+ const char *zStr, /* The tainted string */
3039
+ int nStr /* Length of the tainted string */
3040
+){
3041
+ static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042
+ const char *zVulnType; /* Type of vulnerability */
3043
+
3044
+ if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045
+ if( is_false(zDisp) ) return 0;
3046
+ if( strstr(zWhere,"SQL")!=0 ){
3047
+ zVulnType = "SQL-injection";
3048
+ }else{
3049
+ zVulnType = "XSS";
3050
+ }
3051
+ nStr = TH1_LEN(nStr);
3052
+ fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053
+ zVulnType, zWhere, nStr, zStr);
3054
+ if( strcmp(zDisp,"log")==0 ){
3055
+ return 0;
3056
+ }
3057
+ if( strcmp(zDisp,"block")==0 ){
3058
+ char *z = mprintf("tainted %s: \"", zWhere);
3059
+ Th_ErrorMessage(interp, z, zStr, nStr);
3060
+ fossil_free(z);
3061
+ }else{
3062
+ char *z = mprintf("%#h", nStr, zStr);
3063
+ zDisp = "off";
3064
+ cgi_reset_content();
3065
+ style_submenu_enable(0);
3066
+ style_set_current_feature("error");
3067
+ style_header("Configuration Error");
3068
+ @ <p>Error in a TH1 configuration script:
3069
+ @ tainted %h(zWhere): "%z(z)"
3070
+ style_finish_page();
3071
+ cgi_reply();
3072
+ fossil_exit(1);
3073
+ }
3074
+ return 1;
3075
+}
29583076
29593077
/*
29603078
** COMMAND: test-th-render
29613079
**
29623080
** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
29923110
if( find_option("set-user-caps", 0, 0)!=0 ){
29933111
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
29943112
login_set_capabilities(zCap ? zCap : "sx", 0);
29953113
g.useLocalauth = 1;
29963114
}
3115
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
29973116
verify_all_options();
29983117
if( g.argc<3 ){
29993118
usage("FILE");
30003119
}
30013120
blob_zero(&in);
@@ -3044,10 +3163,11 @@
30443163
if( find_option("set-user-caps", 0, 0)!=0 ){
30453164
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
30463165
login_set_capabilities(zCap ? zCap : "sx", 0);
30473166
g.useLocalauth = 1;
30483167
}
3168
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
30493169
verify_all_options();
30503170
if( g.argc!=3 ){
30513171
usage("script");
30523172
}
30533173
if(file_isfile(g.argv[2], ExtFILE)){
30543174
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/
37 /* output. */
38 #define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */
39
40 /*
41 ** Useful and/or "well-known" combinations of flag values.
42 */
43 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
262 ){
263 char *zOut;
264 if( argc!=2 ){
265 return Th_WrongNumArgs(interp, "httpize STRING");
266 }
267 zOut = httpize((char*)argv[1], argl[1]);
268 Th_SetResult(interp, zOut, -1);
269 free(zOut);
270 return TH_OK;
271 }
272
@@ -291,51 +289,12 @@
291 if( argc<2 || argc>3 ){
292 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
293 }
294 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
295 if( g.thTrace ){
296 Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
297 }
298 return rc;
299 }
300
301 /*
302 ** TH1 command: enable_htmlify ?BOOLEAN?
303 **
304 ** Enable or disable the HTML escaping done by all output which
305 ** originates from TH1 (via sendText()).
306 **
307 ** If passed no arguments it instead returns 0 or 1 to indicate the
308 ** current state.
309 */
310 static int enableHtmlifyCmd(
311 Th_Interp *interp,
312 void *p,
313 int argc,
314 const char **argv,
315 int *argl
316 ){
317 int rc = 0, buul;
318 if( argc>3 ){
319 return Th_WrongNumArgs(interp,
320 "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
321 }
322 buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
323 Th_SetResultInt(g.interp, buul);
324 if(argc>1){
325 if( g.thTrace ){
326 Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
327 argl[1],argv[1],buul);
328 }
329 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
330 if(!rc){
331 if(buul){
332 g.th1Flags &= ~TH_INIT_NO_ENCODE;
333 }else{
334 g.th1Flags |= TH_INIT_NO_ENCODE;
335 }
336 }
337 }
338 return rc;
339 }
340
341 /*
@@ -375,25 +334,25 @@
375
376 /*
377 ** Send text to the appropriate output: If pOut is not NULL, it is
378 ** appended there, else to the console or to the CGI reply buffer.
379 ** Escape all characters with special meaning to HTML if the encode
380 ** parameter is true, with the exception that that flag is ignored if
381 ** g.th1Flags has the TH_INIT_NO_ENCODE flag.
382 **
383 ** If pOut is NULL and the global pThOut is not then that blob
384 ** is used for output.
385 */
386 static void sendText(Blob * pOut, const char *z, int n, int encode){
387 if(0==pOut && pThOut!=0){
388 pOut = pThOut;
389 }
390 if(TH_INIT_NO_ENCODE & g.th1Flags){
391 encode = 0;
392 }
393 if( enableOutput && n ){
394 if( n<0 ) n = strlen(z);
 
 
 
 
395 if( encode ){
396 z = htmlize(z, n);
397 n = strlen(z);
398 }
399 if(pOut!=0){
@@ -525,14 +484,22 @@
525 void *pConvert,
526 int argc,
527 const char **argv,
528 int *argl
529 ){
 
 
530 if( argc!=2 ){
531 return Th_WrongNumArgs(interp, "puts STRING");
532 }
533 sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
 
 
 
 
 
 
534 return TH_OK;
535 }
536
537 /*
538 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
557 }
558 if( argc==3 ){
559 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
560 return TH_ERROR;
561 }
 
 
 
 
 
562 }
563 if( withMethod ){
564 cgi_redirect_with_method(argv[1]);
565 }else{
566 cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
660 int nValue = 0;
661 if( argc!=2 ){
662 return Th_WrongNumArgs(interp, "markdown STRING");
663 }
664 blob_zero(&src);
665 blob_init(&src, (char*)argv[1], argl[1]);
666 blob_zero(&title); blob_zero(&body);
667 markdown_to_html(&src, &title, &body);
668 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
669 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
670 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
690 if( argc!=2 ){
691 return Th_WrongNumArgs(interp, "wiki STRING");
692 }
693 if( enableOutput ){
694 Blob src;
695 blob_init(&src, (char*)argv[1], argl[1]);
696 wiki_convert(&src, 0, flags);
697 blob_reset(&src);
698 }
699 return TH_OK;
700 }
@@ -735,11 +707,11 @@
735 ){
736 char *zOut;
737 if( argc!=2 ){
738 return Th_WrongNumArgs(interp, "htmlize STRING");
739 }
740 zOut = htmlize((char*)argv[1], argl[1]);
741 Th_SetResult(interp, zOut, -1);
742 free(zOut);
743 return TH_OK;
744 }
745
@@ -757,11 +729,11 @@
757 ){
758 char *zOut;
759 if( argc!=2 ){
760 return Th_WrongNumArgs(interp, "encode64 STRING");
761 }
762 zOut = encode64((char*)argv[1], argl[1]);
763 Th_SetResult(interp, zOut, -1);
764 free(zOut);
765 return TH_OK;
766 }
767
@@ -778,11 +750,11 @@
778 int argc,
779 const char **argv,
780 int *argl
781 ){
782 char *zOut;
783 if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
784 zOut = db_text("??", "SELECT datetime('now',toLocal())");
785 }else{
786 zOut = db_text("??", "SELECT datetime('now')");
787 }
788 Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
810 if( argc<2 ){
811 return Th_WrongNumArgs(interp, "hascap STRING ...");
812 }
813 for(i=1; rc==1 && i<argc; i++){
814 if( g.thTrace ){
815 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
816 }
817 rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
818 }
819 if( g.thTrace ){
820 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
821 Th_Free(interp, zCapList);
822 }
@@ -858,11 +830,11 @@
858 int i;
859
860 if( argc!=2 ){
861 return Th_WrongNumArgs(interp, "capexpr EXPR");
862 }
863 rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
864 if( rc ) return rc;
865 rc = 0;
866 for(i=0; i<nCap; i++){
867 if( azCap[i][0]=='!' ){
868 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
921 if( argc<2 ){
922 return Th_WrongNumArgs(interp, "hascap STRING ...");
923 }
924 for(i=1; i<argc && rc; i++){
925 int match = 0;
926 for(j=0; j<argl[i]; j++){
 
927 switch( argv[i][j] ){
928 case 'c': match |= searchCap & SRCH_CKIN; break;
929 case 'd': match |= searchCap & SRCH_DOC; break;
930 case 't': match |= searchCap & SRCH_TKT; break;
931 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
932 }
933 }
934 if( !match ) rc = 0;
935 }
936 if( g.thTrace ){
937 Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
938 }
939 Th_SetResultInt(interp, rc);
940 return TH_OK;
941 }
942
@@ -1051,11 +1024,11 @@
1051 #endif
1052 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1053 rc = 1;
1054 }
1055 if( g.thTrace ){
1056 Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
1057 }
1058 Th_SetResultInt(interp, rc);
1059 return TH_OK;
1060 }
1061
@@ -1104,18 +1077,20 @@
1104 const char **argv,
1105 int *argl
1106 ){
1107 int rc = 0;
1108 int i;
 
1109 if( argc!=2 ){
1110 return Th_WrongNumArgs(interp, "anycap STRING");
1111 }
1112 for(i=0; rc==0 && i<argl[1]; i++){
 
1113 rc = login_has_capability((char*)&argv[1][i],1,0);
1114 }
1115 if( g.thTrace ){
1116 Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
1117 }
1118 Th_SetResultInt(interp, rc);
1119 return TH_OK;
1120 }
1121
@@ -1140,22 +1115,23 @@
1140 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1141 }
1142 if( enableOutput ){
1143 int height;
1144 Blob name;
1145 int nValue;
1146 const char *zValue;
1147 char *z, *zH;
1148 int nElem;
1149 int *aszElem;
1150 char **azElem;
1151 int i;
1152
1153 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1154 Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
1155 blob_init(&name, (char*)argv[1], argl[1]);
1156 zValue = Th_Fetch(blob_str(&name), &nValue);
 
1157 zH = htmlize(blob_buffer(&name), blob_size(&name));
1158 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1159 free(zH);
1160 sendText(0,z, -1, 0);
1161 free(z);
@@ -1247,11 +1223,11 @@
1247 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1248 }
1249 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1250 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1251 z = argv[1];
1252 size = argl[1];
1253 for(n=1, i=0; i<size; i++){
1254 if( z[i]=='\n' ){
1255 n++;
1256 if( n>=iMax ) break;
1257 }
@@ -1407,11 +1383,12 @@
1407 return TH_OK;
1408 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1409 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1410 return TH_OK;
1411 }else{
1412 Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
 
1413 return TH_ERROR;
1414 }
1415 }
1416
1417 /*
@@ -1426,17 +1403,21 @@
1426 int argc,
1427 const char **argv,
1428 int *argl
1429 ){
1430 const char *zDefault = 0;
 
 
1431 if( argc!=2 && argc!=3 ){
1432 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1433 }
1434 if( argc==3 ){
1435 zDefault = argv[2];
1436 }
1437 Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
 
 
1438 return TH_OK;
1439 }
1440
1441 /*
1442 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
1848 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1849 Th_SetResult(interp, zUTime, -1);
1850 return TH_OK;
1851 }
1852
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1853
1854 /*
1855 ** TH1 command: randhex N
1856 **
1857 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
1923 int res = TH_OK;
1924 int nVar;
1925 char *zErr = 0;
1926 int noComplain = 0;
1927
1928 if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
 
 
1929 argc--;
1930 argv++;
1931 argl++;
1932 noComplain = 1;
1933 }
@@ -1939,15 +1959,22 @@
1939 Th_ErrorMessage(interp, "database is not open", 0, 0);
1940 return TH_ERROR;
1941 }
1942 zSql = argv[1];
1943 nSql = argl[1];
 
 
 
 
 
 
 
1944 while( res==TH_OK && nSql>0 ){
1945 zErr = 0;
1946 report_restrict_sql(&zErr);
1947 g.dbIgnoreErrors++;
1948 rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
1949 g.dbIgnoreErrors--;
1950 report_unrestrict_sql();
1951 if( rc!=0 || zErr!=0 ){
1952 if( noComplain ) return TH_OK;
1953 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
1964 int szVar = zVar ? th_strlen(zVar) : 0;
1965 if( szVar>1 && zVar[0]=='$'
1966 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1967 int nVal;
1968 const char *zVal = Th_GetResult(interp, &nVal);
1969 sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
1970 }
1971 }
1972 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
1973 int nCol = sqlite3_column_count(pStmt);
1974 for(i=0; i<nCol; i++){
1975 const char *zCol = sqlite3_column_name(pStmt, i);
1976 int szCol = th_strlen(zCol);
1977 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
1978 int szVal = sqlite3_column_bytes(pStmt, i);
1979 Th_SetVar(interp, zCol, szCol, zVal, szVal);
1980 }
1981 if( g.thTrace ){
1982 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
1983 }
1984 res = Th_Eval(interp, 0, argv[2], argl[2]);
1985 if( g.thTrace ){
1986 int nTrRes;
1987 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
1988 Th_Trace("[query_eval] => %h {%#h}<br>\n",
1989 Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
1990 }
1991 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
1992 }
1993 rc = sqlite3_finalize(pStmt);
1994 if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
2038 Th_SetResult(interp, 0, 0);
2039 rc = TH_OK;
2040 }
2041 if( g.thTrace ){
2042 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2043 argl[nArg], argv[nArg], rc);
2044 }
2045 return rc;
2046 }
2047
2048 /*
@@ -2121,11 +2148,11 @@
2121 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2122 }
2123 zErr = re_compile(&pRe, argv[nArg], noCase);
2124 if( !zErr ){
2125 Th_SetResultInt(interp, re_match(pRe,
2126 (const unsigned char *)argv[nArg+1], argl[nArg+1]));
2127 rc = TH_OK;
2128 }else{
2129 Th_SetResult(interp, zErr, -1);
2130 rc = TH_ERROR;
2131 }
@@ -2160,11 +2187,11 @@
2160 UrlData urlData;
2161
2162 if( argc<2 || argc>5 ){
2163 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2164 }
2165 if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
2166 fAsynchronous = 1; nArg++;
2167 }
2168 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2169 if( nArg+1!=argc && nArg+2!=argc ){
2170 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
2189 return TH_ERROR;
2190 }
2191 re_free(pRe);
2192 blob_zero(&payload);
2193 if( nArg+2==argc ){
2194 blob_append(&payload, argv[nArg+1], argl[nArg+1]);
2195 zType = "POST";
2196 }else{
2197 zType = "GET";
2198 }
2199 if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
2268 if( argc!=2 ){
2269 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2270 }
2271 pOrig = Th_SetOutputBlob(&out);
2272 zStr = argv[1];
2273 nStr = argl[1];
2274 rc = Th_Eval(g.interp, 0, zStr, nStr);
2275 Th_SetOutputBlob(pOrig);
2276 if(0==rc){
2277 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2278 }
@@ -2356,11 +2383,10 @@
2356 {"copybtn", copybtnCmd, 0},
2357 {"date", dateCmd, 0},
2358 {"decorate", wikiCmd, (void*)&aFlags[2]},
2359 {"defHeader", defHeaderCmd, 0},
2360 {"dir", dirCmd, 0},
2361 {"enable_htmlify",enableHtmlifyCmd, 0},
2362 {"enable_output", enableOutputCmd, 0},
2363 {"encode64", encode64Cmd, 0},
2364 {"getParameter", getParameterCmd, 0},
2365 {"glob_match", globMatchCmd, 0},
2366 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
2387 {"setting", settingCmd, 0},
2388 {"styleFooter", styleFooterCmd, 0},
2389 {"styleHeader", styleHeaderCmd, 0},
2390 {"styleScript", styleScriptCmd, 0},
2391 {"submenu", submenuCmd, 0},
 
2392 {"tclReady", tclReadyCmd, 0},
2393 {"trace", traceCmd, 0},
2394 {"stime", stimeCmd, 0},
 
2395 {"unversioned", unversionedCmd, 0},
2396 {"utime", utimeCmd, 0},
2397 {"verifyCsrf", verifyCsrfCmd, 0},
2398 {"verifyLogin", verifyLoginCmd, 0},
2399 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
2494 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2495 }
2496 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2497 }
2498 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2499
2500 /*
2501 ** Appends an element to a TH1 list value. This function is called by the
2502 ** transfer subsystem; therefore, it must be very careful to avoid doing
2503 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
2680 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2681 /*
2682 ** Make sure that the TH1 script error was not caused by a "missing"
2683 ** command hook handler as that is not actually an error condition.
2684 */
 
2685 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2686 sendError(0,zResult, nResult, 0);
2687 }else{
2688 /*
2689 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
2767 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2768 /*
2769 ** Make sure that the TH1 script error was not caused by a "missing"
2770 ** webpage hook handler as that is not actually an error condition.
2771 */
 
2772 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2773 sendError(0,zResult, nResult, 1);
2774 }else{
2775 /*
2776 ** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
2894 }
2895 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2896 z += i+1+n;
2897 i = 0;
2898 zResult = (char*)Th_GetResult(g.interp, &n);
2899 sendText(pOut,(char*)zResult, n, encode);
 
 
 
 
 
2900 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2901 sendText(pOut,z, i, 0);
2902 z += i+5;
2903 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2904 if( g.thTrace ){
@@ -2907,11 +2958,11 @@
2907 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2908 if( g.thTrace ){
2909 int nTrRes;
2910 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2911 Th_Trace("[render_eval] => %h {%#h}<br>\n",
2912 Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
2913 }
2914 if( rc!=TH_OK ) break;
2915 z += i;
2916 if( z[0] ){ z += 6; }
2917 i = 0;
@@ -2949,14 +3000,81 @@
2949 ** e.g. via the "render" script function binding, need to use the
2950 ** pThOut blob in order to avoid out-of-order output if
2951 ** Th_SetOutputBlob() has been called. If it has not been called,
2952 ** pThOut will be 0, which will redirect the output to CGI/stdout,
2953 ** as appropriate. We need to pass on g.th1Flags for the case of
2954 ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
2955 ** inadvertently toggled off by a recursive call.
2956 */;
2957 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2958
2959 /*
2960 ** COMMAND: test-th-render
2961 **
2962 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
2992 if( find_option("set-user-caps", 0, 0)!=0 ){
2993 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
2994 login_set_capabilities(zCap ? zCap : "sx", 0);
2995 g.useLocalauth = 1;
2996 }
 
2997 verify_all_options();
2998 if( g.argc<3 ){
2999 usage("FILE");
3000 }
3001 blob_zero(&in);
@@ -3044,10 +3163,11 @@
3044 if( find_option("set-user-caps", 0, 0)!=0 ){
3045 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3046 login_set_capabilities(zCap ? zCap : "sx", 0);
3047 g.useLocalauth = 1;
3048 }
 
3049 verify_all_options();
3050 if( g.argc!=3 ){
3051 usage("script");
3052 }
3053 if(file_isfile(g.argv[2], ExtFILE)){
3054
--- src/th_main.c
+++ src/th_main.c
@@ -31,13 +31,11 @@
31 #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
32 #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */
33 #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
34 #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
35 #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */
36 #define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */
 
 
37
38 /*
39 ** Useful and/or "well-known" combinations of flag values.
40 */
41 #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */
@@ -262,11 +260,11 @@
260 ){
261 char *zOut;
262 if( argc!=2 ){
263 return Th_WrongNumArgs(interp, "httpize STRING");
264 }
265 zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
266 Th_SetResult(interp, zOut, -1);
267 free(zOut);
268 return TH_OK;
269 }
270
@@ -291,51 +289,12 @@
289 if( argc<2 || argc>3 ){
290 return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
291 }
292 rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
293 if( g.thTrace ){
294 Th_Trace("enable_output {%.*s} -> %d<br>\n",
295 TH1_LEN(argl[1]),argv[1],enableOutput);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296 }
297 return rc;
298 }
299
300 /*
@@ -375,25 +334,25 @@
334
335 /*
336 ** Send text to the appropriate output: If pOut is not NULL, it is
337 ** appended there, else to the console or to the CGI reply buffer.
338 ** Escape all characters with special meaning to HTML if the encode
339 ** parameter is true.
 
340 **
341 ** If pOut is NULL and the global pThOut is not then that blob
342 ** is used for output.
343 */
344 static void sendText(Blob *pOut, const char *z, int n, int encode){
345 if(0==pOut && pThOut!=0){
346 pOut = pThOut;
347 }
 
 
 
348 if( enableOutput && n ){
349 if( n<0 ){
350 n = strlen(z);
351 }else{
352 n = TH1_LEN(n);
353 }
354 if( encode ){
355 z = htmlize(z, n);
356 n = strlen(z);
357 }
358 if(pOut!=0){
@@ -525,14 +484,22 @@
484 void *pConvert,
485 int argc,
486 const char **argv,
487 int *argl
488 ){
489 int encode = *(unsigned int*)pConvert;
490 int n;
491 if( argc!=2 ){
492 return Th_WrongNumArgs(interp, "puts STRING");
493 }
494 n = argl[1];
495 if( encode==0 && n>0 && TH1_TAINTED(n) ){
496 if( Th_ReportTaint(interp, "output string", argv[1], n) ){
497 return TH_ERROR;
498 }
499 }
500 sendText(0,(char*)argv[1], TH1_LEN(n), encode);
501 return TH_OK;
502 }
503
504 /*
505 ** TH1 command: redirect URL ?withMethod?
@@ -557,10 +524,15 @@
524 }
525 if( argc==3 ){
526 if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
527 return TH_ERROR;
528 }
529 }
530 if( TH1_TAINTED(argl[1])
531 && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
532 ){
533 return TH_ERROR;
534 }
535 if( withMethod ){
536 cgi_redirect_with_method(argv[1]);
537 }else{
538 cgi_redirect(argv[1]);
@@ -660,11 +632,11 @@
632 int nValue = 0;
633 if( argc!=2 ){
634 return Th_WrongNumArgs(interp, "markdown STRING");
635 }
636 blob_zero(&src);
637 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
638 blob_zero(&title); blob_zero(&body);
639 markdown_to_html(&src, &title, &body);
640 Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
641 Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
642 Th_SetResult(interp, zValue, nValue);
@@ -690,11 +662,11 @@
662 if( argc!=2 ){
663 return Th_WrongNumArgs(interp, "wiki STRING");
664 }
665 if( enableOutput ){
666 Blob src;
667 blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
668 wiki_convert(&src, 0, flags);
669 blob_reset(&src);
670 }
671 return TH_OK;
672 }
@@ -735,11 +707,11 @@
707 ){
708 char *zOut;
709 if( argc!=2 ){
710 return Th_WrongNumArgs(interp, "htmlize STRING");
711 }
712 zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
713 Th_SetResult(interp, zOut, -1);
714 free(zOut);
715 return TH_OK;
716 }
717
@@ -757,11 +729,11 @@
729 ){
730 char *zOut;
731 if( argc!=2 ){
732 return Th_WrongNumArgs(interp, "encode64 STRING");
733 }
734 zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
735 Th_SetResult(interp, zOut, -1);
736 free(zOut);
737 return TH_OK;
738 }
739
@@ -778,11 +750,11 @@
750 int argc,
751 const char **argv,
752 int *argl
753 ){
754 char *zOut;
755 if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
756 zOut = db_text("??", "SELECT datetime('now',toLocal())");
757 }else{
758 zOut = db_text("??", "SELECT datetime('now')");
759 }
760 Th_SetResult(interp, zOut, -1);
@@ -810,13 +782,13 @@
782 if( argc<2 ){
783 return Th_WrongNumArgs(interp, "hascap STRING ...");
784 }
785 for(i=1; rc==1 && i<argc; i++){
786 if( g.thTrace ){
787 Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
788 }
789 rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
790 }
791 if( g.thTrace ){
792 Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
793 Th_Free(interp, zCapList);
794 }
@@ -858,11 +830,11 @@
830 int i;
831
832 if( argc!=2 ){
833 return Th_WrongNumArgs(interp, "capexpr EXPR");
834 }
835 rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
836 if( rc ) return rc;
837 rc = 0;
838 for(i=0; i<nCap; i++){
839 if( azCap[i][0]=='!' ){
840 rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
@@ -921,11 +893,12 @@
893 if( argc<2 ){
894 return Th_WrongNumArgs(interp, "hascap STRING ...");
895 }
896 for(i=1; i<argc && rc; i++){
897 int match = 0;
898 int nn = TH1_LEN(argl[i]);
899 for(j=0; j<nn; j++){
900 switch( argv[i][j] ){
901 case 'c': match |= searchCap & SRCH_CKIN; break;
902 case 'd': match |= searchCap & SRCH_DOC; break;
903 case 't': match |= searchCap & SRCH_TKT; break;
904 case 'w': match |= searchCap & SRCH_WIKI; break;
@@ -932,11 +905,11 @@
905 }
906 }
907 if( !match ) rc = 0;
908 }
909 if( g.thTrace ){
910 Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
911 }
912 Th_SetResultInt(interp, rc);
913 return TH_OK;
914 }
915
@@ -1051,11 +1024,11 @@
1024 #endif
1025 else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
1026 rc = 1;
1027 }
1028 if( g.thTrace ){
1029 Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
1030 }
1031 Th_SetResultInt(interp, rc);
1032 return TH_OK;
1033 }
1034
@@ -1104,18 +1077,20 @@
1077 const char **argv,
1078 int *argl
1079 ){
1080 int rc = 0;
1081 int i;
1082 int nn;
1083 if( argc!=2 ){
1084 return Th_WrongNumArgs(interp, "anycap STRING");
1085 }
1086 nn = TH1_LEN(argl[1]);
1087 for(i=0; rc==0 && i<nn; i++){
1088 rc = login_has_capability((char*)&argv[1][i],1,0);
1089 }
1090 if( g.thTrace ){
1091 Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
1092 }
1093 Th_SetResultInt(interp, rc);
1094 return TH_OK;
1095 }
1096
@@ -1140,22 +1115,23 @@
1115 return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
1116 }
1117 if( enableOutput ){
1118 int height;
1119 Blob name;
1120 int nValue = 0;
1121 const char *zValue;
1122 char *z, *zH;
1123 int nElem;
1124 int *aszElem;
1125 char **azElem;
1126 int i;
1127
1128 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
1129 Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
1130 blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
1131 zValue = Th_Fetch(blob_str(&name), &nValue);
1132 nValue = TH1_LEN(nValue);
1133 zH = htmlize(blob_buffer(&name), blob_size(&name));
1134 z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
1135 free(zH);
1136 sendText(0,z, -1, 0);
1137 free(z);
@@ -1247,11 +1223,11 @@
1223 return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
1224 }
1225 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
1226 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
1227 z = argv[1];
1228 size = TH1_LEN(argl[1]);
1229 for(n=1, i=0; i<size; i++){
1230 if( z[i]=='\n' ){
1231 n++;
1232 if( n>=iMax ) break;
1233 }
@@ -1407,11 +1383,12 @@
1383 return TH_OK;
1384 }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
1385 Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
1386 return TH_OK;
1387 }else{
1388 Th_ErrorMessage(interp, "unsupported global state:",
1389 argv[1], TH1_LEN(argl[1]));
1390 return TH_ERROR;
1391 }
1392 }
1393
1394 /*
@@ -1426,17 +1403,21 @@
1403 int argc,
1404 const char **argv,
1405 int *argl
1406 ){
1407 const char *zDefault = 0;
1408 const char *zVal;
1409 int sz;
1410 if( argc!=2 && argc!=3 ){
1411 return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
1412 }
1413 if( argc==3 ){
1414 zDefault = argv[2];
1415 }
1416 zVal = cgi_parameter(argv[1], zDefault);
1417 sz = th_strlen(zVal);
1418 Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
1419 return TH_OK;
1420 }
1421
1422 /*
1423 ** TH1 command: setParameter NAME VALUE
@@ -1848,10 +1829,47 @@
1829 sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
1830 Th_SetResult(interp, zUTime, -1);
1831 return TH_OK;
1832 }
1833
1834 /*
1835 ** TH1 command: taint STRING
1836 **
1837 ** Return a copy of STRING that is marked as tainted.
1838 */
1839 static int taintCmd(
1840 Th_Interp *interp,
1841 void *p,
1842 int argc,
1843 const char **argv,
1844 int *argl
1845 ){
1846 if( argc!=2 ){
1847 return Th_WrongNumArgs(interp, "STRING");
1848 }
1849 Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
1850 return TH_OK;
1851 }
1852
1853 /*
1854 ** TH1 command: untaint STRING
1855 **
1856 ** Return a copy of STRING that is marked as untainted.
1857 */
1858 static int untaintCmd(
1859 Th_Interp *interp,
1860 void *p,
1861 int argc,
1862 const char **argv,
1863 int *argl
1864 ){
1865 if( argc!=2 ){
1866 return Th_WrongNumArgs(interp, "STRING");
1867 }
1868 Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
1869 return TH_OK;
1870 }
1871
1872 /*
1873 ** TH1 command: randhex N
1874 **
1875 ** Return N*2 random hexadecimal digits with N<50. If N is omitted,
@@ -1923,11 +1941,13 @@
1941 int res = TH_OK;
1942 int nVar;
1943 char *zErr = 0;
1944 int noComplain = 0;
1945
1946 if( argc>3 && TH1_LEN(argl[1])==11
1947 && strncmp(argv[1], "-nocomplain", 11)==0
1948 ){
1949 argc--;
1950 argv++;
1951 argl++;
1952 noComplain = 1;
1953 }
@@ -1939,15 +1959,22 @@
1959 Th_ErrorMessage(interp, "database is not open", 0, 0);
1960 return TH_ERROR;
1961 }
1962 zSql = argv[1];
1963 nSql = argl[1];
1964 if( TH1_TAINTED(nSql) ){
1965 if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
1966 return TH_ERROR;
1967 }
1968 nSql = TH1_LEN(nSql);
1969 }
1970
1971 while( res==TH_OK && nSql>0 ){
1972 zErr = 0;
1973 report_restrict_sql(&zErr);
1974 g.dbIgnoreErrors++;
1975 rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
1976 g.dbIgnoreErrors--;
1977 report_unrestrict_sql();
1978 if( rc!=0 || zErr!=0 ){
1979 if( noComplain ) return TH_OK;
1980 Th_ErrorMessage(interp, "SQL error: ",
@@ -1964,31 +1991,31 @@
1991 int szVar = zVar ? th_strlen(zVar) : 0;
1992 if( szVar>1 && zVar[0]=='$'
1993 && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
1994 int nVal;
1995 const char *zVal = Th_GetResult(interp, &nVal);
1996 sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
1997 }
1998 }
1999 while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
2000 int nCol = sqlite3_column_count(pStmt);
2001 for(i=0; i<nCol; i++){
2002 const char *zCol = sqlite3_column_name(pStmt, i);
2003 int szCol = th_strlen(zCol);
2004 const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
2005 int szVal = sqlite3_column_bytes(pStmt, i);
2006 Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
2007 }
2008 if( g.thTrace ){
2009 Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
2010 }
2011 res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
2012 if( g.thTrace ){
2013 int nTrRes;
2014 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2015 Th_Trace("[query_eval] => %h {%#h}<br>\n",
2016 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
2017 }
2018 if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
2019 }
2020 rc = sqlite3_finalize(pStmt);
2021 if( rc!=SQLITE_OK ){
@@ -2038,11 +2065,11 @@
2065 Th_SetResult(interp, 0, 0);
2066 rc = TH_OK;
2067 }
2068 if( g.thTrace ){
2069 Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
2070 TH1_LEN(argl[nArg]), argv[nArg], rc);
2071 }
2072 return rc;
2073 }
2074
2075 /*
@@ -2121,11 +2148,11 @@
2148 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
2149 }
2150 zErr = re_compile(&pRe, argv[nArg], noCase);
2151 if( !zErr ){
2152 Th_SetResultInt(interp, re_match(pRe,
2153 (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
2154 rc = TH_OK;
2155 }else{
2156 Th_SetResult(interp, zErr, -1);
2157 rc = TH_ERROR;
2158 }
@@ -2160,11 +2187,11 @@
2187 UrlData urlData;
2188
2189 if( argc<2 || argc>5 ){
2190 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
2191 }
2192 if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
2193 fAsynchronous = 1; nArg++;
2194 }
2195 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
2196 if( nArg+1!=argc && nArg+2!=argc ){
2197 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
@@ -2189,11 +2216,11 @@
2216 return TH_ERROR;
2217 }
2218 re_free(pRe);
2219 blob_zero(&payload);
2220 if( nArg+2==argc ){
2221 blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
2222 zType = "POST";
2223 }else{
2224 zType = "GET";
2225 }
2226 if( fAsynchronous ){
@@ -2268,11 +2295,11 @@
2295 if( argc!=2 ){
2296 return Th_WrongNumArgs(interp, "captureTh1 STRING");
2297 }
2298 pOrig = Th_SetOutputBlob(&out);
2299 zStr = argv[1];
2300 nStr = TH1_LEN(argl[1]);
2301 rc = Th_Eval(g.interp, 0, zStr, nStr);
2302 Th_SetOutputBlob(pOrig);
2303 if(0==rc){
2304 Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
2305 }
@@ -2356,11 +2383,10 @@
2383 {"copybtn", copybtnCmd, 0},
2384 {"date", dateCmd, 0},
2385 {"decorate", wikiCmd, (void*)&aFlags[2]},
2386 {"defHeader", defHeaderCmd, 0},
2387 {"dir", dirCmd, 0},
 
2388 {"enable_output", enableOutputCmd, 0},
2389 {"encode64", encode64Cmd, 0},
2390 {"getParameter", getParameterCmd, 0},
2391 {"glob_match", globMatchCmd, 0},
2392 {"globalState", globalStateCmd, 0},
@@ -2387,13 +2413,15 @@
2413 {"setting", settingCmd, 0},
2414 {"styleFooter", styleFooterCmd, 0},
2415 {"styleHeader", styleHeaderCmd, 0},
2416 {"styleScript", styleScriptCmd, 0},
2417 {"submenu", submenuCmd, 0},
2418 {"taint", taintCmd, 0},
2419 {"tclReady", tclReadyCmd, 0},
2420 {"trace", traceCmd, 0},
2421 {"stime", stimeCmd, 0},
2422 {"untaint", untaintCmd, 0},
2423 {"unversioned", unversionedCmd, 0},
2424 {"utime", utimeCmd, 0},
2425 {"verifyCsrf", verifyCsrfCmd, 0},
2426 {"verifyLogin", verifyLoginCmd, 0},
2427 {"wiki", wikiCmd, (void*)&aFlags[0]},
@@ -2494,10 +2522,26 @@
2522 Th_Trace("set %h {%h}<br>\n", zName, zValue);
2523 }
2524 Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
2525 }
2526 }
2527
2528 /*
2529 ** Store a string value in a variable in the interpreter
2530 ** with the "taint" marking, so that TH1 knows that this
2531 ** variable contains content under the control of the remote
2532 ** user and presents a risk of XSS or SQL-injection attacks.
2533 */
2534 void Th_StoreUnsafe(const char *zName, const char *zValue){
2535 Th_FossilInit(TH_INIT_DEFAULT);
2536 if( zValue ){
2537 if( g.thTrace ){
2538 Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
2539 }
2540 Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
2541 }
2542 }
2543
2544 /*
2545 ** Appends an element to a TH1 list value. This function is called by the
2546 ** transfer subsystem; therefore, it must be very careful to avoid doing
2547 ** any unnecessary work. To that end, the TH1 subsystem will not be called
@@ -2680,10 +2724,11 @@
2724 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2725 /*
2726 ** Make sure that the TH1 script error was not caused by a "missing"
2727 ** command hook handler as that is not actually an error condition.
2728 */
2729 nResult = TH1_LEN(nResult);
2730 if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
2731 sendError(0,zResult, nResult, 0);
2732 }else{
2733 /*
2734 ** There is no command hook handler "installed". This situation
@@ -2767,10 +2812,11 @@
2812 char *zResult = (char*)Th_GetResult(g.interp, &nResult);
2813 /*
2814 ** Make sure that the TH1 script error was not caused by a "missing"
2815 ** webpage hook handler as that is not actually an error condition.
2816 */
2817 nResult = TH1_LEN(nResult);
2818 if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
2819 sendError(0,zResult, nResult, 1);
2820 }else{
2821 /*
2822 ** There is no webpage hook handler "installed". This situation
@@ -2894,11 +2940,16 @@
2940 }
2941 rc = Th_GetVar(g.interp, (char*)zVar, nVar);
2942 z += i+1+n;
2943 i = 0;
2944 zResult = (char*)Th_GetResult(g.interp, &n);
2945 if( !TH1_TAINTED(n)
2946 || encode
2947 || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
2948 ){
2949 sendText(pOut,(char*)zResult, n, encode);
2950 }
2951 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
2952 sendText(pOut,z, i, 0);
2953 z += i+5;
2954 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
2955 if( g.thTrace ){
@@ -2907,11 +2958,11 @@
2958 rc = Th_Eval(g.interp, 0, (const char*)z, i);
2959 if( g.thTrace ){
2960 int nTrRes;
2961 char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
2962 Th_Trace("[render_eval] => %h {%#h}<br>\n",
2963 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
2964 }
2965 if( rc!=TH_OK ) break;
2966 z += i;
2967 if( z[0] ){ z += 6; }
2968 i = 0;
@@ -2949,14 +3000,81 @@
3000 ** e.g. via the "render" script function binding, need to use the
3001 ** pThOut blob in order to avoid out-of-order output if
3002 ** Th_SetOutputBlob() has been called. If it has not been called,
3003 ** pThOut will be 0, which will redirect the output to CGI/stdout,
3004 ** as appropriate. We need to pass on g.th1Flags for the case of
3005 ** recursive calls.
 
3006 */;
3007 }
3008
3009 /*
3010 ** SETTING: vuln-report width=8 default=log
3011 **
3012 ** This setting controls Fossil's behavior when it encounters a potential
3013 ** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
3014 ** scripts. Choices are:
3015 **
3016 ** off Do nothing. Ignore the vulnerability.
3017 **
3018 ** log Write a report of the problem into the error log.
3019 **
3020 ** block Like "log" but also prevent the offending TH1 command
3021 ** from running.
3022 **
3023 ** fatal Render an error message page instead of the requested
3024 ** page.
3025 */
3026
3027 /*
3028 ** Report misuse of a tainted string in TH1.
3029 **
3030 ** The behavior depends on the vuln-report setting. If "off", this routine
3031 ** is a no-op. Otherwise, right a message into the error log. If
3032 ** vuln-report is "log", that is all that happens. But for any other
3033 ** value of vuln-report, a fatal error is raised.
3034 */
3035 int Th_ReportTaint(
3036 Th_Interp *interp, /* Report error here, if an error is reported */
3037 const char *zWhere, /* Where the tainted string appears */
3038 const char *zStr, /* The tainted string */
3039 int nStr /* Length of the tainted string */
3040 ){
3041 static const char *zDisp = 0; /* Dispensation; what to do with the error */
3042 const char *zVulnType; /* Type of vulnerability */
3043
3044 if( zDisp==0 ) zDisp = db_get("vuln-report","log");
3045 if( is_false(zDisp) ) return 0;
3046 if( strstr(zWhere,"SQL")!=0 ){
3047 zVulnType = "SQL-injection";
3048 }else{
3049 zVulnType = "XSS";
3050 }
3051 nStr = TH1_LEN(nStr);
3052 fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
3053 zVulnType, zWhere, nStr, zStr);
3054 if( strcmp(zDisp,"log")==0 ){
3055 return 0;
3056 }
3057 if( strcmp(zDisp,"block")==0 ){
3058 char *z = mprintf("tainted %s: \"", zWhere);
3059 Th_ErrorMessage(interp, z, zStr, nStr);
3060 fossil_free(z);
3061 }else{
3062 char *z = mprintf("%#h", nStr, zStr);
3063 zDisp = "off";
3064 cgi_reset_content();
3065 style_submenu_enable(0);
3066 style_set_current_feature("error");
3067 style_header("Configuration Error");
3068 @ <p>Error in a TH1 configuration script:
3069 @ tainted %h(zWhere): "%z(z)"
3070 style_finish_page();
3071 cgi_reply();
3072 fossil_exit(1);
3073 }
3074 return 1;
3075 }
3076
3077 /*
3078 ** COMMAND: test-th-render
3079 **
3080 ** Usage: %fossil test-th-render FILE
@@ -2992,10 +3110,11 @@
3110 if( find_option("set-user-caps", 0, 0)!=0 ){
3111 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3112 login_set_capabilities(zCap ? zCap : "sx", 0);
3113 g.useLocalauth = 1;
3114 }
3115 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3116 verify_all_options();
3117 if( g.argc<3 ){
3118 usage("FILE");
3119 }
3120 blob_zero(&in);
@@ -3044,10 +3163,11 @@
3163 if( find_option("set-user-caps", 0, 0)!=0 ){
3164 const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
3165 login_set_capabilities(zCap ? zCap : "sx", 0);
3166 g.useLocalauth = 1;
3167 }
3168 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
3169 verify_all_options();
3170 if( g.argc!=3 ){
3171 usage("script");
3172 }
3173 if(file_isfile(g.argv[2], ExtFILE)){
3174
+34 -88
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -24,10 +24,14 @@
2424
2525
#include "sqlite3.h"
2626
#include "th.h"
2727
#include "tcl.h"
2828
29
+#if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size)
30
+# define Tcl_Size int
31
+#endif
32
+
2933
/*
3034
** This macro is used to verify that the header version of Tcl meets some
3135
** minimum requirement.
3236
*/
3337
#define MINIMUM_TCL_VERSION(major, minor) \
@@ -41,16 +45,16 @@
4145
#define USE_ARGV_TO_OBJV() \
4246
int objc; \
4347
Tcl_Obj **objv; \
4448
int obji;
4549
46
-#define COPY_ARGV_TO_OBJV() \
47
- objc = argc-1; \
48
- objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49
- for(obji=1; obji<argc; obji++){ \
50
- objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \
51
- Tcl_IncrRefCount(objv[obji-1]); \
50
+#define COPY_ARGV_TO_OBJV() \
51
+ objc = argc-1; \
52
+ objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
53
+ for(obji=1; obji<argc; obji++){ \
54
+ objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
55
+ Tcl_IncrRefCount(objv[obji-1]); \
5256
}
5357
5458
#define FREE_ARGV_TO_OBJV() \
5559
for(obji=1; obji<argc; obji++){ \
5660
Tcl_DecrRefCount(objv[obji-1]); \
@@ -183,11 +187,15 @@
183187
** the only Tcl API functions that MUST be called prior to being able to call
184188
** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
185189
** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
186190
** and Tcl_Finalize function types are also required.
187191
*/
192
+#if TCL_MAJOR_VERSION>=9
193
+typedef const char *(tcl_FindExecutableProc) (const char *);
194
+#else
188195
typedef void (tcl_FindExecutableProc) (const char *);
196
+#endif
189197
typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
190198
typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
191199
typedef void (tcl_FinalizeProc) (void);
192200
193201
/*
@@ -321,27 +329,10 @@
321329
** by the caller. This must be declared here because quite a few functions in
322330
** this file need to use it before it can be defined.
323331
*/
324332
static int createTclInterp(Th_Interp *interp, void *pContext);
325333
326
-/*
327
-** Returns the TH1 return code corresponding to the specified Tcl
328
-** return code.
329
-*/
330
-static int getTh1ReturnCode(
331
- int rc /* The Tcl return code value to convert. */
332
-){
333
- switch( rc ){
334
- case /*0*/ TCL_OK: return /*0*/ TH_OK;
335
- case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR;
336
- case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN;
337
- case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK;
338
- case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
339
- default /*?*/: return /*?*/ rc;
340
- }
341
-}
342
-
343334
/*
344335
** Returns the Tcl return code corresponding to the specified TH1
345336
** return code.
346337
*/
347338
static int getTclReturnCode(
@@ -387,10 +378,12 @@
387378
static char *getTclResult(
388379
Tcl_Interp *pInterp,
389380
int *pN
390381
){
391382
Tcl_Obj *resultPtr;
383
+ Tcl_Size n;
384
+ char *zRes;
392385
393386
if( !pInterp ){ /* This should not happen. */
394387
if( pN ) *pN = 0;
395388
return 0;
396389
}
@@ -397,11 +390,13 @@
397390
resultPtr = Tcl_GetObjResult(pInterp);
398391
if( !resultPtr ){ /* This should not happen either? */
399392
if( pN ) *pN = 0;
400393
return 0;
401394
}
402
- return Tcl_GetStringFromObj(resultPtr, pN);
395
+ zRes = Tcl_GetStringFromObj(resultPtr, &n);
396
+ *pN = (int)n;
397
+ return zRes;
403398
}
404399
405400
/*
406401
** Tcl context information used by TH1. This structure definition has been
407402
** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +411,12 @@
416411
tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
417412
Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
418413
int useObjProc; /* Non-zero if an objProc can be called directly. */
419414
int useTip285; /* Non-zero if TIP #285 is available. */
420415
const char *setup; /* The optional Tcl setup script. */
421
- tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */
422
- void *pPreContext; /* Optional, provided to xPreEval(). */
423
- tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
424
- void *pPostContext; /* Optional, provided to xPostEval(). */
425416
};
426417
427
-/*
428
-** This function calls the configured xPreEval or xPostEval functions, if any.
429
-** May have arbitrary side-effects. This function returns the result of the
430
-** called notification function or the value of the rc argument if there is no
431
-** notification function configured.
432
-*/
433
-static int notifyPreOrPostEval(
434
- int bIsPost,
435
- Th_Interp *interp,
436
- void *ctx,
437
- int argc,
438
- const char **argv,
439
- int *argl,
440
- int rc
441
-){
442
- struct TclContext *tclContext = (struct TclContext *)ctx;
443
- tcl_NotifyProc *xNotifyProc;
444
-
445
- if( !tclContext ){
446
- Th_ErrorMessage(interp,
447
- "invalid Tcl context", (const char *)"", 0);
448
- return TH_ERROR;
449
- }
450
- xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
451
- if( xNotifyProc ){
452
- rc = xNotifyProc(bIsPost ?
453
- tclContext->pPostContext : tclContext->pPreContext,
454
- interp, ctx, argc, argv, argl, rc);
455
- }
456
- return rc;
457
-}
458
-
459418
/*
460419
** TH1 command: tclEval arg ?arg ...?
461420
**
462421
** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
463422
** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +444,13 @@
485444
tclInterp = GET_CTX_TCL_INTERP(ctx);
486445
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487446
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488447
return TH_ERROR;
489448
}
490
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
491
- if( rc!=TH_OK ){
492
- return rc;
493
- }
494449
Tcl_Preserve((ClientData)tclInterp);
495450
if( argc==2 ){
496
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
451
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
497452
Tcl_IncrRefCount(objPtr);
498453
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499454
Tcl_DecrRefCount(objPtr); objPtr = 0;
500455
}else{
501456
USE_ARGV_TO_OBJV();
@@ -507,12 +462,10 @@
507462
FREE_ARGV_TO_OBJV();
508463
}
509464
zResult = getTclResult(tclInterp, &nResult);
510465
Th_SetResult(interp, zResult, nResult);
511466
Tcl_Release((ClientData)tclInterp);
512
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
513
- getTh1ReturnCode(rc));
514467
return rc;
515468
}
516469
517470
/*
518471
** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +498,13 @@
545498
tclInterp = GET_CTX_TCL_INTERP(ctx);
546499
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547500
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548501
return TH_ERROR;
549502
}
550
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
551
- if( rc!=TH_OK ){
552
- return rc;
553
- }
554503
Tcl_Preserve((ClientData)tclInterp);
555504
if( argc==2 ){
556
- objPtr = Tcl_NewStringObj(argv[1], argl[1]);
505
+ objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
557506
Tcl_IncrRefCount(objPtr);
558507
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559508
Tcl_DecrRefCount(objPtr); objPtr = 0;
560509
}else{
561510
USE_ARGV_TO_OBJV();
@@ -565,21 +514,21 @@
565514
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
566515
Tcl_DecrRefCount(objPtr); objPtr = 0;
567516
FREE_ARGV_TO_OBJV();
568517
}
569518
if( rc==TCL_OK ){
570
- zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
519
+ Tcl_Size szResult = 0;
520
+ zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
521
+ nResult = (int)szResult;
571522
}else{
572523
zResult = getTclResult(tclInterp, &nResult);
573524
}
574
- Th_SetResult(interp, zResult, nResult);
525
+ Th_SetResult(interp, zResult, (int)nResult);
575526
if( rc==TCL_OK ){
576527
Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577528
}
578529
Tcl_Release((ClientData)tclInterp);
579
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
580
- getTh1ReturnCode(rc));
581530
return rc;
582531
}
583532
584533
/*
585534
** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +559,16 @@
610559
tclInterp = GET_CTX_TCL_INTERP(ctx);
611560
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612561
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613562
return TH_ERROR;
614563
}
615
- rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
616
- if( rc!=TH_OK ){
617
- return rc;
618
- }
619564
Tcl_Preserve((ClientData)tclInterp);
620565
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621566
if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622567
Tcl_Command command;
623568
Tcl_CmdInfo cmdInfo;
624
- Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
569
+ Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
625570
Tcl_IncrRefCount(objPtr);
626571
command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627572
if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628573
Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629574
Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,12 +594,10 @@
649594
FREE_ARGV_TO_OBJV();
650595
}
651596
zResult = getTclResult(tclInterp, &nResult);
652597
Th_SetResult(interp, zResult, nResult);
653598
Tcl_Release((ClientData)tclInterp);
654
- rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
655
- getTh1ReturnCode(rc));
656599
return rc;
657600
}
658601
659602
/*
660603
** TH1 command: tclIsSafe
@@ -767,10 +710,11 @@
767710
int objc,
768711
Tcl_Obj *const objv[]
769712
){
770713
Th_Interp *th1Interp;
771714
int nArg;
715
+ Tcl_Size szArg;
772716
const char *arg;
773717
int rc;
774718
775719
if( objc!=2 ){
776720
Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +723,15 @@
779723
th1Interp = (Th_Interp *)clientData;
780724
if( !th1Interp ){
781725
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
782726
return TCL_ERROR;
783727
}
784
- arg = Tcl_GetStringFromObj(objv[1], &nArg);
728
+ arg = Tcl_GetStringFromObj(objv[1], &szArg);
729
+ nArg = (int)szArg;
785730
rc = Th_Eval(th1Interp, 0, arg, nArg);
786731
arg = Th_GetResult(th1Interp, &nArg);
787
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
732
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
788733
return getTclReturnCode(rc);
789734
}
790735
791736
/*
792737
** Tcl command: th1Expr arg
@@ -800,10 +745,11 @@
800745
int objc,
801746
Tcl_Obj *const objv[]
802747
){
803748
Th_Interp *th1Interp;
804749
int nArg;
750
+ Tcl_Size szArg;
805751
const char *arg;
806752
int rc;
807753
808754
if( objc!=2 ){
809755
Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +758,14 @@
812758
th1Interp = (Th_Interp *)clientData;
813759
if( !th1Interp ){
814760
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
815761
return TCL_ERROR;
816762
}
817
- arg = Tcl_GetStringFromObj(objv[1], &nArg);
818
- rc = Th_Expr(th1Interp, arg, nArg);
763
+ arg = Tcl_GetStringFromObj(objv[1], &szArg);
764
+ rc = Th_Expr(th1Interp, arg, (int)szArg);
819765
arg = Th_GetResult(th1Interp, &nArg);
820
- Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
766
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
821767
return getTclReturnCode(rc);
822768
}
823769
824770
/*
825771
** Array of Tcl integration commands. Used when adding or removing the Tcl
826772
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -24,10 +24,14 @@
24
25 #include "sqlite3.h"
26 #include "th.h"
27 #include "tcl.h"
28
 
 
 
 
29 /*
30 ** This macro is used to verify that the header version of Tcl meets some
31 ** minimum requirement.
32 */
33 #define MINIMUM_TCL_VERSION(major, minor) \
@@ -41,16 +45,16 @@
41 #define USE_ARGV_TO_OBJV() \
42 int objc; \
43 Tcl_Obj **objv; \
44 int obji;
45
46 #define COPY_ARGV_TO_OBJV() \
47 objc = argc-1; \
48 objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
49 for(obji=1; obji<argc; obji++){ \
50 objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \
51 Tcl_IncrRefCount(objv[obji-1]); \
52 }
53
54 #define FREE_ARGV_TO_OBJV() \
55 for(obji=1; obji<argc; obji++){ \
56 Tcl_DecrRefCount(objv[obji-1]); \
@@ -183,11 +187,15 @@
183 ** the only Tcl API functions that MUST be called prior to being able to call
184 ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
185 ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
186 ** and Tcl_Finalize function types are also required.
187 */
 
 
 
188 typedef void (tcl_FindExecutableProc) (const char *);
 
189 typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
190 typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
191 typedef void (tcl_FinalizeProc) (void);
192
193 /*
@@ -321,27 +329,10 @@
321 ** by the caller. This must be declared here because quite a few functions in
322 ** this file need to use it before it can be defined.
323 */
324 static int createTclInterp(Th_Interp *interp, void *pContext);
325
326 /*
327 ** Returns the TH1 return code corresponding to the specified Tcl
328 ** return code.
329 */
330 static int getTh1ReturnCode(
331 int rc /* The Tcl return code value to convert. */
332 ){
333 switch( rc ){
334 case /*0*/ TCL_OK: return /*0*/ TH_OK;
335 case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR;
336 case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN;
337 case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK;
338 case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
339 default /*?*/: return /*?*/ rc;
340 }
341 }
342
343 /*
344 ** Returns the Tcl return code corresponding to the specified TH1
345 ** return code.
346 */
347 static int getTclReturnCode(
@@ -387,10 +378,12 @@
387 static char *getTclResult(
388 Tcl_Interp *pInterp,
389 int *pN
390 ){
391 Tcl_Obj *resultPtr;
 
 
392
393 if( !pInterp ){ /* This should not happen. */
394 if( pN ) *pN = 0;
395 return 0;
396 }
@@ -397,11 +390,13 @@
397 resultPtr = Tcl_GetObjResult(pInterp);
398 if( !resultPtr ){ /* This should not happen either? */
399 if( pN ) *pN = 0;
400 return 0;
401 }
402 return Tcl_GetStringFromObj(resultPtr, pN);
 
 
403 }
404
405 /*
406 ** Tcl context information used by TH1. This structure definition has been
407 ** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +411,12 @@
416 tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
417 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
418 int useObjProc; /* Non-zero if an objProc can be called directly. */
419 int useTip285; /* Non-zero if TIP #285 is available. */
420 const char *setup; /* The optional Tcl setup script. */
421 tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */
422 void *pPreContext; /* Optional, provided to xPreEval(). */
423 tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
424 void *pPostContext; /* Optional, provided to xPostEval(). */
425 };
426
427 /*
428 ** This function calls the configured xPreEval or xPostEval functions, if any.
429 ** May have arbitrary side-effects. This function returns the result of the
430 ** called notification function or the value of the rc argument if there is no
431 ** notification function configured.
432 */
433 static int notifyPreOrPostEval(
434 int bIsPost,
435 Th_Interp *interp,
436 void *ctx,
437 int argc,
438 const char **argv,
439 int *argl,
440 int rc
441 ){
442 struct TclContext *tclContext = (struct TclContext *)ctx;
443 tcl_NotifyProc *xNotifyProc;
444
445 if( !tclContext ){
446 Th_ErrorMessage(interp,
447 "invalid Tcl context", (const char *)"", 0);
448 return TH_ERROR;
449 }
450 xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
451 if( xNotifyProc ){
452 rc = xNotifyProc(bIsPost ?
453 tclContext->pPostContext : tclContext->pPreContext,
454 interp, ctx, argc, argv, argl, rc);
455 }
456 return rc;
457 }
458
459 /*
460 ** TH1 command: tclEval arg ?arg ...?
461 **
462 ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
463 ** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +444,13 @@
485 tclInterp = GET_CTX_TCL_INTERP(ctx);
486 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
487 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
488 return TH_ERROR;
489 }
490 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
491 if( rc!=TH_OK ){
492 return rc;
493 }
494 Tcl_Preserve((ClientData)tclInterp);
495 if( argc==2 ){
496 objPtr = Tcl_NewStringObj(argv[1], argl[1]);
497 Tcl_IncrRefCount(objPtr);
498 rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
499 Tcl_DecrRefCount(objPtr); objPtr = 0;
500 }else{
501 USE_ARGV_TO_OBJV();
@@ -507,12 +462,10 @@
507 FREE_ARGV_TO_OBJV();
508 }
509 zResult = getTclResult(tclInterp, &nResult);
510 Th_SetResult(interp, zResult, nResult);
511 Tcl_Release((ClientData)tclInterp);
512 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
513 getTh1ReturnCode(rc));
514 return rc;
515 }
516
517 /*
518 ** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +498,13 @@
545 tclInterp = GET_CTX_TCL_INTERP(ctx);
546 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
547 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
548 return TH_ERROR;
549 }
550 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
551 if( rc!=TH_OK ){
552 return rc;
553 }
554 Tcl_Preserve((ClientData)tclInterp);
555 if( argc==2 ){
556 objPtr = Tcl_NewStringObj(argv[1], argl[1]);
557 Tcl_IncrRefCount(objPtr);
558 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
559 Tcl_DecrRefCount(objPtr); objPtr = 0;
560 }else{
561 USE_ARGV_TO_OBJV();
@@ -565,21 +514,21 @@
565 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
566 Tcl_DecrRefCount(objPtr); objPtr = 0;
567 FREE_ARGV_TO_OBJV();
568 }
569 if( rc==TCL_OK ){
570 zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
 
 
571 }else{
572 zResult = getTclResult(tclInterp, &nResult);
573 }
574 Th_SetResult(interp, zResult, nResult);
575 if( rc==TCL_OK ){
576 Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
577 }
578 Tcl_Release((ClientData)tclInterp);
579 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
580 getTh1ReturnCode(rc));
581 return rc;
582 }
583
584 /*
585 ** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +559,16 @@
610 tclInterp = GET_CTX_TCL_INTERP(ctx);
611 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
612 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
613 return TH_ERROR;
614 }
615 rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
616 if( rc!=TH_OK ){
617 return rc;
618 }
619 Tcl_Preserve((ClientData)tclInterp);
620 #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
621 if( GET_CTX_TCL_USEOBJPROC(ctx) ){
622 Tcl_Command command;
623 Tcl_CmdInfo cmdInfo;
624 Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
625 Tcl_IncrRefCount(objPtr);
626 command = Tcl_GetCommandFromObj(tclInterp, objPtr);
627 if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
628 Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
629 Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,12 +594,10 @@
649 FREE_ARGV_TO_OBJV();
650 }
651 zResult = getTclResult(tclInterp, &nResult);
652 Th_SetResult(interp, zResult, nResult);
653 Tcl_Release((ClientData)tclInterp);
654 rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
655 getTh1ReturnCode(rc));
656 return rc;
657 }
658
659 /*
660 ** TH1 command: tclIsSafe
@@ -767,10 +710,11 @@
767 int objc,
768 Tcl_Obj *const objv[]
769 ){
770 Th_Interp *th1Interp;
771 int nArg;
 
772 const char *arg;
773 int rc;
774
775 if( objc!=2 ){
776 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +723,15 @@
779 th1Interp = (Th_Interp *)clientData;
780 if( !th1Interp ){
781 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
782 return TCL_ERROR;
783 }
784 arg = Tcl_GetStringFromObj(objv[1], &nArg);
 
785 rc = Th_Eval(th1Interp, 0, arg, nArg);
786 arg = Th_GetResult(th1Interp, &nArg);
787 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
788 return getTclReturnCode(rc);
789 }
790
791 /*
792 ** Tcl command: th1Expr arg
@@ -800,10 +745,11 @@
800 int objc,
801 Tcl_Obj *const objv[]
802 ){
803 Th_Interp *th1Interp;
804 int nArg;
 
805 const char *arg;
806 int rc;
807
808 if( objc!=2 ){
809 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +758,14 @@
812 th1Interp = (Th_Interp *)clientData;
813 if( !th1Interp ){
814 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
815 return TCL_ERROR;
816 }
817 arg = Tcl_GetStringFromObj(objv[1], &nArg);
818 rc = Th_Expr(th1Interp, arg, nArg);
819 arg = Th_GetResult(th1Interp, &nArg);
820 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
821 return getTclReturnCode(rc);
822 }
823
824 /*
825 ** Array of Tcl integration commands. Used when adding or removing the Tcl
826
--- src/th_tcl.c
+++ src/th_tcl.c
@@ -24,10 +24,14 @@
24
25 #include "sqlite3.h"
26 #include "th.h"
27 #include "tcl.h"
28
29 #if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size)
30 # define Tcl_Size int
31 #endif
32
33 /*
34 ** This macro is used to verify that the header version of Tcl meets some
35 ** minimum requirement.
36 */
37 #define MINIMUM_TCL_VERSION(major, minor) \
@@ -41,16 +45,16 @@
45 #define USE_ARGV_TO_OBJV() \
46 int objc; \
47 Tcl_Obj **objv; \
48 int obji;
49
50 #define COPY_ARGV_TO_OBJV() \
51 objc = argc-1; \
52 objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
53 for(obji=1; obji<argc; obji++){ \
54 objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
55 Tcl_IncrRefCount(objv[obji-1]); \
56 }
57
58 #define FREE_ARGV_TO_OBJV() \
59 for(obji=1; obji<argc; obji++){ \
60 Tcl_DecrRefCount(objv[obji-1]); \
@@ -183,11 +187,15 @@
187 ** the only Tcl API functions that MUST be called prior to being able to call
188 ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete
189 ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
190 ** and Tcl_Finalize function types are also required.
191 */
192 #if TCL_MAJOR_VERSION>=9
193 typedef const char *(tcl_FindExecutableProc) (const char *);
194 #else
195 typedef void (tcl_FindExecutableProc) (const char *);
196 #endif
197 typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
198 typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
199 typedef void (tcl_FinalizeProc) (void);
200
201 /*
@@ -321,27 +329,10 @@
329 ** by the caller. This must be declared here because quite a few functions in
330 ** this file need to use it before it can be defined.
331 */
332 static int createTclInterp(Th_Interp *interp, void *pContext);
333
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334 /*
335 ** Returns the Tcl return code corresponding to the specified TH1
336 ** return code.
337 */
338 static int getTclReturnCode(
@@ -387,10 +378,12 @@
378 static char *getTclResult(
379 Tcl_Interp *pInterp,
380 int *pN
381 ){
382 Tcl_Obj *resultPtr;
383 Tcl_Size n;
384 char *zRes;
385
386 if( !pInterp ){ /* This should not happen. */
387 if( pN ) *pN = 0;
388 return 0;
389 }
@@ -397,11 +390,13 @@
390 resultPtr = Tcl_GetObjResult(pInterp);
391 if( !resultPtr ){ /* This should not happen either? */
392 if( pN ) *pN = 0;
393 return 0;
394 }
395 zRes = Tcl_GetStringFromObj(resultPtr, &n);
396 *pN = (int)n;
397 return zRes;
398 }
399
400 /*
401 ** Tcl context information used by TH1. This structure definition has been
402 ** copied from and should be kept in sync with the one in "main.c".
@@ -416,48 +411,12 @@
411 tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
412 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
413 int useObjProc; /* Non-zero if an objProc can be called directly. */
414 int useTip285; /* Non-zero if TIP #285 is available. */
415 const char *setup; /* The optional Tcl setup script. */
 
 
 
 
416 };
417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418 /*
419 ** TH1 command: tclEval arg ?arg ...?
420 **
421 ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
422 ** error is generated, it will be transformed into a TH1 script error. The
@@ -485,17 +444,13 @@
444 tclInterp = GET_CTX_TCL_INTERP(ctx);
445 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
446 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
447 return TH_ERROR;
448 }
 
 
 
 
449 Tcl_Preserve((ClientData)tclInterp);
450 if( argc==2 ){
451 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
452 Tcl_IncrRefCount(objPtr);
453 rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
454 Tcl_DecrRefCount(objPtr); objPtr = 0;
455 }else{
456 USE_ARGV_TO_OBJV();
@@ -507,12 +462,10 @@
462 FREE_ARGV_TO_OBJV();
463 }
464 zResult = getTclResult(tclInterp, &nResult);
465 Th_SetResult(interp, zResult, nResult);
466 Tcl_Release((ClientData)tclInterp);
 
 
467 return rc;
468 }
469
470 /*
471 ** TH1 command: tclExpr arg ?arg ...?
@@ -545,17 +498,13 @@
498 tclInterp = GET_CTX_TCL_INTERP(ctx);
499 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
500 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
501 return TH_ERROR;
502 }
 
 
 
 
503 Tcl_Preserve((ClientData)tclInterp);
504 if( argc==2 ){
505 objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
506 Tcl_IncrRefCount(objPtr);
507 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
508 Tcl_DecrRefCount(objPtr); objPtr = 0;
509 }else{
510 USE_ARGV_TO_OBJV();
@@ -565,21 +514,21 @@
514 rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
515 Tcl_DecrRefCount(objPtr); objPtr = 0;
516 FREE_ARGV_TO_OBJV();
517 }
518 if( rc==TCL_OK ){
519 Tcl_Size szResult = 0;
520 zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
521 nResult = (int)szResult;
522 }else{
523 zResult = getTclResult(tclInterp, &nResult);
524 }
525 Th_SetResult(interp, zResult, (int)nResult);
526 if( rc==TCL_OK ){
527 Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
528 }
529 Tcl_Release((ClientData)tclInterp);
 
 
530 return rc;
531 }
532
533 /*
534 ** TH1 command: tclInvoke command ?arg ...?
@@ -610,20 +559,16 @@
559 tclInterp = GET_CTX_TCL_INTERP(ctx);
560 if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
561 Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
562 return TH_ERROR;
563 }
 
 
 
 
564 Tcl_Preserve((ClientData)tclInterp);
565 #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
566 if( GET_CTX_TCL_USEOBJPROC(ctx) ){
567 Tcl_Command command;
568 Tcl_CmdInfo cmdInfo;
569 Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
570 Tcl_IncrRefCount(objPtr);
571 command = Tcl_GetCommandFromObj(tclInterp, objPtr);
572 if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
573 Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
574 Tcl_DecrRefCount(objPtr); objPtr = 0;
@@ -649,12 +594,10 @@
594 FREE_ARGV_TO_OBJV();
595 }
596 zResult = getTclResult(tclInterp, &nResult);
597 Th_SetResult(interp, zResult, nResult);
598 Tcl_Release((ClientData)tclInterp);
 
 
599 return rc;
600 }
601
602 /*
603 ** TH1 command: tclIsSafe
@@ -767,10 +710,11 @@
710 int objc,
711 Tcl_Obj *const objv[]
712 ){
713 Th_Interp *th1Interp;
714 int nArg;
715 Tcl_Size szArg;
716 const char *arg;
717 int rc;
718
719 if( objc!=2 ){
720 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -779,14 +723,15 @@
723 th1Interp = (Th_Interp *)clientData;
724 if( !th1Interp ){
725 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
726 return TCL_ERROR;
727 }
728 arg = Tcl_GetStringFromObj(objv[1], &szArg);
729 nArg = (int)szArg;
730 rc = Th_Eval(th1Interp, 0, arg, nArg);
731 arg = Th_GetResult(th1Interp, &nArg);
732 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
733 return getTclReturnCode(rc);
734 }
735
736 /*
737 ** Tcl command: th1Expr arg
@@ -800,10 +745,11 @@
745 int objc,
746 Tcl_Obj *const objv[]
747 ){
748 Th_Interp *th1Interp;
749 int nArg;
750 Tcl_Size szArg;
751 const char *arg;
752 int rc;
753
754 if( objc!=2 ){
755 Tcl_WrongNumArgs(interp, 1, objv, "arg");
@@ -812,14 +758,14 @@
758 th1Interp = (Th_Interp *)clientData;
759 if( !th1Interp ){
760 Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
761 return TCL_ERROR;
762 }
763 arg = Tcl_GetStringFromObj(objv[1], &szArg);
764 rc = Th_Expr(th1Interp, arg, (int)szArg);
765 arg = Th_GetResult(th1Interp, &nArg);
766 Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
767 return getTclReturnCode(rc);
768 }
769
770 /*
771 ** Array of Tcl integration commands. Used when adding or removing the Tcl
772
+2 -2
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
18881888
if( zTagName ){
18891889
zType = "ci";
18901890
if( matchStyle==MS_EXACT ){
18911891
/* For exact maching, inhibit links to the selected tag. */
18921892
zThisTag = zTagName;
1893
- Th_Store("current_checkin", zTagName);
1893
+ Th_StoreUnsafe("current_checkin", zTagName);
18941894
}
18951895
18961896
/* Display a checkbox to enable/disable display of related check-ins. */
18971897
if( advancedMenu ){
18981898
style_submenu_checkbox("rel", "Related", 0, 0);
@@ -3847,11 +3847,11 @@
38473847
** Query parameters:
38483848
**
38493849
** today=DATE Use DATE as today's date
38503850
*/
38513851
void thisdayinhistory_page(void){
3852
- static int aYearsAgo[] = { 1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 75, 100 };
3852
+ static int aYearsAgo[] = { 1,2,3,4,5,10,15,20,25,30,40,50,75,100 };
38533853
const char *zToday;
38543854
char *zStartOfProject;
38553855
int i;
38563856
Stmt q;
38573857
char *z;
38583858
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
1888 if( zTagName ){
1889 zType = "ci";
1890 if( matchStyle==MS_EXACT ){
1891 /* For exact maching, inhibit links to the selected tag. */
1892 zThisTag = zTagName;
1893 Th_Store("current_checkin", zTagName);
1894 }
1895
1896 /* Display a checkbox to enable/disable display of related check-ins. */
1897 if( advancedMenu ){
1898 style_submenu_checkbox("rel", "Related", 0, 0);
@@ -3847,11 +3847,11 @@
3847 ** Query parameters:
3848 **
3849 ** today=DATE Use DATE as today's date
3850 */
3851 void thisdayinhistory_page(void){
3852 static int aYearsAgo[] = { 1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 75, 100 };
3853 const char *zToday;
3854 char *zStartOfProject;
3855 int i;
3856 Stmt q;
3857 char *z;
3858
--- src/timeline.c
+++ src/timeline.c
@@ -1888,11 +1888,11 @@
1888 if( zTagName ){
1889 zType = "ci";
1890 if( matchStyle==MS_EXACT ){
1891 /* For exact maching, inhibit links to the selected tag. */
1892 zThisTag = zTagName;
1893 Th_StoreUnsafe("current_checkin", zTagName);
1894 }
1895
1896 /* Display a checkbox to enable/disable display of related check-ins. */
1897 if( advancedMenu ){
1898 style_submenu_checkbox("rel", "Related", 0, 0);
@@ -3847,11 +3847,11 @@
3847 ** Query parameters:
3848 **
3849 ** today=DATE Use DATE as today's date
3850 */
3851 void thisdayinhistory_page(void){
3852 static int aYearsAgo[] = { 1,2,3,4,5,10,15,20,25,30,40,50,75,100 };
3853 const char *zToday;
3854 char *zStartOfProject;
3855 int i;
3856 Stmt q;
3857 char *z;
3858
+18 -12
--- src/tkt.c
+++ src/tkt.c
@@ -188,14 +188,19 @@
188188
*/
189189
static void initializeVariablesFromDb(void){
190190
const char *zName;
191191
Stmt q;
192192
int i, n, size, j;
193
+ const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";
193194
194195
zName = PD("name","-none-");
195
- db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, *"
196
+ db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
197
+ "datetime(%s,toLocal()) AS tkt_datetime_creation, "
198
+ "julianday('now') - tkt_mtime, "
199
+ "julianday('now') - %s, *"
196200
" FROM ticket WHERE tkt_uuid GLOB '%q*'",
201
+ zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
197202
zName);
198203
if( db_step(&q)==SQLITE_ROW ){
199204
n = db_column_count(&q);
200205
for(i=0; i<n; i++){
201206
const char *zVal = db_column_text(&q, i);
@@ -207,19 +212,22 @@
207212
zVal = zRevealed = db_reveal(zVal);
208213
}
209214
if( (j = fieldId(zName))>=0 ){
210215
aField[j].zValue = mprintf("%s", zVal);
211216
}else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
217
+ /* TICKET table columns that begin with "tkt_" are always safe */
212218
Th_Store(zName, zVal);
213219
}
214220
free(zRevealed);
215221
}
222
+ Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
223
+ Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
216224
}
217225
db_finalize(&q);
218226
for(i=0; i<nField; i++){
219227
if( Th_Fetch(aField[i].zName, &size)==0 ){
220
- Th_Store(aField[i].zName, aField[i].zValue);
228
+ Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
221229
}
222230
}
223231
}
224232
225233
/*
@@ -228,11 +236,11 @@
228236
static void initializeVariablesFromCGI(void){
229237
int i;
230238
const char *z;
231239
232240
for(i=0; (z = cgi_parameter_name(i))!=0; i++){
233
- Th_Store(z, P(z));
241
+ Th_StoreUnsafe(z, P(z));
234242
}
235243
}
236244
237245
/*
238246
** Information about a single J-card
@@ -768,13 +776,10 @@
768776
style_submenu_element("Timeline", "%R/info/%T", zUuid);
769777
}
770778
zFullName = db_text(0,
771779
"SELECT tkt_uuid FROM ticket"
772780
" WHERE tkt_uuid GLOB '%q*'", zUuid);
773
- if( g.perm.WrWiki && g.perm.WrTkt ){
774
- style_submenu_element("Edit Description", "%R/wikiedit?name=ticket/%T", zFullName);
775
- }
776781
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
777782
ticket_init();
778783
initializeVariablesFromCGI();
779784
getAllTicketFields();
780785
initializeVariablesFromDb();
@@ -812,15 +817,15 @@
812817
if( argc!=3 ){
813818
return Th_WrongNumArgs(interp, "append_field FIELD STRING");
814819
}
815820
if( g.thTrace ){
816821
Th_Trace("append_field %#h {%#h}<br>\n",
817
- argl[1], argv[1], argl[2], argv[2]);
822
+ TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
818823
}
819824
for(idx=0; idx<nField; idx++){
820
- if( memcmp(aField[idx].zName, argv[1], argl[1])==0
821
- && aField[idx].zName[argl[1]]==0 ){
825
+ if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
826
+ && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
822827
break;
823828
}
824829
}
825830
if( idx>=nField ){
826831
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -932,10 +937,11 @@
932937
const char *zValue;
933938
int nValue;
934939
if( aField[i].zAppend ) continue;
935940
zValue = Th_Fetch(aField[i].zName, &nValue);
936941
if( zValue ){
942
+ nValue = TH1_LEN(nValue);
937943
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
938944
if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
939945
|| memcmp(zValue, aField[i].zValue, nValue)!=0
940946
||(int)strlen(aField[i].zValue)!=nValue
941947
){
@@ -1034,16 +1040,16 @@
10341040
if( uid ){
10351041
char * zEmail =
10361042
db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
10371043
uid);
10381044
if( zEmail ){
1039
- Th_Store("private_contact", zEmail);
1045
+ Th_StoreUnsafe("private_contact", zEmail);
10401046
fossil_free(zEmail);
10411047
}
10421048
}
10431049
}
1044
- Th_Store("login", login_name());
1050
+ Th_StoreUnsafe("login", login_name());
10451051
Th_Store("date", db_text(0, "SELECT datetime('now')"));
10461052
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
10471053
(void*)&zNewUuid, 0);
10481054
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
10491055
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1114,11 +1120,11 @@
11141120
initializeVariablesFromDb();
11151121
if( g.zPath[0]=='d' ) showAllFields();
11161122
form_begin(0, "%R/%s", g.zPath);
11171123
@ <input type="hidden" name="name" value="%s(zName)">
11181124
zScript = ticket_editpage_code();
1119
- Th_Store("login", login_name());
1125
+ Th_StoreUnsafe("login", login_name());
11201126
Th_Store("date", db_text(0, "SELECT datetime('now')"));
11211127
Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
11221128
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
11231129
if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
11241130
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
11251131
--- src/tkt.c
+++ src/tkt.c
@@ -188,14 +188,19 @@
188 */
189 static void initializeVariablesFromDb(void){
190 const char *zName;
191 Stmt q;
192 int i, n, size, j;
 
193
194 zName = PD("name","-none-");
195 db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, *"
 
 
 
196 " FROM ticket WHERE tkt_uuid GLOB '%q*'",
 
197 zName);
198 if( db_step(&q)==SQLITE_ROW ){
199 n = db_column_count(&q);
200 for(i=0; i<n; i++){
201 const char *zVal = db_column_text(&q, i);
@@ -207,19 +212,22 @@
207 zVal = zRevealed = db_reveal(zVal);
208 }
209 if( (j = fieldId(zName))>=0 ){
210 aField[j].zValue = mprintf("%s", zVal);
211 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
 
212 Th_Store(zName, zVal);
213 }
214 free(zRevealed);
215 }
 
 
216 }
217 db_finalize(&q);
218 for(i=0; i<nField; i++){
219 if( Th_Fetch(aField[i].zName, &size)==0 ){
220 Th_Store(aField[i].zName, aField[i].zValue);
221 }
222 }
223 }
224
225 /*
@@ -228,11 +236,11 @@
228 static void initializeVariablesFromCGI(void){
229 int i;
230 const char *z;
231
232 for(i=0; (z = cgi_parameter_name(i))!=0; i++){
233 Th_Store(z, P(z));
234 }
235 }
236
237 /*
238 ** Information about a single J-card
@@ -768,13 +776,10 @@
768 style_submenu_element("Timeline", "%R/info/%T", zUuid);
769 }
770 zFullName = db_text(0,
771 "SELECT tkt_uuid FROM ticket"
772 " WHERE tkt_uuid GLOB '%q*'", zUuid);
773 if( g.perm.WrWiki && g.perm.WrTkt ){
774 style_submenu_element("Edit Description", "%R/wikiedit?name=ticket/%T", zFullName);
775 }
776 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
777 ticket_init();
778 initializeVariablesFromCGI();
779 getAllTicketFields();
780 initializeVariablesFromDb();
@@ -812,15 +817,15 @@
812 if( argc!=3 ){
813 return Th_WrongNumArgs(interp, "append_field FIELD STRING");
814 }
815 if( g.thTrace ){
816 Th_Trace("append_field %#h {%#h}<br>\n",
817 argl[1], argv[1], argl[2], argv[2]);
818 }
819 for(idx=0; idx<nField; idx++){
820 if( memcmp(aField[idx].zName, argv[1], argl[1])==0
821 && aField[idx].zName[argl[1]]==0 ){
822 break;
823 }
824 }
825 if( idx>=nField ){
826 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -932,10 +937,11 @@
932 const char *zValue;
933 int nValue;
934 if( aField[i].zAppend ) continue;
935 zValue = Th_Fetch(aField[i].zName, &nValue);
936 if( zValue ){
 
937 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
938 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
939 || memcmp(zValue, aField[i].zValue, nValue)!=0
940 ||(int)strlen(aField[i].zValue)!=nValue
941 ){
@@ -1034,16 +1040,16 @@
1034 if( uid ){
1035 char * zEmail =
1036 db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
1037 uid);
1038 if( zEmail ){
1039 Th_Store("private_contact", zEmail);
1040 fossil_free(zEmail);
1041 }
1042 }
1043 }
1044 Th_Store("login", login_name());
1045 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1046 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
1047 (void*)&zNewUuid, 0);
1048 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
1049 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1114,11 +1120,11 @@
1114 initializeVariablesFromDb();
1115 if( g.zPath[0]=='d' ) showAllFields();
1116 form_begin(0, "%R/%s", g.zPath);
1117 @ <input type="hidden" name="name" value="%s(zName)">
1118 zScript = ticket_editpage_code();
1119 Th_Store("login", login_name());
1120 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1121 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
1122 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
1123 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
1124 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
1125
--- src/tkt.c
+++ src/tkt.c
@@ -188,14 +188,19 @@
188 */
189 static void initializeVariablesFromDb(void){
190 const char *zName;
191 Stmt q;
192 int i, n, size, j;
193 const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";
194
195 zName = PD("name","-none-");
196 db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
197 "datetime(%s,toLocal()) AS tkt_datetime_creation, "
198 "julianday('now') - tkt_mtime, "
199 "julianday('now') - %s, *"
200 " FROM ticket WHERE tkt_uuid GLOB '%q*'",
201 zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
202 zName);
203 if( db_step(&q)==SQLITE_ROW ){
204 n = db_column_count(&q);
205 for(i=0; i<n; i++){
206 const char *zVal = db_column_text(&q, i);
@@ -207,19 +212,22 @@
212 zVal = zRevealed = db_reveal(zVal);
213 }
214 if( (j = fieldId(zName))>=0 ){
215 aField[j].zValue = mprintf("%s", zVal);
216 }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
217 /* TICKET table columns that begin with "tkt_" are always safe */
218 Th_Store(zName, zVal);
219 }
220 free(zRevealed);
221 }
222 Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
223 Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
224 }
225 db_finalize(&q);
226 for(i=0; i<nField; i++){
227 if( Th_Fetch(aField[i].zName, &size)==0 ){
228 Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
229 }
230 }
231 }
232
233 /*
@@ -228,11 +236,11 @@
236 static void initializeVariablesFromCGI(void){
237 int i;
238 const char *z;
239
240 for(i=0; (z = cgi_parameter_name(i))!=0; i++){
241 Th_StoreUnsafe(z, P(z));
242 }
243 }
244
245 /*
246 ** Information about a single J-card
@@ -768,13 +776,10 @@
776 style_submenu_element("Timeline", "%R/info/%T", zUuid);
777 }
778 zFullName = db_text(0,
779 "SELECT tkt_uuid FROM ticket"
780 " WHERE tkt_uuid GLOB '%q*'", zUuid);
 
 
 
781 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
782 ticket_init();
783 initializeVariablesFromCGI();
784 getAllTicketFields();
785 initializeVariablesFromDb();
@@ -812,15 +817,15 @@
817 if( argc!=3 ){
818 return Th_WrongNumArgs(interp, "append_field FIELD STRING");
819 }
820 if( g.thTrace ){
821 Th_Trace("append_field %#h {%#h}<br>\n",
822 TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
823 }
824 for(idx=0; idx<nField; idx++){
825 if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
826 && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
827 break;
828 }
829 }
830 if( idx>=nField ){
831 Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
@@ -932,10 +937,11 @@
937 const char *zValue;
938 int nValue;
939 if( aField[i].zAppend ) continue;
940 zValue = Th_Fetch(aField[i].zName, &nValue);
941 if( zValue ){
942 nValue = TH1_LEN(nValue);
943 while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
944 if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
945 || memcmp(zValue, aField[i].zValue, nValue)!=0
946 ||(int)strlen(aField[i].zValue)!=nValue
947 ){
@@ -1034,16 +1040,16 @@
1040 if( uid ){
1041 char * zEmail =
1042 db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
1043 uid);
1044 if( zEmail ){
1045 Th_StoreUnsafe("private_contact", zEmail);
1046 fossil_free(zEmail);
1047 }
1048 }
1049 }
1050 Th_StoreUnsafe("login", login_name());
1051 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1052 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
1053 (void*)&zNewUuid, 0);
1054 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
1055 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
@@ -1114,11 +1120,11 @@
1120 initializeVariablesFromDb();
1121 if( g.zPath[0]=='d' ) showAllFields();
1122 form_begin(0, "%R/%s", g.zPath);
1123 @ <input type="hidden" name="name" value="%s(zName)">
1124 zScript = ticket_editpage_code();
1125 Th_StoreUnsafe("login", login_name());
1126 Th_Store("date", db_text(0, "SELECT datetime('now')"));
1127 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
1128 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
1129 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
1130 if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
1131
+101 -12
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -125,11 +125,11 @@
125125
if( !g.perm.Setup ){
126126
login_needed(0);
127127
return;
128128
}
129129
style_set_current_feature("tktsetup");
130
- if( PB("setup") ){
130
+ if( P("setup") ){
131131
cgi_redirect("tktsetup");
132132
}
133133
isSubmit = P("submit")!=0;
134134
z = P("x");
135135
if( z==0 ){
@@ -164,10 +164,11 @@
164164
@ <hr>
165165
@ <h2>Default %s(zTitle)</h2>
166166
@ <blockquote><pre>
167167
@ %h(zDfltValue)
168168
@ </pre></blockquote>
169
+ style_submenu_element("Back", "%R/tktsetup");
169170
style_finish_page();
170171
}
171172
172173
/*
173174
** WEBPAGE: tktsetup_tab
@@ -480,11 +481,11 @@
480481
@ <th1>
481482
@ if {[info exists tkt_uuid]} {
482483
@ html "<td class='tktDspValue' colspan='3'>"
483484
@ copybtn hash-tk 0 $tkt_uuid 2
484485
@ if {[hascap s]} {
485
-@ html " ($tkt_id)"
486
+@ puts " ($tkt_id)"
486487
@ }
487488
@ html "</td></tr>\n"
488489
@ } else {
489490
@ if {[hascap s]} {
490491
@ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -495,10 +496,13 @@
495496
@ }
496497
@
497498
@ if {[capexpr {n}]} {
498499
@ submenu link "Copy Ticket" /tktnew/$tkt_uuid
499500
@ }
501
+@ if {[capexpr {nk}]} {
502
+@ submenu link "Edit Wiki" /wikiedit?name=ticket/$tkt_uuid
503
+@ }
500504
@ </th1>
501505
@ <tr><td class="tktDspLabel">Title:</td>
502506
@ <td class="tktDspValue" colspan="3">
503507
@ $<title>
504508
@ </td></tr>
@@ -521,23 +525,63 @@
521525
@ $<resolution>
522526
@ </td></tr>
523527
@ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
524528
@ <th1>
525529
@ if {[info exists tkt_datetime]} {
526
-@ html $tkt_datetime
530
+@ puts $tkt_datetime
531
+@ }
532
+@ if {[info exists tkt_mage]} {
533
+@ html "<br>[htmlize $tkt_mage] ago"
527534
@ }
528535
@ </th1>
529536
@ </td>
537
+@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
538
+@ <th1>
539
+@ if {[info exists tkt_datetime_creation]} {
540
+@ puts $tkt_datetime_creation
541
+@ }
542
+@ if {[info exists tkt_cage]} {
543
+@ html "<br>[htmlize $tkt_cage] ago"
544
+@ }
545
+@ </th1>
546
+@ </td></tr>
530547
@ <th1>enable_output [hascap e]</th1>
531
-@ <td class="tktDspLabel">Contact:</td><td class="tktDspValue">
548
+@ <tr>
549
+@ <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
532550
@ $<private_contact>
533551
@ </td>
552
+@ </tr>
534553
@ <th1>enable_output 1</th1>
535
-@ </tr>
536554
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
537555
@ <td colspan="3" valign="top" class="tktDspValue">
538
-@ $<foundin>
556
+@ <th1>
557
+@ set versionlink ""
558
+@ set urlfoundin [httpize $foundin]
559
+@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
560
+@ if [regexp $tagpattern $foundin] {
561
+@ query {SELECT count(*) AS match FROM tag
562
+@ WHERE tagname=concat('sym-',$foundin)} {
563
+@ if {$match} {set versionlink "timeline?t=$urlfoundin"}
564
+@ }
565
+@ }
566
+@ set hashpattern {^[0-9a-f]+$}
567
+@ if [regexp $hashpattern $foundin] {
568
+@ set pattern $foundin*
569
+@ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
570
+@ if {$match} {set versionlink "info/$urlfoundin"}
571
+@ }
572
+@ }
573
+@ if {$versionlink eq ""} {
574
+@ puts $foundin
575
+@ } else {
576
+@ html "<a href=\""
577
+@ puts $versionlink
578
+@ html "\">"
579
+@ puts $foundin
580
+@ html "</a>"
581
+@ }
582
+@ </th1>
539583
@ </td></tr>
540584
@ </table>
541585
@
542586
@ <th1>
543587
@ wiki_assoc "ticket" $tkt_uuid
@@ -567,24 +611,25 @@
567611
@ FROM ticketchng
568612
@ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
569613
@ if {$seenRow} {
570614
@ html "<hr>\n"
571615
@ } else {
572
-@ html "<tr><td class='tktDspLabel' style='text-align:left'>User Comments:</td></tr>\n"
616
+@ html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
617
+@ html "User Comments:</td></tr>\n"
573618
@ html "<tr><td colspan='5' class='tktDspValue'>\n"
574619
@ set seenRow 1
575620
@ }
576621
@ html "<span class='tktDspCommenter'>"
577
-@ html "[htmlize $xlogin]"
622
+@ puts $xlogin
578623
@ if {$xlogin ne $xusername && [string length $xusername]>0} {
579
-@ html " (claiming to be [htmlize $xusername])"
624
+@ puts " (claiming to be $xusername)"
580625
@ }
581
-@ html " added on $xdate:"
626
+@ puts " added on $xdate:"
582627
@ html "</span>\n"
583628
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
584629
@ set r [randhex]
585
-@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
630
+@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
586631
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
587632
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
588633
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
589634
@ } elseif {$xmimetype eq "text/x-markdown"} {
590635
@ html [lindex [markdown $xcomment] 1]
@@ -741,10 +786,52 @@
741786
@ <input type="submit" name="cancel" value="Cancel">
742787
@ </td>
743788
@ <td>Abandon this edit</td>
744789
@ </tr>
745790
@
791
+@ <th1>
792
+@ set seenRow 0
793
+@ set alwaysPlaintext [info exists plaintext]
794
+@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
795
+@ mimetype as xmimetype, icomment AS xcomment,
796
+@ username AS xusername
797
+@ FROM ticketchng
798
+@ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
799
+@ if {$seenRow} {
800
+@ html "<hr>\n"
801
+@ } else {
802
+@ html "<tr><td colspan='2'><hr></td></tr>\n"
803
+@ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
804
+@ html "Previous User Comments:</td></tr>\n"
805
+@ html "<tr><td colspan='2' class='tktDspValue'>\n"
806
+@ set seenRow 1
807
+@ }
808
+@ html "<span class='tktDspCommenter'>"
809
+@ puts $xlogin
810
+@ if {$xlogin ne $xusername && [string length $xusername]>0} {
811
+@ puts " (claiming to be $xusername)"
812
+@ }
813
+@ puts " added on $xdate:"
814
+@ html "</span>\n"
815
+@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
816
+@ set r [randhex]
817
+@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
818
+@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
819
+@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
820
+@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
821
+@ } elseif {$xmimetype eq "text/x-markdown"} {
822
+@ html [lindex [markdown $xcomment] 1]
823
+@ } elseif {$xmimetype eq "text/html"} {
824
+@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
825
+@ } else {
826
+@ set r [randhex]
827
+@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
828
+@ }
829
+@ }
830
+@ if {$seenRow} {html "</td></tr>\n"}
831
+@ </th1>
832
+@
746833
@ </table>
747834
;
748835
749836
/*
750837
** Return the code used to generate the edit ticket page
@@ -839,11 +926,12 @@
839926
@ WHEN status='Fixed' THEN '#cfe8bd'
840927
@ WHEN status='Tested' THEN '#bde5d6'
841928
@ WHEN status='Deferred' THEN '#cacae5'
842929
@ ELSE '#c8c8c8' END AS 'bgcolor',
843930
@ substr(tkt_uuid,1,10) AS '#',
844
-@ datetime(tkt_mtime) AS 'mtime',
931
+@ datetime(tkt_ctime) AS 'created',
932
+@ datetime(tkt_mtime) AS 'modified',
845933
@ type,
846934
@ status,
847935
@ subsystem,
848936
@ title,
849937
@ comment AS '_comments'
@@ -973,8 +1061,9 @@
9731061
@ <input type="submit" name="submit" value="Apply Changes">
9741062
@ <input type="submit" name="setup" value="Cancel">
9751063
@ </p>
9761064
@ </div></form>
9771065
db_end_transaction(0);
1066
+ style_submenu_element("Back", "%R/tktsetup");
9781067
style_finish_page();
9791068
9801069
}
9811070
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -125,11 +125,11 @@
125 if( !g.perm.Setup ){
126 login_needed(0);
127 return;
128 }
129 style_set_current_feature("tktsetup");
130 if( PB("setup") ){
131 cgi_redirect("tktsetup");
132 }
133 isSubmit = P("submit")!=0;
134 z = P("x");
135 if( z==0 ){
@@ -164,10 +164,11 @@
164 @ <hr>
165 @ <h2>Default %s(zTitle)</h2>
166 @ <blockquote><pre>
167 @ %h(zDfltValue)
168 @ </pre></blockquote>
 
169 style_finish_page();
170 }
171
172 /*
173 ** WEBPAGE: tktsetup_tab
@@ -480,11 +481,11 @@
480 @ <th1>
481 @ if {[info exists tkt_uuid]} {
482 @ html "<td class='tktDspValue' colspan='3'>"
483 @ copybtn hash-tk 0 $tkt_uuid 2
484 @ if {[hascap s]} {
485 @ html " ($tkt_id)"
486 @ }
487 @ html "</td></tr>\n"
488 @ } else {
489 @ if {[hascap s]} {
490 @ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -495,10 +496,13 @@
495 @ }
496 @
497 @ if {[capexpr {n}]} {
498 @ submenu link "Copy Ticket" /tktnew/$tkt_uuid
499 @ }
 
 
 
500 @ </th1>
501 @ <tr><td class="tktDspLabel">Title:</td>
502 @ <td class="tktDspValue" colspan="3">
503 @ $<title>
504 @ </td></tr>
@@ -521,23 +525,63 @@
521 @ $<resolution>
522 @ </td></tr>
523 @ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
524 @ <th1>
525 @ if {[info exists tkt_datetime]} {
526 @ html $tkt_datetime
 
 
 
527 @ }
528 @ </th1>
529 @ </td>
 
 
 
 
 
 
 
 
 
 
530 @ <th1>enable_output [hascap e]</th1>
531 @ <td class="tktDspLabel">Contact:</td><td class="tktDspValue">
 
532 @ $<private_contact>
533 @ </td>
 
534 @ <th1>enable_output 1</th1>
535 @ </tr>
536 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
537 @ <td colspan="3" valign="top" class="tktDspValue">
538 @ $<foundin>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539 @ </td></tr>
540 @ </table>
541 @
542 @ <th1>
543 @ wiki_assoc "ticket" $tkt_uuid
@@ -567,24 +611,25 @@
567 @ FROM ticketchng
568 @ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
569 @ if {$seenRow} {
570 @ html "<hr>\n"
571 @ } else {
572 @ html "<tr><td class='tktDspLabel' style='text-align:left'>User Comments:</td></tr>\n"
 
573 @ html "<tr><td colspan='5' class='tktDspValue'>\n"
574 @ set seenRow 1
575 @ }
576 @ html "<span class='tktDspCommenter'>"
577 @ html "[htmlize $xlogin]"
578 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
579 @ html " (claiming to be [htmlize $xusername])"
580 @ }
581 @ html " added on $xdate:"
582 @ html "</span>\n"
583 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
584 @ set r [randhex]
585 @ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
586 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
587 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
588 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
589 @ } elseif {$xmimetype eq "text/x-markdown"} {
590 @ html [lindex [markdown $xcomment] 1]
@@ -741,10 +786,52 @@
741 @ <input type="submit" name="cancel" value="Cancel">
742 @ </td>
743 @ <td>Abandon this edit</td>
744 @ </tr>
745 @
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
746 @ </table>
747 ;
748
749 /*
750 ** Return the code used to generate the edit ticket page
@@ -839,11 +926,12 @@
839 @ WHEN status='Fixed' THEN '#cfe8bd'
840 @ WHEN status='Tested' THEN '#bde5d6'
841 @ WHEN status='Deferred' THEN '#cacae5'
842 @ ELSE '#c8c8c8' END AS 'bgcolor',
843 @ substr(tkt_uuid,1,10) AS '#',
844 @ datetime(tkt_mtime) AS 'mtime',
 
845 @ type,
846 @ status,
847 @ subsystem,
848 @ title,
849 @ comment AS '_comments'
@@ -973,8 +1061,9 @@
973 @ <input type="submit" name="submit" value="Apply Changes">
974 @ <input type="submit" name="setup" value="Cancel">
975 @ </p>
976 @ </div></form>
977 db_end_transaction(0);
 
978 style_finish_page();
979
980 }
981
--- src/tktsetup.c
+++ src/tktsetup.c
@@ -125,11 +125,11 @@
125 if( !g.perm.Setup ){
126 login_needed(0);
127 return;
128 }
129 style_set_current_feature("tktsetup");
130 if( P("setup") ){
131 cgi_redirect("tktsetup");
132 }
133 isSubmit = P("submit")!=0;
134 z = P("x");
135 if( z==0 ){
@@ -164,10 +164,11 @@
164 @ <hr>
165 @ <h2>Default %s(zTitle)</h2>
166 @ <blockquote><pre>
167 @ %h(zDfltValue)
168 @ </pre></blockquote>
169 style_submenu_element("Back", "%R/tktsetup");
170 style_finish_page();
171 }
172
173 /*
174 ** WEBPAGE: tktsetup_tab
@@ -480,11 +481,11 @@
481 @ <th1>
482 @ if {[info exists tkt_uuid]} {
483 @ html "<td class='tktDspValue' colspan='3'>"
484 @ copybtn hash-tk 0 $tkt_uuid 2
485 @ if {[hascap s]} {
486 @ puts " ($tkt_id)"
487 @ }
488 @ html "</td></tr>\n"
489 @ } else {
490 @ if {[hascap s]} {
491 @ html "<td class='tktDspValue' colspan='3'>Deleted "
@@ -495,10 +496,13 @@
496 @ }
497 @
498 @ if {[capexpr {n}]} {
499 @ submenu link "Copy Ticket" /tktnew/$tkt_uuid
500 @ }
501 @ if {[capexpr {nk}]} {
502 @ submenu link "Edit Wiki" /wikiedit?name=ticket/$tkt_uuid
503 @ }
504 @ </th1>
505 @ <tr><td class="tktDspLabel">Title:</td>
506 @ <td class="tktDspValue" colspan="3">
507 @ $<title>
508 @ </td></tr>
@@ -521,23 +525,63 @@
525 @ $<resolution>
526 @ </td></tr>
527 @ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
528 @ <th1>
529 @ if {[info exists tkt_datetime]} {
530 @ puts $tkt_datetime
531 @ }
532 @ if {[info exists tkt_mage]} {
533 @ html "<br>[htmlize $tkt_mage] ago"
534 @ }
535 @ </th1>
536 @ </td>
537 @ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
538 @ <th1>
539 @ if {[info exists tkt_datetime_creation]} {
540 @ puts $tkt_datetime_creation
541 @ }
542 @ if {[info exists tkt_cage]} {
543 @ html "<br>[htmlize $tkt_cage] ago"
544 @ }
545 @ </th1>
546 @ </td></tr>
547 @ <th1>enable_output [hascap e]</th1>
548 @ <tr>
549 @ <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
550 @ $<private_contact>
551 @ </td>
552 @ </tr>
553 @ <th1>enable_output 1</th1>
 
554 @ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
555 @ <td colspan="3" valign="top" class="tktDspValue">
556 @ <th1>
557 @ set versionlink ""
558 @ set urlfoundin [httpize $foundin]
559 @ set tagpattern {^[-0-9A-Za-z_\\.]+$}
560 @ if [regexp $tagpattern $foundin] {
561 @ query {SELECT count(*) AS match FROM tag
562 @ WHERE tagname=concat('sym-',$foundin)} {
563 @ if {$match} {set versionlink "timeline?t=$urlfoundin"}
564 @ }
565 @ }
566 @ set hashpattern {^[0-9a-f]+$}
567 @ if [regexp $hashpattern $foundin] {
568 @ set pattern $foundin*
569 @ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
570 @ if {$match} {set versionlink "info/$urlfoundin"}
571 @ }
572 @ }
573 @ if {$versionlink eq ""} {
574 @ puts $foundin
575 @ } else {
576 @ html "<a href=\""
577 @ puts $versionlink
578 @ html "\">"
579 @ puts $foundin
580 @ html "</a>"
581 @ }
582 @ </th1>
583 @ </td></tr>
584 @ </table>
585 @
586 @ <th1>
587 @ wiki_assoc "ticket" $tkt_uuid
@@ -567,24 +611,25 @@
611 @ FROM ticketchng
612 @ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
613 @ if {$seenRow} {
614 @ html "<hr>\n"
615 @ } else {
616 @ html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
617 @ html "User Comments:</td></tr>\n"
618 @ html "<tr><td colspan='5' class='tktDspValue'>\n"
619 @ set seenRow 1
620 @ }
621 @ html "<span class='tktDspCommenter'>"
622 @ puts $xlogin
623 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
624 @ puts " (claiming to be $xusername)"
625 @ }
626 @ puts " added on $xdate:"
627 @ html "</span>\n"
628 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
629 @ set r [randhex]
630 @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
631 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
632 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
633 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
634 @ } elseif {$xmimetype eq "text/x-markdown"} {
635 @ html [lindex [markdown $xcomment] 1]
@@ -741,10 +786,52 @@
786 @ <input type="submit" name="cancel" value="Cancel">
787 @ </td>
788 @ <td>Abandon this edit</td>
789 @ </tr>
790 @
791 @ <th1>
792 @ set seenRow 0
793 @ set alwaysPlaintext [info exists plaintext]
794 @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
795 @ mimetype as xmimetype, icomment AS xcomment,
796 @ username AS xusername
797 @ FROM ticketchng
798 @ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
799 @ if {$seenRow} {
800 @ html "<hr>\n"
801 @ } else {
802 @ html "<tr><td colspan='2'><hr></td></tr>\n"
803 @ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
804 @ html "Previous User Comments:</td></tr>\n"
805 @ html "<tr><td colspan='2' class='tktDspValue'>\n"
806 @ set seenRow 1
807 @ }
808 @ html "<span class='tktDspCommenter'>"
809 @ puts $xlogin
810 @ if {$xlogin ne $xusername && [string length $xusername]>0} {
811 @ puts " (claiming to be $xusername)"
812 @ }
813 @ puts " added on $xdate:"
814 @ html "</span>\n"
815 @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
816 @ set r [randhex]
817 @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
818 @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
819 @ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
820 @ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
821 @ } elseif {$xmimetype eq "text/x-markdown"} {
822 @ html [lindex [markdown $xcomment] 1]
823 @ } elseif {$xmimetype eq "text/html"} {
824 @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
825 @ } else {
826 @ set r [randhex]
827 @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
828 @ }
829 @ }
830 @ if {$seenRow} {html "</td></tr>\n"}
831 @ </th1>
832 @
833 @ </table>
834 ;
835
836 /*
837 ** Return the code used to generate the edit ticket page
@@ -839,11 +926,12 @@
926 @ WHEN status='Fixed' THEN '#cfe8bd'
927 @ WHEN status='Tested' THEN '#bde5d6'
928 @ WHEN status='Deferred' THEN '#cacae5'
929 @ ELSE '#c8c8c8' END AS 'bgcolor',
930 @ substr(tkt_uuid,1,10) AS '#',
931 @ datetime(tkt_ctime) AS 'created',
932 @ datetime(tkt_mtime) AS 'modified',
933 @ type,
934 @ status,
935 @ subsystem,
936 @ title,
937 @ comment AS '_comments'
@@ -973,8 +1061,9 @@
1061 @ <input type="submit" name="submit" value="Apply Changes">
1062 @ <input type="submit" name="setup" value="Cancel">
1063 @ </p>
1064 @ </div></form>
1065 db_end_transaction(0);
1066 style_submenu_element("Back", "%R/tktsetup");
1067 style_finish_page();
1068
1069 }
1070
--- src/unversioned.c
+++ src/unversioned.c
@@ -246,10 +246,12 @@
246246
** a single file at a time.
247247
**
248248
** cat FILE ... Concatenate the content of FILEs to stdout.
249249
**
250250
** edit FILE Bring up FILE in a text editor for modification.
251
+** Options:
252
+** --editor NAME Name of the text editor to use
251253
**
252254
** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
253255
**
254256
** list | ls Show all unversioned files held in the local
255257
** repository.
@@ -361,17 +363,17 @@
361363
const char *zTFile; /* Temporary file */
362364
const char *zUVFile; /* Name of the unversioned file */
363365
char *zCmd; /* Command to run the text editor */
364366
Blob content; /* Content of the unversioned file */
365367
366
- verify_all_options();
367
- if( g.argc!=4) usage("edit UVFILE");
368
- zUVFile = g.argv[3];
369368
zEditor = fossil_text_editor();
370369
if( zEditor==0 ){
371370
fossil_fatal("no text editor - set the VISUAL env variable");
372371
}
372
+ verify_all_options();
373
+ if( g.argc!=4) usage("edit UVFILE");
374
+ zUVFile = g.argv[3];
373375
zTFile = fossil_temp_filename();
374376
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
375377
db_begin_transaction();
376378
content_rcvid_init("#!fossil unversioned edit");
377379
if( unversioned_content(zUVFile, &content)==0 ){
378380
--- src/unversioned.c
+++ src/unversioned.c
@@ -246,10 +246,12 @@
246 ** a single file at a time.
247 **
248 ** cat FILE ... Concatenate the content of FILEs to stdout.
249 **
250 ** edit FILE Bring up FILE in a text editor for modification.
 
 
251 **
252 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
253 **
254 ** list | ls Show all unversioned files held in the local
255 ** repository.
@@ -361,17 +363,17 @@
361 const char *zTFile; /* Temporary file */
362 const char *zUVFile; /* Name of the unversioned file */
363 char *zCmd; /* Command to run the text editor */
364 Blob content; /* Content of the unversioned file */
365
366 verify_all_options();
367 if( g.argc!=4) usage("edit UVFILE");
368 zUVFile = g.argv[3];
369 zEditor = fossil_text_editor();
370 if( zEditor==0 ){
371 fossil_fatal("no text editor - set the VISUAL env variable");
372 }
 
 
 
373 zTFile = fossil_temp_filename();
374 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
375 db_begin_transaction();
376 content_rcvid_init("#!fossil unversioned edit");
377 if( unversioned_content(zUVFile, &content)==0 ){
378
--- src/unversioned.c
+++ src/unversioned.c
@@ -246,10 +246,12 @@
246 ** a single file at a time.
247 **
248 ** cat FILE ... Concatenate the content of FILEs to stdout.
249 **
250 ** edit FILE Bring up FILE in a text editor for modification.
251 ** Options:
252 ** --editor NAME Name of the text editor to use
253 **
254 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
255 **
256 ** list | ls Show all unversioned files held in the local
257 ** repository.
@@ -361,17 +363,17 @@
363 const char *zTFile; /* Temporary file */
364 const char *zUVFile; /* Name of the unversioned file */
365 char *zCmd; /* Command to run the text editor */
366 Blob content; /* Content of the unversioned file */
367
 
 
 
368 zEditor = fossil_text_editor();
369 if( zEditor==0 ){
370 fossil_fatal("no text editor - set the VISUAL env variable");
371 }
372 verify_all_options();
373 if( g.argc!=4) usage("edit UVFILE");
374 zUVFile = g.argv[3];
375 zTFile = fossil_temp_filename();
376 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
377 db_begin_transaction();
378 content_rcvid_init("#!fossil unversioned edit");
379 if( unversioned_content(zUVFile, &content)==0 ){
380
+79 -41
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326326
**
327327
** > fossil user contact USERNAME ?CONTACT-INFO?
328328
**
329329
** Query or set contact information for user USERNAME
330330
**
331
-** > fossil user default ?USERNAME?
331
+** > fossil user default ?OPTIONS? ?USERNAME?
332332
**
333333
** Query or set the default user. The default user is the
334
-** user for command-line interaction.
334
+** user for command-line interaction. If USERNAME is an
335
+** empty string, then the default user is unset from the
336
+** repository and will subsequently be determined by the -U
337
+** command-line option or by environment variables
338
+** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
339
+** OPTIONS:
340
+**
341
+** -v|--verbose Show how the default user is computed
335342
**
336343
** > fossil user list | ls
337344
**
338345
** List all users known to the repository
339346
**
@@ -385,21 +392,50 @@
385392
&login, zPw, &caps, &contact
386393
);
387394
db_protect_pop();
388395
free(zPw);
389396
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
390
- if( g.argc==3 ){
391
- user_select();
392
- fossil_print("%s\n", g.zLogin);
393
- }else{
394
- if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
395
- fossil_fatal("no such user: %s", g.argv[3]);
396
- }
397
- if( g.localOpen ){
398
- db_lset("default-user", g.argv[3]);
399
- }else{
400
- db_set("default-user", g.argv[3], 0);
397
+ int eVerbose = find_option("verbose","v",0)!=0;
398
+ verify_all_options();
399
+ if( g.argc>3 ){
400
+ const char *zUser = g.argv[3];
401
+ if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
402
+ db_begin_transaction();
403
+ if( g.localOpen ){
404
+ db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
405
+ }
406
+ db_unset("default-user",0);
407
+ db_commit_transaction();
408
+ }else{
409
+ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
410
+ fossil_fatal("no such user: %s", g.argv[3]);
411
+ }
412
+ if( g.localOpen ){
413
+ db_lset("default-user", g.argv[3]);
414
+ }else{
415
+ db_set("default-user", g.argv[3], 0);
416
+ }
417
+ }
418
+ }
419
+ if( g.argc==3 || eVerbose ){
420
+ int eHow = user_select();
421
+ const char *zHow = "???";
422
+ switch( eHow ){
423
+ case 1: zHow = "-U option"; break;
424
+ case 2: zHow = "previously set"; break;
425
+ case 3: zHow = "local check-out"; break;
426
+ case 4: zHow = "repository"; break;
427
+ case 5: zHow = "FOSSIL_USER"; break;
428
+ case 6: zHow = "USER"; break;
429
+ case 7: zHow = "LOGNAME"; break;
430
+ case 8: zHow = "USERNAME"; break;
431
+ case 9: zHow = "URL"; break;
432
+ }
433
+ if( eVerbose ){
434
+ fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
435
+ }else{
436
+ fossil_print("%s\n", g.zLogin);
401437
}
402438
}
403439
}else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
404440
( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
405441
Stmt q;
@@ -496,52 +532,54 @@
496532
/*
497533
** Figure out what user is at the controls.
498534
**
499535
** (1) Use the --user and -U command-line options.
500536
**
501
-** (2) If the local database is open, check in VVAR.
502
-**
503
-** (3) Check the default user in the repository
504
-**
505
-** (4) Try the FOSSIL_USER environment variable.
506
-**
507
-** (5) Try the USER environment variable.
508
-**
509
-** (6) Try the LOGNAME environment variable.
510
-**
511
-** (7) Try the USERNAME environment variable.
512
-**
513
-** (8) Check if the user can be extracted from the remote URL.
537
+** (2) The name used for login (if there was a login).
538
+**
539
+** (3) If the local database is open, check in VVAR.
540
+**
541
+** (4) Check the default-user in the repository
542
+**
543
+** (5) Try the FOSSIL_USER environment variable.
544
+**
545
+** (6) Try the USER environment variable.
546
+**
547
+** (7) Try the LOGNAME environment variable.
548
+**
549
+** (8) Try the USERNAME environment variable.
550
+**
551
+** (9) Check if the user can be extracted from the remote URL.
514552
**
515553
** The user name is stored in g.zLogin. The uid is in g.userUid.
516554
*/
517
-void user_select(void){
555
+int user_select(void){
518556
UrlData url;
519
- if( g.userUid ) return;
557
+ if( g.userUid ) return 1;
520558
if( g.zLogin ){
521559
if( attempt_user(g.zLogin)==0 ){
522560
fossil_fatal("no such user: %s", g.zLogin);
523561
}else{
524
- return;
562
+ return 2;
525563
}
526564
}
527565
528
- if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
529
-
530
- if( attempt_user(db_get("default-user", 0)) ) return;
531
-
532
- if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;
533
-
534
- if( attempt_user(fossil_getenv("USER")) ) return;
535
-
536
- if( attempt_user(fossil_getenv("LOGNAME")) ) return;
537
-
538
- if( attempt_user(fossil_getenv("USERNAME")) ) return;
566
+ if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
567
+
568
+ if( attempt_user(db_get("default-user", 0)) ) return 4;
569
+
570
+ if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
571
+
572
+ if( attempt_user(fossil_getenv("USER")) ) return 6;
573
+
574
+ if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
575
+
576
+ if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
539577
540578
memset(&url, 0, sizeof(url));
541579
url_parse_local(0, URL_USE_CONFIG, &url);
542
- if( url.user && attempt_user(url.user) ) return;
580
+ if( url.user && attempt_user(url.user) ) return 9;
543581
544582
fossil_print(
545583
"Cannot figure out who you are! Consider using the --user\n"
546584
"command line option, setting your USER environment variable,\n"
547585
"or setting a default user with \"fossil user default USER\".\n"
548586
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326 **
327 ** > fossil user contact USERNAME ?CONTACT-INFO?
328 **
329 ** Query or set contact information for user USERNAME
330 **
331 ** > fossil user default ?USERNAME?
332 **
333 ** Query or set the default user. The default user is the
334 ** user for command-line interaction.
 
 
 
 
 
 
 
335 **
336 ** > fossil user list | ls
337 **
338 ** List all users known to the repository
339 **
@@ -385,21 +392,50 @@
385 &login, zPw, &caps, &contact
386 );
387 db_protect_pop();
388 free(zPw);
389 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
390 if( g.argc==3 ){
391 user_select();
392 fossil_print("%s\n", g.zLogin);
393 }else{
394 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
395 fossil_fatal("no such user: %s", g.argv[3]);
396 }
397 if( g.localOpen ){
398 db_lset("default-user", g.argv[3]);
399 }else{
400 db_set("default-user", g.argv[3], 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401 }
402 }
403 }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
404 ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
405 Stmt q;
@@ -496,52 +532,54 @@
496 /*
497 ** Figure out what user is at the controls.
498 **
499 ** (1) Use the --user and -U command-line options.
500 **
501 ** (2) If the local database is open, check in VVAR.
502 **
503 ** (3) Check the default user in the repository
504 **
505 ** (4) Try the FOSSIL_USER environment variable.
506 **
507 ** (5) Try the USER environment variable.
508 **
509 ** (6) Try the LOGNAME environment variable.
510 **
511 ** (7) Try the USERNAME environment variable.
512 **
513 ** (8) Check if the user can be extracted from the remote URL.
 
 
514 **
515 ** The user name is stored in g.zLogin. The uid is in g.userUid.
516 */
517 void user_select(void){
518 UrlData url;
519 if( g.userUid ) return;
520 if( g.zLogin ){
521 if( attempt_user(g.zLogin)==0 ){
522 fossil_fatal("no such user: %s", g.zLogin);
523 }else{
524 return;
525 }
526 }
527
528 if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
529
530 if( attempt_user(db_get("default-user", 0)) ) return;
531
532 if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;
533
534 if( attempt_user(fossil_getenv("USER")) ) return;
535
536 if( attempt_user(fossil_getenv("LOGNAME")) ) return;
537
538 if( attempt_user(fossil_getenv("USERNAME")) ) return;
539
540 memset(&url, 0, sizeof(url));
541 url_parse_local(0, URL_USE_CONFIG, &url);
542 if( url.user && attempt_user(url.user) ) return;
543
544 fossil_print(
545 "Cannot figure out who you are! Consider using the --user\n"
546 "command line option, setting your USER environment variable,\n"
547 "or setting a default user with \"fossil user default USER\".\n"
548
--- src/user.c
+++ src/user.c
@@ -326,14 +326,21 @@
326 **
327 ** > fossil user contact USERNAME ?CONTACT-INFO?
328 **
329 ** Query or set contact information for user USERNAME
330 **
331 ** > fossil user default ?OPTIONS? ?USERNAME?
332 **
333 ** Query or set the default user. The default user is the
334 ** user for command-line interaction. If USERNAME is an
335 ** empty string, then the default user is unset from the
336 ** repository and will subsequently be determined by the -U
337 ** command-line option or by environment variables
338 ** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
339 ** OPTIONS:
340 **
341 ** -v|--verbose Show how the default user is computed
342 **
343 ** > fossil user list | ls
344 **
345 ** List all users known to the repository
346 **
@@ -385,21 +392,50 @@
392 &login, zPw, &caps, &contact
393 );
394 db_protect_pop();
395 free(zPw);
396 }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
397 int eVerbose = find_option("verbose","v",0)!=0;
398 verify_all_options();
399 if( g.argc>3 ){
400 const char *zUser = g.argv[3];
401 if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
402 db_begin_transaction();
403 if( g.localOpen ){
404 db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
405 }
406 db_unset("default-user",0);
407 db_commit_transaction();
408 }else{
409 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
410 fossil_fatal("no such user: %s", g.argv[3]);
411 }
412 if( g.localOpen ){
413 db_lset("default-user", g.argv[3]);
414 }else{
415 db_set("default-user", g.argv[3], 0);
416 }
417 }
418 }
419 if( g.argc==3 || eVerbose ){
420 int eHow = user_select();
421 const char *zHow = "???";
422 switch( eHow ){
423 case 1: zHow = "-U option"; break;
424 case 2: zHow = "previously set"; break;
425 case 3: zHow = "local check-out"; break;
426 case 4: zHow = "repository"; break;
427 case 5: zHow = "FOSSIL_USER"; break;
428 case 6: zHow = "USER"; break;
429 case 7: zHow = "LOGNAME"; break;
430 case 8: zHow = "USERNAME"; break;
431 case 9: zHow = "URL"; break;
432 }
433 if( eVerbose ){
434 fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
435 }else{
436 fossil_print("%s\n", g.zLogin);
437 }
438 }
439 }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
440 ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
441 Stmt q;
@@ -496,52 +532,54 @@
532 /*
533 ** Figure out what user is at the controls.
534 **
535 ** (1) Use the --user and -U command-line options.
536 **
537 ** (2) The name used for login (if there was a login).
538 **
539 ** (3) If the local database is open, check in VVAR.
540 **
541 ** (4) Check the default-user in the repository
542 **
543 ** (5) Try the FOSSIL_USER environment variable.
544 **
545 ** (6) Try the USER environment variable.
546 **
547 ** (7) Try the LOGNAME environment variable.
548 **
549 ** (8) Try the USERNAME environment variable.
550 **
551 ** (9) Check if the user can be extracted from the remote URL.
552 **
553 ** The user name is stored in g.zLogin. The uid is in g.userUid.
554 */
555 int user_select(void){
556 UrlData url;
557 if( g.userUid ) return 1;
558 if( g.zLogin ){
559 if( attempt_user(g.zLogin)==0 ){
560 fossil_fatal("no such user: %s", g.zLogin);
561 }else{
562 return 2;
563 }
564 }
565
566 if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
567
568 if( attempt_user(db_get("default-user", 0)) ) return 4;
569
570 if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
571
572 if( attempt_user(fossil_getenv("USER")) ) return 6;
573
574 if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
575
576 if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
577
578 memset(&url, 0, sizeof(url));
579 url_parse_local(0, URL_USE_CONFIG, &url);
580 if( url.user && attempt_user(url.user) ) return 9;
581
582 fossil_print(
583 "Cannot figure out who you are! Consider using the --user\n"
584 "command line option, setting your USER environment variable,\n"
585 "or setting a default user with \"fossil user default USER\".\n"
586
+16 -6
--- src/util.c
+++ src/util.c
@@ -666,23 +666,33 @@
666666
/*
667667
** Return the name of the users preferred text editor. Return NULL if
668668
** not found.
669669
**
670670
** Search algorithm:
671
-** (1) The local "editor" setting
672
-** (2) The global "editor" setting
673
-** (3) The VISUAL environment variable
674
-** (4) The EDITOR environment variable
675
-** (5) Any of the following programs that are available:
671
+** (1) The value of the --editor command-line argument
672
+** (2) The local "editor" setting
673
+** (3) The global "editor" setting
674
+** (4) The VISUAL environment variable
675
+** (5) The EDITOR environment variable
676
+** (6) Any of the following programs that are available:
676677
** notepad, nano, pico, jove, edit, vi, vim, ed,
678
+**
679
+** The search only occurs once, the first time this routine is called.
680
+** Second and subsequent invocations always return the same value.
677681
*/
678682
const char *fossil_text_editor(void){
679
- const char *zEditor = db_get("editor", 0);
683
+ static const char *zEditor = 0;
680684
const char *azStdEd[] = {
681685
"notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed"
682686
};
683687
int i = 0;
688
+ if( zEditor==0 ){
689
+ zEditor = find_option("editor",0,1);
690
+ }
691
+ if( zEditor==0 ){
692
+ zEditor = db_get("editor", 0);
693
+ }
684694
if( zEditor==0 ){
685695
zEditor = fossil_getenv("VISUAL");
686696
}
687697
if( zEditor==0 ){
688698
zEditor = fossil_getenv("EDITOR");
689699
--- src/util.c
+++ src/util.c
@@ -666,23 +666,33 @@
666 /*
667 ** Return the name of the users preferred text editor. Return NULL if
668 ** not found.
669 **
670 ** Search algorithm:
671 ** (1) The local "editor" setting
672 ** (2) The global "editor" setting
673 ** (3) The VISUAL environment variable
674 ** (4) The EDITOR environment variable
675 ** (5) Any of the following programs that are available:
 
676 ** notepad, nano, pico, jove, edit, vi, vim, ed,
 
 
 
677 */
678 const char *fossil_text_editor(void){
679 const char *zEditor = db_get("editor", 0);
680 const char *azStdEd[] = {
681 "notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed"
682 };
683 int i = 0;
 
 
 
 
 
 
684 if( zEditor==0 ){
685 zEditor = fossil_getenv("VISUAL");
686 }
687 if( zEditor==0 ){
688 zEditor = fossil_getenv("EDITOR");
689
--- src/util.c
+++ src/util.c
@@ -666,23 +666,33 @@
666 /*
667 ** Return the name of the users preferred text editor. Return NULL if
668 ** not found.
669 **
670 ** Search algorithm:
671 ** (1) The value of the --editor command-line argument
672 ** (2) The local "editor" setting
673 ** (3) The global "editor" setting
674 ** (4) The VISUAL environment variable
675 ** (5) The EDITOR environment variable
676 ** (6) Any of the following programs that are available:
677 ** notepad, nano, pico, jove, edit, vi, vim, ed,
678 **
679 ** The search only occurs once, the first time this routine is called.
680 ** Second and subsequent invocations always return the same value.
681 */
682 const char *fossil_text_editor(void){
683 static const char *zEditor = 0;
684 const char *azStdEd[] = {
685 "notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed"
686 };
687 int i = 0;
688 if( zEditor==0 ){
689 zEditor = find_option("editor",0,1);
690 }
691 if( zEditor==0 ){
692 zEditor = db_get("editor", 0);
693 }
694 if( zEditor==0 ){
695 zEditor = fossil_getenv("VISUAL");
696 }
697 if( zEditor==0 ){
698 zEditor = fossil_getenv("EDITOR");
699
+34 -19
--- src/winfile.c
+++ src/winfile.c
@@ -304,39 +304,47 @@
304304
*/
305305
int win32_filenames_equal_nocase(
306306
const wchar_t *fn1,
307307
const wchar_t *fn2
308308
){
309
- static FARPROC fnCompareStringOrdinal;
310
- static FARPROC fnRtlInitUnicodeString;
311
- static FARPROC fnRtlEqualUnicodeString;
309
+ /* ---- Data types used by dynamically loaded API functions. -------------- */
310
+ typedef struct { /* UNICODE_STRING from <ntdef.h> */
311
+ USHORT Length;
312
+ USHORT MaximumLength;
313
+ PWSTR Buffer;
314
+ } MY_UNICODE_STRING;
315
+ /* ---- Prototypes for dynamically loaded API functions. ------------------ */
316
+ typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL);
317
+ typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR);
318
+ typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING)
319
+ (MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN);
320
+ /* ------------------------------------------------------------------------ */
321
+ static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal;
322
+ static FNRTLINITUNICODESTRING fnRtlInitUnicodeString;
323
+ static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString;
312324
static int loaded_CompareStringOrdinal;
313325
static int loaded_RtlUnicodeStringAPIs;
314326
if( !loaded_CompareStringOrdinal ){
315
- fnCompareStringOrdinal =
327
+ fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL)
316328
GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
317329
loaded_CompareStringOrdinal = 1;
318330
}
319331
if( fnCompareStringOrdinal ){
320332
return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0;
321333
}
322334
if( !loaded_RtlUnicodeStringAPIs ){
323
- fnRtlInitUnicodeString =
335
+ fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING)
324336
GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString");
325
- fnRtlEqualUnicodeString =
337
+ fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING)
326338
GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString");
327339
loaded_RtlUnicodeStringAPIs = 1;
328340
}
329341
if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){
330
- struct { /* UNICODE_STRING from <ntdef.h> */
331
- unsigned short Length;
332
- unsigned short MaximumLength;
333
- wchar_t *Buffer;
334
- } u1, u2;
342
+ MY_UNICODE_STRING u1, u2;
335343
fnRtlInitUnicodeString(&u1,fn1);
336344
fnRtlInitUnicodeString(&u2,fn2);
337
- return (unsigned char)fnRtlEqualUnicodeString(&u1,&u2,1);
345
+ return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1);
338346
}
339347
/* In what kind of strange parallel universe are we? */
340348
return lstrcmpiW(fn1,fn2)==0;
341349
}
342350
@@ -461,11 +469,20 @@
461469
** is allocated by mprintf(), or NULL on failure.
462470
*/
463471
char *win32_file_id(
464472
const char *zFileName
465473
){
466
- static FARPROC fnGetFileInformationByHandleEx;
474
+ /* ---- Data types used by dynamically loaded API functions. -------------- */
475
+ typedef struct { /* FILE_ID_INFO from <winbase.h> */
476
+ ULONGLONG VolumeSerialNumber;
477
+ BYTE FileId[16];
478
+ } MY_FILE_ID_INFO;
479
+ /* ---- Prototypes for dynamically loaded API functions. ------------------ */
480
+ typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX)
481
+ (HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD);
482
+ /* ------------------------------------------------------------------------ */
483
+ static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx;
467484
static int loaded_fnGetFileInformationByHandleEx;
468485
wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
469486
HANDLE hFile;
470487
char *zFileId = 0;
471488
hFile = CreateFileW(
@@ -476,17 +493,15 @@
476493
OPEN_EXISTING,
477494
FILE_FLAG_BACKUP_SEMANTICS,
478495
NULL);
479496
if( hFile!=INVALID_HANDLE_VALUE ){
480497
BY_HANDLE_FILE_INFORMATION fi;
481
- struct { /* FILE_ID_INFO from <winbase.h> */
482
- u64 VolumeSerialNumber;
483
- unsigned char FileId[16];
484
- } fi2;
498
+ MY_FILE_ID_INFO fi2;
485499
if( !loaded_fnGetFileInformationByHandleEx ){
486
- fnGetFileInformationByHandleEx = GetProcAddress(
487
- GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
500
+ fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX)
501
+ GetProcAddress(
502
+ GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
488503
loaded_fnGetFileInformationByHandleEx = 1;
489504
}
490505
if( fnGetFileInformationByHandleEx ){
491506
if( fnGetFileInformationByHandleEx(
492507
hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
493508
--- src/winfile.c
+++ src/winfile.c
@@ -304,39 +304,47 @@
304 */
305 int win32_filenames_equal_nocase(
306 const wchar_t *fn1,
307 const wchar_t *fn2
308 ){
309 static FARPROC fnCompareStringOrdinal;
310 static FARPROC fnRtlInitUnicodeString;
311 static FARPROC fnRtlEqualUnicodeString;
 
 
 
 
 
 
 
 
 
 
 
 
312 static int loaded_CompareStringOrdinal;
313 static int loaded_RtlUnicodeStringAPIs;
314 if( !loaded_CompareStringOrdinal ){
315 fnCompareStringOrdinal =
316 GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
317 loaded_CompareStringOrdinal = 1;
318 }
319 if( fnCompareStringOrdinal ){
320 return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0;
321 }
322 if( !loaded_RtlUnicodeStringAPIs ){
323 fnRtlInitUnicodeString =
324 GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString");
325 fnRtlEqualUnicodeString =
326 GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString");
327 loaded_RtlUnicodeStringAPIs = 1;
328 }
329 if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){
330 struct { /* UNICODE_STRING from <ntdef.h> */
331 unsigned short Length;
332 unsigned short MaximumLength;
333 wchar_t *Buffer;
334 } u1, u2;
335 fnRtlInitUnicodeString(&u1,fn1);
336 fnRtlInitUnicodeString(&u2,fn2);
337 return (unsigned char)fnRtlEqualUnicodeString(&u1,&u2,1);
338 }
339 /* In what kind of strange parallel universe are we? */
340 return lstrcmpiW(fn1,fn2)==0;
341 }
342
@@ -461,11 +469,20 @@
461 ** is allocated by mprintf(), or NULL on failure.
462 */
463 char *win32_file_id(
464 const char *zFileName
465 ){
466 static FARPROC fnGetFileInformationByHandleEx;
 
 
 
 
 
 
 
 
 
467 static int loaded_fnGetFileInformationByHandleEx;
468 wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
469 HANDLE hFile;
470 char *zFileId = 0;
471 hFile = CreateFileW(
@@ -476,17 +493,15 @@
476 OPEN_EXISTING,
477 FILE_FLAG_BACKUP_SEMANTICS,
478 NULL);
479 if( hFile!=INVALID_HANDLE_VALUE ){
480 BY_HANDLE_FILE_INFORMATION fi;
481 struct { /* FILE_ID_INFO from <winbase.h> */
482 u64 VolumeSerialNumber;
483 unsigned char FileId[16];
484 } fi2;
485 if( !loaded_fnGetFileInformationByHandleEx ){
486 fnGetFileInformationByHandleEx = GetProcAddress(
487 GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
 
488 loaded_fnGetFileInformationByHandleEx = 1;
489 }
490 if( fnGetFileInformationByHandleEx ){
491 if( fnGetFileInformationByHandleEx(
492 hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
493
--- src/winfile.c
+++ src/winfile.c
@@ -304,39 +304,47 @@
304 */
305 int win32_filenames_equal_nocase(
306 const wchar_t *fn1,
307 const wchar_t *fn2
308 ){
309 /* ---- Data types used by dynamically loaded API functions. -------------- */
310 typedef struct { /* UNICODE_STRING from <ntdef.h> */
311 USHORT Length;
312 USHORT MaximumLength;
313 PWSTR Buffer;
314 } MY_UNICODE_STRING;
315 /* ---- Prototypes for dynamically loaded API functions. ------------------ */
316 typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL);
317 typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR);
318 typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING)
319 (MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN);
320 /* ------------------------------------------------------------------------ */
321 static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal;
322 static FNRTLINITUNICODESTRING fnRtlInitUnicodeString;
323 static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString;
324 static int loaded_CompareStringOrdinal;
325 static int loaded_RtlUnicodeStringAPIs;
326 if( !loaded_CompareStringOrdinal ){
327 fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL)
328 GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
329 loaded_CompareStringOrdinal = 1;
330 }
331 if( fnCompareStringOrdinal ){
332 return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0;
333 }
334 if( !loaded_RtlUnicodeStringAPIs ){
335 fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING)
336 GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString");
337 fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING)
338 GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString");
339 loaded_RtlUnicodeStringAPIs = 1;
340 }
341 if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){
342 MY_UNICODE_STRING u1, u2;
 
 
 
 
343 fnRtlInitUnicodeString(&u1,fn1);
344 fnRtlInitUnicodeString(&u2,fn2);
345 return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1);
346 }
347 /* In what kind of strange parallel universe are we? */
348 return lstrcmpiW(fn1,fn2)==0;
349 }
350
@@ -461,11 +469,20 @@
469 ** is allocated by mprintf(), or NULL on failure.
470 */
471 char *win32_file_id(
472 const char *zFileName
473 ){
474 /* ---- Data types used by dynamically loaded API functions. -------------- */
475 typedef struct { /* FILE_ID_INFO from <winbase.h> */
476 ULONGLONG VolumeSerialNumber;
477 BYTE FileId[16];
478 } MY_FILE_ID_INFO;
479 /* ---- Prototypes for dynamically loaded API functions. ------------------ */
480 typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX)
481 (HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD);
482 /* ------------------------------------------------------------------------ */
483 static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx;
484 static int loaded_fnGetFileInformationByHandleEx;
485 wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
486 HANDLE hFile;
487 char *zFileId = 0;
488 hFile = CreateFileW(
@@ -476,17 +493,15 @@
493 OPEN_EXISTING,
494 FILE_FLAG_BACKUP_SEMANTICS,
495 NULL);
496 if( hFile!=INVALID_HANDLE_VALUE ){
497 BY_HANDLE_FILE_INFORMATION fi;
498 MY_FILE_ID_INFO fi2;
 
 
 
499 if( !loaded_fnGetFileInformationByHandleEx ){
500 fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX)
501 GetProcAddress(
502 GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
503 loaded_fnGetFileInformationByHandleEx = 1;
504 }
505 if( fnGetFileInformationByHandleEx ){
506 if( fnGetFileInformationByHandleEx(
507 hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
508
+20 -8
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
11161116
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
11171117
zName, mtime, zHash, sz);
11181118
}
11191119
db_finalize(&uvq);
11201120
}
1121
+
1122
+/*
1123
+** Return a string that contains supplemental information about a
1124
+** "not authorized" error. The string might be empty if no additional
1125
+** information is available.
1126
+*/
1127
+static char *whyNotAuth(void){
1128
+ if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
1129
+ return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
1130
+ }
1131
+ return "";
1132
+}
11211133
11221134
/*
11231135
** Called when there is an attempt to transfer private content to and
11241136
** from a server without authorization.
11251137
*/
11261138
static void server_private_xfer_not_authorized(void){
1127
- @ error not\sauthorized\sto\ssync\sprivate\scontent
1139
+ @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
11281140
}
11291141
11301142
/*
11311143
** Return the common TH1 code to evaluate prior to evaluating any other
11321144
** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
13161328
** Server accepts a file from the client.
13171329
*/
13181330
if( blob_eq(&xfer.aToken[0], "file") ){
13191331
if( !isPush ){
13201332
cgi_reset_content();
1321
- @ error not\sauthorized\sto\swrite
1333
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
13221334
nErr++;
13231335
break;
13241336
}
13251337
xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
13261338
if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
13371349
** Server accepts a compressed file from the client.
13381350
*/
13391351
if( blob_eq(&xfer.aToken[0], "cfile") ){
13401352
if( !isPush ){
13411353
cgi_reset_content();
1342
- @ error not\sauthorized\sto\swrite
1354
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
13431355
nErr++;
13441356
break;
13451357
}
13461358
xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
13471359
if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
14611473
}
14621474
login_check_credentials();
14631475
if( blob_eq(&xfer.aToken[0], "pull") ){
14641476
if( !g.perm.Read ){
14651477
cgi_reset_content();
1466
- @ error not\sauthorized\sto\sread
1478
+ @ error not\sauthorized\sto\sread%s(whyNotAuth())
14671479
nErr++;
14681480
break;
14691481
}
14701482
isPull = 1;
14711483
}else{
14721484
if( !g.perm.Write ){
14731485
if( !isPull ){
14741486
cgi_reset_content();
1475
- @ error not\sauthorized\sto\swrite
1487
+ @ error not\sauthorized\sto\swrite%s(whyNotAuth())
14761488
nErr++;
14771489
}else{
1478
- @ message pull\sonly\s-\snot\sauthorized\sto\spush
1490
+ @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
14791491
}
14801492
}else{
14811493
isPush = 1;
14821494
}
14831495
}
@@ -1491,11 +1503,11 @@
14911503
int iVers;
14921504
login_check_credentials();
14931505
if( !g.perm.Clone ){
14941506
cgi_reset_content();
14951507
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1496
- @ error not\sauthorized\sto\sclone
1508
+ @ error not\sauthorized\sto\sclone%s(whyNotAuth())
14971509
nErr++;
14981510
break;
14991511
}
15001512
if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
15011513
@ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
15921604
}
15931605
blob_zero(&content);
15941606
blob_extract(xfer.pIn, size, &content);
15951607
if( !g.perm.Admin ){
15961608
cgi_reset_content();
1597
- @ error not\sauthorized\sto\spush\sconfiguration
1609
+ @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
15981610
nErr++;
15991611
break;
16001612
}
16011613
configure_receive(zName, &content, CONFIGSET_ALL);
16021614
blob_reset(&content);
16031615
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
1116 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
1117 zName, mtime, zHash, sz);
1118 }
1119 db_finalize(&uvq);
1120 }
 
 
 
 
 
 
 
 
 
 
 
 
1121
1122 /*
1123 ** Called when there is an attempt to transfer private content to and
1124 ** from a server without authorization.
1125 */
1126 static void server_private_xfer_not_authorized(void){
1127 @ error not\sauthorized\sto\ssync\sprivate\scontent
1128 }
1129
1130 /*
1131 ** Return the common TH1 code to evaluate prior to evaluating any other
1132 ** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
1316 ** Server accepts a file from the client.
1317 */
1318 if( blob_eq(&xfer.aToken[0], "file") ){
1319 if( !isPush ){
1320 cgi_reset_content();
1321 @ error not\sauthorized\sto\swrite
1322 nErr++;
1323 break;
1324 }
1325 xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
1326 if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
1337 ** Server accepts a compressed file from the client.
1338 */
1339 if( blob_eq(&xfer.aToken[0], "cfile") ){
1340 if( !isPush ){
1341 cgi_reset_content();
1342 @ error not\sauthorized\sto\swrite
1343 nErr++;
1344 break;
1345 }
1346 xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
1347 if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
1461 }
1462 login_check_credentials();
1463 if( blob_eq(&xfer.aToken[0], "pull") ){
1464 if( !g.perm.Read ){
1465 cgi_reset_content();
1466 @ error not\sauthorized\sto\sread
1467 nErr++;
1468 break;
1469 }
1470 isPull = 1;
1471 }else{
1472 if( !g.perm.Write ){
1473 if( !isPull ){
1474 cgi_reset_content();
1475 @ error not\sauthorized\sto\swrite
1476 nErr++;
1477 }else{
1478 @ message pull\sonly\s-\snot\sauthorized\sto\spush
1479 }
1480 }else{
1481 isPush = 1;
1482 }
1483 }
@@ -1491,11 +1503,11 @@
1491 int iVers;
1492 login_check_credentials();
1493 if( !g.perm.Clone ){
1494 cgi_reset_content();
1495 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1496 @ error not\sauthorized\sto\sclone
1497 nErr++;
1498 break;
1499 }
1500 if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
1501 @ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
1592 }
1593 blob_zero(&content);
1594 blob_extract(xfer.pIn, size, &content);
1595 if( !g.perm.Admin ){
1596 cgi_reset_content();
1597 @ error not\sauthorized\sto\spush\sconfiguration
1598 nErr++;
1599 break;
1600 }
1601 configure_receive(zName, &content, CONFIGSET_ALL);
1602 blob_reset(&content);
1603
--- src/xfer.c
+++ src/xfer.c
@@ -1116,17 +1116,29 @@
1116 blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
1117 zName, mtime, zHash, sz);
1118 }
1119 db_finalize(&uvq);
1120 }
1121
1122 /*
1123 ** Return a string that contains supplemental information about a
1124 ** "not authorized" error. The string might be empty if no additional
1125 ** information is available.
1126 */
1127 static char *whyNotAuth(void){
1128 if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
1129 return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
1130 }
1131 return "";
1132 }
1133
1134 /*
1135 ** Called when there is an attempt to transfer private content to and
1136 ** from a server without authorization.
1137 */
1138 static void server_private_xfer_not_authorized(void){
1139 @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
1140 }
1141
1142 /*
1143 ** Return the common TH1 code to evaluate prior to evaluating any other
1144 ** TH1 transfer notification scripts.
@@ -1316,11 +1328,11 @@
1328 ** Server accepts a file from the client.
1329 */
1330 if( blob_eq(&xfer.aToken[0], "file") ){
1331 if( !isPush ){
1332 cgi_reset_content();
1333 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1334 nErr++;
1335 break;
1336 }
1337 xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
1338 if( blob_size(&xfer.err) ){
@@ -1337,11 +1349,11 @@
1349 ** Server accepts a compressed file from the client.
1350 */
1351 if( blob_eq(&xfer.aToken[0], "cfile") ){
1352 if( !isPush ){
1353 cgi_reset_content();
1354 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1355 nErr++;
1356 break;
1357 }
1358 xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
1359 if( blob_size(&xfer.err) ){
@@ -1461,23 +1473,23 @@
1473 }
1474 login_check_credentials();
1475 if( blob_eq(&xfer.aToken[0], "pull") ){
1476 if( !g.perm.Read ){
1477 cgi_reset_content();
1478 @ error not\sauthorized\sto\sread%s(whyNotAuth())
1479 nErr++;
1480 break;
1481 }
1482 isPull = 1;
1483 }else{
1484 if( !g.perm.Write ){
1485 if( !isPull ){
1486 cgi_reset_content();
1487 @ error not\sauthorized\sto\swrite%s(whyNotAuth())
1488 nErr++;
1489 }else{
1490 @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
1491 }
1492 }else{
1493 isPush = 1;
1494 }
1495 }
@@ -1491,11 +1503,11 @@
1503 int iVers;
1504 login_check_credentials();
1505 if( !g.perm.Clone ){
1506 cgi_reset_content();
1507 @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
1508 @ error not\sauthorized\sto\sclone%s(whyNotAuth())
1509 nErr++;
1510 break;
1511 }
1512 if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
1513 @ pragma uv-pull-only
@@ -1592,11 +1604,11 @@
1604 }
1605 blob_zero(&content);
1606 blob_extract(xfer.pIn, size, &content);
1607 if( !g.perm.Admin ){
1608 cgi_reset_content();
1609 @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
1610 nErr++;
1611 break;
1612 }
1613 configure_receive(zName, &content, CONFIGSET_ALL);
1614 blob_reset(&content);
1615
+3
--- src/zip.c
+++ src/zip.c
@@ -927,10 +927,13 @@
927927
login_check_credentials();
928928
if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
929929
if( fossil_strcmp(g.zPath, "sqlar")==0 ){
930930
eType = ARCHIVE_SQLAR;
931931
zType = "SQL";
932
+ /* For some reason, SQL-archives are like catnip for robots. So
933
+ ** don't allow them to be downloaded by user "nobody" */
934
+ if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; }
932935
}else{
933936
eType = ARCHIVE_ZIP;
934937
zType = "ZIP";
935938
}
936939
fossil_nice_default();
937940
--- src/zip.c
+++ src/zip.c
@@ -927,10 +927,13 @@
927 login_check_credentials();
928 if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
929 if( fossil_strcmp(g.zPath, "sqlar")==0 ){
930 eType = ARCHIVE_SQLAR;
931 zType = "SQL";
 
 
 
932 }else{
933 eType = ARCHIVE_ZIP;
934 zType = "ZIP";
935 }
936 fossil_nice_default();
937
--- src/zip.c
+++ src/zip.c
@@ -927,10 +927,13 @@
927 login_check_credentials();
928 if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
929 if( fossil_strcmp(g.zPath, "sqlar")==0 ){
930 eType = ARCHIVE_SQLAR;
931 zType = "SQL";
932 /* For some reason, SQL-archives are like catnip for robots. So
933 ** don't allow them to be downloaded by user "nobody" */
934 if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; }
935 }else{
936 eType = ARCHIVE_ZIP;
937 zType = "ZIP";
938 }
939 fossil_nice_default();
940
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356356
mtime-changes \
357357
mv-rm-files \
358358
pgp-command \
359359
preferred-diff-type \
360360
proxy \
361
+ raw-bgcolor \
361362
redirect-to-https \
362363
relative-paths \
363364
repo-cksum \
364365
repolist-skin \
365366
robot-restrict \
@@ -373,13 +374,19 @@
373374
ssl-identity \
374375
tclsh \
375376
th1-setup \
376377
th1-uri-regexp \
377378
ticket-default-report \
379
+ timeline-hard-newlines \
380
+ timeline-plaintext \
381
+ timeline-truncate-at-blank \
382
+ timeline-tslink-info \
378383
timeline-utc \
379384
user-color-map \
385
+ verify-comments \
380386
uv-sync \
387
+ vuln-report \
381388
web-browser]
382389
383390
fossil test-th-eval "hasfeature legacyMvRm"
384391
385392
if {[normalize_result] eq "1"} {
386393
387394
ADDED test/th1-taint.test
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356 mtime-changes \
357 mv-rm-files \
358 pgp-command \
359 preferred-diff-type \
360 proxy \
 
361 redirect-to-https \
362 relative-paths \
363 repo-cksum \
364 repolist-skin \
365 robot-restrict \
@@ -373,13 +374,19 @@
373 ssl-identity \
374 tclsh \
375 th1-setup \
376 th1-uri-regexp \
377 ticket-default-report \
 
 
 
 
378 timeline-utc \
379 user-color-map \
 
380 uv-sync \
 
381 web-browser]
382
383 fossil test-th-eval "hasfeature legacyMvRm"
384
385 if {[normalize_result] eq "1"} {
386
387 DDED test/th1-taint.test
--- test/tester.tcl
+++ test/tester.tcl
@@ -356,10 +356,11 @@
356 mtime-changes \
357 mv-rm-files \
358 pgp-command \
359 preferred-diff-type \
360 proxy \
361 raw-bgcolor \
362 redirect-to-https \
363 relative-paths \
364 repo-cksum \
365 repolist-skin \
366 robot-restrict \
@@ -373,13 +374,19 @@
374 ssl-identity \
375 tclsh \
376 th1-setup \
377 th1-uri-regexp \
378 ticket-default-report \
379 timeline-hard-newlines \
380 timeline-plaintext \
381 timeline-truncate-at-blank \
382 timeline-tslink-info \
383 timeline-utc \
384 user-color-map \
385 verify-comments \
386 uv-sync \
387 vuln-report \
388 web-browser]
389
390 fossil test-th-eval "hasfeature legacyMvRm"
391
392 if {[normalize_result] eq "1"} {
393
394 DDED test/th1-taint.test
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
1
+#
2
+# Copyright (c) 2025 D. Richard Hipp
3
+#
4
+# This program is free software; you can redistribute it and/or
5
+# modify it under the terms of the Simplified BSD License (also
6
+# known as the "2-Clause License" or "FreeBSD License".)
7
+#
8
+# This program is distributed in the hope that it will be useful,
9
+# but without any warranty; without even the implied warranty of
10
+# merchantability or fitness for a particular purpose.
11
+#
12
+# Author contact information:
13
+# [email protected]
14
+# http://www.hwaci.com/drh/
15
+#
16
+############################################################################
17
+#
18
+# TH1 Commands
19
+#
20
+
21
+set path [file dirname [info script]]; test_setup
22
+
23
+proc taint-test {testnum th1script expected} {
24
+ global fossilexe
25
+ set rc [catch {exec $fossilexe test-th-eval $th1script} got]
26
+ if {$rc} {
27
+ test th1-taint-$testnum 0
28
+ puts $got
29
+ return
30
+ }
31
+ if {$got ne $expected} {
32
+ test th1-taint-$testnum 0
33
+ puts " Expected: $expected"
34
+ puts " Got: $got"
35
+ } else {
36
+ test th1-taint-$testnum 1
37
+ }
38
+}
39
+
40
+taint-test 10 {string is tainted abcd} 0
41
+taint-test 20 {string is tainted [taint abcd]} 1
42
+taint-test 30 {string is tainted [untaint [taint abcd]]} 0
43
+taint-test 40 {string is tainted [untaint abcde]} 0
44
+taint-test 50 {string is tainted "abc[taint def]ghi"} 1
45
+taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1
46
+
47
+taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
48
+taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1
49
+
50
+taint-test 200 {string is tainted [list abc def ghi]} 0
51
+taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
52
+taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
53
+taint-test 230 {string is tainted [list abc def [taint ghi]]} 1
54
+
55
+taint-test 300 {
56
+ set res {}
57
+ foreach x [list abc [taint def] ghi] {
58
+ lappend res [string is tainted $x]
59
+ }
60
+ set res
61
+} {1 1 1}
62
+taint-test 310 {
63
+ set res {}
64
+ foreach {x y} [list abc [taint def] ghi jkl] {
65
+ lappend res [string is tainted $x] [string is tainted $y]
66
+ }
67
+ set res
68
+} {1 1 1 1}
69
+
70
+taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
71
+taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
72
+taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
73
+taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0
74
+
75
+taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
76
+
77
+taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1
78
+
79
+taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1
80
+taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1
81
+taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1
82
+
83
+
84
+test_cleanup
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/th1-taint.test
+++ b/test/th1-taint.test
@@ -0,0 +1,84 @@
1 #
2 # Copyright (c) 2025 D. Richard Hipp
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the Simplified BSD License (also
6 # known as the "2-Clause License" or "FreeBSD License".)
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but without any warranty; without even the implied warranty of
10 # merchantability or fitness for a particular purpose.
11 #
12 # Author contact information:
13 # [email protected]
14 # http://www.hwaci.com/drh/
15 #
16 ############################################################################
17 #
18 # TH1 Commands
19 #
20
21 set path [file dirname [info script]]; test_setup
22
23 proc taint-test {testnum th1script expected} {
24 global fossilexe
25 set rc [catch {exec $fossilexe test-th-eval $th1script} got]
26 if {$rc} {
27 test th1-taint-$testnum 0
28 puts $got
29 return
30 }
31 if {$got ne $expected} {
32 test th1-taint-$testnum 0
33 puts " Expected: $expected"
34 puts " Got: $got"
35 } else {
36 test th1-taint-$testnum 1
37 }
38 }
39
40 taint-test 10 {string is tainted abcd} 0
41 taint-test 20 {string is tainted [taint abcd]} 1
42 taint-test 30 {string is tainted [untaint [taint abcd]]} 0
43 taint-test 40 {string is tainted [untaint abcde]} 0
44 taint-test 50 {string is tainted "abc[taint def]ghi"} 1
45 taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1
46
47 taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
48 taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1
49
50 taint-test 200 {string is tainted [list abc def ghi]} 0
51 taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
52 taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
53 taint-test 230 {string is tainted [list abc def [taint ghi]]} 1
54
55 taint-test 300 {
56 set res {}
57 foreach x [list abc [taint def] ghi] {
58 lappend res [string is tainted $x]
59 }
60 set res
61 } {1 1 1}
62 taint-test 310 {
63 set res {}
64 foreach {x y} [list abc [taint def] ghi jkl] {
65 lappend res [string is tainted $x] [string is tainted $y]
66 }
67 set res
68 } {1 1 1 1}
69
70 taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
71 taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
72 taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
73 taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0
74
75 taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
76
77 taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1
78
79 taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1
80 taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1
81 taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1
82
83
84 test_cleanup
+36 -36
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795795
rpage-\$requested_page\
796796
cpage-\$canonical_page\">" [normalize_result]]}
797797
798798
###############################################################################
799799
800
-fossil test-th-eval "styleHeader {Page Title Here}"
801
-test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
800
+#fossil test-th-eval "styleHeader {Page Title Here}"
801
+#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802802
803803
###############################################################################
804804
805805
test_in_checkout th1-header-2 {
806806
fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807807
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808808
809809
###############################################################################
810810
811
-fossil test-th-eval "styleFooter"
812
-test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
811
+#fossil test-th-eval "styleFooter"
812
+#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813813
814814
###############################################################################
815815
816816
fossil test-th-eval --open-config "styleFooter"
817817
test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879879
test th1-artifact-1 {$RESULT eq \
880880
{TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881881
882882
###############################################################################
883883
884
-fossil test-th-eval "artifact tip"
885
-test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
884
+#fossil test-th-eval "artifact tip"
885
+#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886886
887887
###############################################################################
888888
889889
test_in_checkout th1-artifact-3 {
890890
fossil test-th-eval --open-config "artifact tip"
891891
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892892
893893
###############################################################################
894894
895
-fossil test-th-eval "artifact 0000000000"
896
-test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
895
+#fossil test-th-eval "artifact 0000000000"
896
+#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897897
898898
###############################################################################
899899
900900
fossil test-th-eval --open-config "artifact 0000000000"
901901
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902902
903903
###############################################################################
904904
905
-fossil test-th-eval "artifact tip test/th1.test"
906
-test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
905
+#fossil test-th-eval "artifact tip test/th1.test"
906
+#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907907
908908
###############################################################################
909909
910910
test_in_checkout th1-artifact-7 {
911911
fossil test-th-eval --open-config "artifact tip test/th1.test"
912912
} {[regexp -- {th1-artifact-7} $RESULT]}
913913
914914
###############################################################################
915915
916
-fossil test-th-eval "artifact 0000000000 test/th1.test"
917
-test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
916
+#fossil test-th-eval "artifact 0000000000 test/th1.test"
917
+#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918918
919919
###############################################################################
920920
921921
fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922922
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947947
}
948948
}
949949
950950
###############################################################################
951951
952
-fossil test-th-eval "globalState configuration"
953
-test th1-globalState-3 {[string length $RESULT] == 0}
952
+#fossil test-th-eval "globalState configuration"
953
+#test th1-globalState-3 {[string length $RESULT] == 0}
954954
955955
###############################################################################
956956
957957
fossil test-th-eval --open-config "globalState configuration"
958958
test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
10411041
fossil test-th-eval "globalState flags"
10421042
test th1-globalState-16 {$RESULT eq "0"}
10431043
10441044
###############################################################################
10451045
1046
-fossil test-th-eval "reinitialize; globalState configuration"
1047
-test th1-reinitialize-1 {$RESULT eq ""}
1046
+#fossil test-th-eval "reinitialize; globalState configuration"
1047
+#test th1-reinitialize-1 {$RESULT eq ""}
10481048
10491049
###############################################################################
10501050
10511051
fossil test-th-eval "reinitialize 1; globalState configuration"
10521052
test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
10561056
#
10571057
# NOTE: This test will fail if the command names are added to TH1, or
10581058
# moved from Tcl builds to plain or the reverse. Sorting the
10591059
# command lists eliminates a dependence on order.
10601060
#
1061
-fossil test-th-eval "info commands"
1062
-set sorted_result [lsort $RESULT]
1063
-protOut "Sorted: $sorted_result"
1064
-set base_commands {anoncap anycap array artifact break breakpoint \
1065
- builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
- combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067
- enable_output encode64 error expr for foreach getParameter glob_match \
1068
- globalState hascap hasfeature html htmlize http httpize if info \
1069
- insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
- proc puts query randhex redirect regexp reinitialize rename render \
1071
- repository return searchable set setParameter setting stime string \
1072
- styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
- unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
-set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
-if {$th1Tcl} {
1076
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
-} else {
1078
- test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
-}
1061
+#fossil test-th-eval "info commands"
1062
+#set sorted_result [lsort $RESULT]
1063
+#protOut "Sorted: $sorted_result"
1064
+#set base_commands {anoncap anycap array artifact break breakpoint \
1065
+# builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066
+# combobox continue copybtn date decorate defHeader dir \
1067
+# enable_output encode64 error expr for foreach getParameter glob_match \
1068
+# globalState hascap hasfeature html htmlize http httpize if info \
1069
+# insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070
+# proc puts query randhex redirect regexp reinitialize rename render \
1071
+# repository return searchable set setParameter setting stime string \
1072
+# styleFooter styleHeader styleScript submenu tclReady trace unset \
1073
+# unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074
+#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075
+#if {$th1Tcl} {
1076
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077
+#} else {
1078
+# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079
+#}
10801080
10811081
###############################################################################
10821082
10831083
fossil test-th-eval "info vars"
10841084
@@ -1326,11 +1326,11 @@
13261326
13271327
###############################################################################
13281328
13291329
fossil test-th-eval {string is other 123}
13301330
test th1-string-is-4 {$RESULT eq \
1331
-"TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1331
+"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
13321332
13331333
###############################################################################
13341334
13351335
fossil test-th-eval {string is alnum 123}
13361336
test th1-string-is-5 {$RESULT eq "1"}
13371337
13381338
ADDED tools/fake-smtpd.tcl
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 fossil test-th-eval "styleHeader {Page Title Here}"
801 test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 fossil test-th-eval "styleFooter"
812 test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 fossil test-th-eval "artifact tip"
885 test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 fossil test-th-eval "artifact 0000000000"
896 test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 fossil test-th-eval "artifact tip test/th1.test"
906 test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 fossil test-th-eval "artifact 0000000000 test/th1.test"
917 test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 fossil test-th-eval "globalState configuration"
953 test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 fossil test-th-eval "reinitialize; globalState configuration"
1047 test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 fossil test-th-eval "info commands"
1062 set sorted_result [lsort $RESULT]
1063 protOut "Sorted: $sorted_result"
1064 set base_commands {anoncap anycap array artifact break breakpoint \
1065 builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 combobox continue copybtn date decorate defHeader dir enable_htmlify \
1067 enable_output encode64 error expr for foreach getParameter glob_match \
1068 globalState hascap hasfeature html htmlize http httpize if info \
1069 insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 proc puts query randhex redirect regexp reinitialize rename render \
1071 repository return searchable set setParameter setting stime string \
1072 styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 if {$th1Tcl} {
1076 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 } else {
1078 test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 }
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, or list, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
1338 DDED tools/fake-smtpd.tcl
--- test/th1.test
+++ test/th1.test
@@ -795,23 +795,23 @@
795 rpage-\$requested_page\
796 cpage-\$canonical_page\">" [normalize_result]]}
797
798 ###############################################################################
799
800 #fossil test-th-eval "styleHeader {Page Title Here}"
801 #test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
802
803 ###############################################################################
804
805 test_in_checkout th1-header-2 {
806 fossil test-th-eval --open-config "styleHeader {Page Title Here}"
807 } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
808
809 ###############################################################################
810
811 #fossil test-th-eval "styleFooter"
812 #test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
813
814 ###############################################################################
815
816 fossil test-th-eval --open-config "styleFooter"
817 test th1-footer-2 {$RESULT eq {}}
@@ -879,44 +879,44 @@
879 test th1-artifact-1 {$RESULT eq \
880 {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
881
882 ###############################################################################
883
884 #fossil test-th-eval "artifact tip"
885 #test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
886
887 ###############################################################################
888
889 test_in_checkout th1-artifact-3 {
890 fossil test-th-eval --open-config "artifact tip"
891 } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
892
893 ###############################################################################
894
895 #fossil test-th-eval "artifact 0000000000"
896 #test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
897
898 ###############################################################################
899
900 fossil test-th-eval --open-config "artifact 0000000000"
901 test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
902
903 ###############################################################################
904
905 #fossil test-th-eval "artifact tip test/th1.test"
906 #test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
907
908 ###############################################################################
909
910 test_in_checkout th1-artifact-7 {
911 fossil test-th-eval --open-config "artifact tip test/th1.test"
912 } {[regexp -- {th1-artifact-7} $RESULT]}
913
914 ###############################################################################
915
916 #fossil test-th-eval "artifact 0000000000 test/th1.test"
917 #test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
918
919 ###############################################################################
920
921 fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
922 test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
@@ -947,12 +947,12 @@
947 }
948 }
949
950 ###############################################################################
951
952 #fossil test-th-eval "globalState configuration"
953 #test th1-globalState-3 {[string length $RESULT] == 0}
954
955 ###############################################################################
956
957 fossil test-th-eval --open-config "globalState configuration"
958 test th1-globalState-4 {[string length $RESULT] > 0}
@@ -1041,12 +1041,12 @@
1041 fossil test-th-eval "globalState flags"
1042 test th1-globalState-16 {$RESULT eq "0"}
1043
1044 ###############################################################################
1045
1046 #fossil test-th-eval "reinitialize; globalState configuration"
1047 #test th1-reinitialize-1 {$RESULT eq ""}
1048
1049 ###############################################################################
1050
1051 fossil test-th-eval "reinitialize 1; globalState configuration"
1052 test th1-reinitialize-2 {$RESULT ne ""}
@@ -1056,29 +1056,29 @@
1056 #
1057 # NOTE: This test will fail if the command names are added to TH1, or
1058 # moved from Tcl builds to plain or the reverse. Sorting the
1059 # command lists eliminates a dependence on order.
1060 #
1061 #fossil test-th-eval "info commands"
1062 #set sorted_result [lsort $RESULT]
1063 #protOut "Sorted: $sorted_result"
1064 #set base_commands {anoncap anycap array artifact break breakpoint \
1065 # builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
1066 # combobox continue copybtn date decorate defHeader dir \
1067 # enable_output encode64 error expr for foreach getParameter glob_match \
1068 # globalState hascap hasfeature html htmlize http httpize if info \
1069 # insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
1070 # proc puts query randhex redirect regexp reinitialize rename render \
1071 # repository return searchable set setParameter setting stime string \
1072 # styleFooter styleHeader styleScript submenu tclReady trace unset \
1073 # unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
1074 #set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
1075 #if {$th1Tcl} {
1076 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
1077 #} else {
1078 # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
1079 #}
1080
1081 ###############################################################################
1082
1083 fossil test-th-eval "info vars"
1084
@@ -1326,11 +1326,11 @@
1326
1327 ###############################################################################
1328
1329 fossil test-th-eval {string is other 123}
1330 test th1-string-is-4 {$RESULT eq \
1331 "TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
1332
1333 ###############################################################################
1334
1335 fossil test-th-eval {string is alnum 123}
1336 test th1-string-is-5 {$RESULT eq "1"}
1337
1338 DDED tools/fake-smtpd.tcl
--- a/tools/fake-smtpd.tcl
+++ b/tools/fake-smtpd.tcl
@@ -0,0 +1,84 @@
1
+#!/usr/bin/tclsh
2
+#
3
+# This script is a testing aid for working on the Relay notification method
4
+# in Fossil.
5
+#
6
+# This script listens for connections on port 25 or probably some other TCP
7
+# port specified by the "--port N" option. It pretend to be an SMTP server,
8
+# though it does not actually relay any email. Instead, it just prints the
9
+# SMTP conversation on stdout.
10
+#
11
+# If the "--max N" option is used, then the fake SMTP server shuts down
12
+# with an error after receiving N messages from the client. This can be
13
+# used to test retry capabilities in the client.
14
+#
15
+# Suggested Test Procedure For Fossil Relay Notifications
16
+#
17
+# 1. Bring up "fossil ui"
18
+# 2. Configure notification for relay to localhost:8025
19
+# 3. Start this script in a separate window. Something like:
20
+# tclsh fake-smtpd.tcl -port 8025 -max 100
21
+# 4. Send test messages using Fossil
22
+#
23
+proc conn_puts {chan txt} {
24
+ puts "S: $txt"
25
+ puts $chan $txt
26
+ flush $chan
27
+}
28
+set mxMsg 100000000
29
+proc connection {chan ip port} {
30
+ global mxMsg
31
+ set nMsg 0
32
+ puts "*** begin connection from $ip:$port ***"
33
+ conn_puts $chan "220 localhost fake-SMTPD"
34
+ set inData 0
35
+ while {1} {
36
+ set line [string trimright [gets $chan]]
37
+ if {$line eq ""} {
38
+ if {[eof $chan]} break
39
+ }
40
+ puts "C: $line"
41
+ incr nMsg
42
+ if {$inData} {
43
+ if {$line eq "."} {
44
+ set inData 0
45
+ conn_puts $chan "250 Ok"
46
+ }
47
+ } elseif {$nMsg>$mxMsg} {
48
+ conn_puts $chan "999 I'm done!"
49
+ break
50
+ } elseif {[string match "HELO *" $line]} {
51
+ conn_puts $chan "250 Ok"
52
+ } elseif {[string match "EHLO *" $line]} {
53
+ conn_puts $chan "250-SIZE 100000"
54
+ conn_puts $chan "250 HELP"
55
+ } elseif {[string match "DATA*" $line]} {
56
+ conn_puts $chan "354 End data with <CR><LF>.<CR><LF>"
57
+ set inData 1
58
+ } elseif {[string match "QUIT*" $line]} {
59
+ conn_puts $chan "221 Bye"
60
+ break
61
+ } else {
62
+ conn_puts $chan "250 Ok"
63
+ }
64
+ }
65
+ puts "*** connection closed ($nMsg messages) ***"
66
+ close $chan
67
+}
68
+set port 25
69
+set argc [llength $argv]
70
+for {set i 0} {$i<$argc-1} {incr i} {
71
+ set arg [lindex $argv $i]
72
+ if {$arg eq "-port" || $arg eq "--port"} {
73
+ incr i
74
+ set port [lindex $argv $i]
75
+ }
76
+ if {$arg eq "-max" || $arg eq "--max"} {
77
+ incr i
78
+ set mxMsg [lindex $argv $i]
79
+ }
80
+}
81
+puts "listening on localhost:$port"
82
+socket -server connection $port
83
+set forever 0
84
+vwait forever
--- a/tools/fake-smtpd.tcl
+++ b/tools/fake-smtpd.tcl
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/tools/fake-smtpd.tcl
+++ b/tools/fake-smtpd.tcl
@@ -0,0 +1,84 @@
1 #!/usr/bin/tclsh
2 #
3 # This script is a testing aid for working on the Relay notification method
4 # in Fossil.
5 #
6 # This script listens for connections on port 25 or probably some other TCP
7 # port specified by the "--port N" option. It pretend to be an SMTP server,
8 # though it does not actually relay any email. Instead, it just prints the
9 # SMTP conversation on stdout.
10 #
11 # If the "--max N" option is used, then the fake SMTP server shuts down
12 # with an error after receiving N messages from the client. This can be
13 # used to test retry capabilities in the client.
14 #
15 # Suggested Test Procedure For Fossil Relay Notifications
16 #
17 # 1. Bring up "fossil ui"
18 # 2. Configure notification for relay to localhost:8025
19 # 3. Start this script in a separate window. Something like:
20 # tclsh fake-smtpd.tcl -port 8025 -max 100
21 # 4. Send test messages using Fossil
22 #
23 proc conn_puts {chan txt} {
24 puts "S: $txt"
25 puts $chan $txt
26 flush $chan
27 }
28 set mxMsg 100000000
29 proc connection {chan ip port} {
30 global mxMsg
31 set nMsg 0
32 puts "*** begin connection from $ip:$port ***"
33 conn_puts $chan "220 localhost fake-SMTPD"
34 set inData 0
35 while {1} {
36 set line [string trimright [gets $chan]]
37 if {$line eq ""} {
38 if {[eof $chan]} break
39 }
40 puts "C: $line"
41 incr nMsg
42 if {$inData} {
43 if {$line eq "."} {
44 set inData 0
45 conn_puts $chan "250 Ok"
46 }
47 } elseif {$nMsg>$mxMsg} {
48 conn_puts $chan "999 I'm done!"
49 break
50 } elseif {[string match "HELO *" $line]} {
51 conn_puts $chan "250 Ok"
52 } elseif {[string match "EHLO *" $line]} {
53 conn_puts $chan "250-SIZE 100000"
54 conn_puts $chan "250 HELP"
55 } elseif {[string match "DATA*" $line]} {
56 conn_puts $chan "354 End data with <CR><LF>.<CR><LF>"
57 set inData 1
58 } elseif {[string match "QUIT*" $line]} {
59 conn_puts $chan "221 Bye"
60 break
61 } else {
62 conn_puts $chan "250 Ok"
63 }
64 }
65 puts "*** connection closed ($nMsg messages) ***"
66 close $chan
67 }
68 set port 25
69 set argc [llength $argv]
70 for {set i 0} {$i<$argc-1} {incr i} {
71 set arg [lindex $argv $i]
72 if {$arg eq "-port" || $arg eq "--port"} {
73 incr i
74 set port [lindex $argv $i]
75 }
76 if {$arg eq "-max" || $arg eq "--max"} {
77 incr i
78 set mxMsg [lindex $argv $i]
79 }
80 }
81 puts "listening on localhost:$port"
82 socket -server connection $port
83 set forever 0
84 vwait forever
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -79,10 +79,15 @@
7979
If no repository has such a non-zero repolist-skin setting, then
8080
the repository list is generic HTML without any decoration, with
8181
the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
8282
environment variable. The variable can be defined in the CGI
8383
control file using the [#setenv|<tt>setenv:</tt>] statement.
84
+
85
+The "Project Description" and "Login-Group" columns on the repolist page
86
+are optional. They are hidden by default. Show them by
87
+etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
88
+a string that contains substrings "description" and/or "login-group".
8489
8590
The repolist-generated page recurses into subdirectories and will list
8691
all <tt>*.fossil</tt> files found, with the following exceptions:
8792
8893
* Filenames starting with a period are treated as "hidden" and skipped.
8994
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -79,10 +79,15 @@
79 If no repository has such a non-zero repolist-skin setting, then
80 the repository list is generic HTML without any decoration, with
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
 
 
 
 
 
84
85 The repolist-generated page recurses into subdirectories and will list
86 all <tt>*.fossil</tt> files found, with the following exceptions:
87
88 * Filenames starting with a period are treated as "hidden" and skipped.
89
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -79,10 +79,15 @@
79 If no repository has such a non-zero repolist-skin setting, then
80 the repository list is generic HTML without any decoration, with
81 the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
82 environment variable. The variable can be defined in the CGI
83 control file using the [#setenv|<tt>setenv:</tt>] statement.
84
85 The "Project Description" and "Login-Group" columns on the repolist page
86 are optional. They are hidden by default. Show them by
87 etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
88 a string that contains substrings "description" and/or "login-group".
89
90 The repolist-generated page recurses into subdirectories and will list
91 all <tt>*.fossil</tt> files found, with the following exceptions:
92
93 * Filenames starting with a period are treated as "hidden" and skipped.
94
+59 -24
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,43 +1,49 @@
11
<title>Change Log</title>
22
3
-<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
3
+<h2 id='v2_27'>Changes for version 2.27 (pending)</h2>
44
5
- * Enhancements to [/help?cmd=diff|fossil diff] and similar:
5
+ * <i>(pending)</i>
6
+
7
+<h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
8
+ <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
69
<ol type="a">
7
- <li> The --from can optionally accept a directory name as its argument,
8
- and uses files under that directory as the baseline for the diff.
10
+ <li> The argument to the --from option can be a directory name, causing
11
+ Fossil to use files under that directory as the baseline for the diff.
912
<li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
1013
is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
1114
are available, or a --by diff if not.
1215
<li> The "Reload" button is added to --tk diffs, to bring the displayed
1316
diff up to date with the latest changes on disk.
1417
<li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
1518
diffs of multiple files.
1619
</ol>
17
- * Added the [/help?cmd=/ckout|/ckout web page] to provide information
20
+ <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
1821
about pending changes in a working check-out
19
- * Enhancements to the [/help?cmd=ui|fossil ui] command:
22
+ <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
2023
<ol type="a">
2124
<li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
2225
start page. Or, if the new "--from PATH" option is present, the
2326
default start page becomes "/ckout?exbase=PATH".
2427
<li> The new "--extpage FILENAME" option opens the named file as if it
2528
where in a [./serverext.wiki|CGI extension]. Example usage: the
2629
person editing this change log has
2730
"fossil ui --extpage www/changes.wiki" running and hence can
2831
press "Reload" on the web browser to view edits.
32
+ <li> Accept both IPv4 and IPv6 connections on all platforms, including
33
+ Windows and OpenBSD. This also applies to the "fossil server"
34
+ command.
2935
</ol>
30
- * Enhancements to [/help?cmd=merge|fossil merge]:
36
+ <li>Enhancements to [/help?cmd=merge|fossil merge]:
3137
<ol type="a">
3238
<li> Added the [/help?cmd=merge-info|fossil merge-info] command and
3339
especially the --tk option to that command, to provide analysis
3440
of the most recent merge or update operation.
3541
<li> When a merge conflict occurs, a new section is added to the conflict
3642
text that shows Fossil's suggested resolution to the conflict.
3743
</ol>
38
- * Enhancements to [/help?cmd=commit|fossil commit]:
44
+ <li>Enhancements to [/help?cmd=commit|fossil commit]:
3945
<ol type="a">
4046
<li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
4147
in the check-in comment, it will alert the developer and give
4248
him or her the opportunity to edit the comment before continuing.
4349
This feature is controllable by the
@@ -47,18 +53,19 @@
4753
<li> Added the ability to sign check-ins with SSH keys.
4854
<li> Issue a warning if a user tries to commit on a check-in where the
4955
branch has been changed.
5056
<li> The interactive checkin comment prompt shows the formatting rules
5157
set for that repository.
58
+ <li> Add the "--editor" option.
5259
</ol>
53
- * Deprecate the --comfmtflags and --comment-format global options and
60
+ <li>Deprecate the --comfmtflags and --comment-format global options and
5461
no longer list them in the built-in help, but keep them working for
5562
backwards compatibility.
5663
Alternative TTY comment formatting can still be specified using the
5764
[/help?cmd=comment-format|comment-format setting], if desired. The
5865
default comment format is now called "canonical", not "legacy".
59
- * Enhancements to the [/help?cmd=/timeline|/timeline page]:
66
+ <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
6067
<ol type="a">
6168
<li> Added the "ml=" ("Merge-in List") query parameter that works
6269
like "rl=" ("Related List") but adds "mionly" style related
6370
check-ins instead of the full "rel" style.
6471
<li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -81,35 +88,35 @@
8188
<li> Accept the "Z" (Zulu-time) suffix on date arguments for the
8289
"ymd" and "yw" query parameters.
8390
<li> The new "min" query parameter, when added to a from=,to= query,
8491
collapses long runs of check-ins on the same branch into just
8592
end-points.
86
- <li> The p= and d= parameters an reference different check-ins, which
87
- case the timeline shows those check-ins that are both ancestors
88
- of p= and descendants of d=.
93
+ <li> The p= and d= parameters can now reference different check-ins,
94
+ in which case the timeline shows those check-ins that are both
95
+ ancestors of p= and descendants of d=.
8996
<li> The saturation and intensity of user-specified checkin and branch
9097
background colors are automatically adjusted to keep the colors
9198
compatible with the current skin, unless the
9299
[/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
93100
</ol>
94
- * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
101
+ <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
95102
/doc but keeps the title of markdown documents with the document rather
96103
that moving it up to the page title.
97
- * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
104
+ <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
98105
and debugging
99
- * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
106
+ <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
100107
decoding of the artifact described by NAME.
101
- * Improvements to the [/help?cmd=patch|fossil patch] command:
108
+ <li>Improvements to the [/help?cmd=patch|fossil patch] command:
102109
<ol type="a">
103110
<li> Fix a bug in "fossil patch create" that causes
104111
[/help?cmd=revert|fossil revert] operations that happened
105112
on individualfiles after a [/help?cmd=merge|fossil merge]
106113
to be omitted from the patch.
107114
<li> Added the [/help?cmd=patch|patch alias] command for managing
108115
aliases for remote checkout names.
109116
</ol>
110
- * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
117
+ <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
111118
<ol type="a">
112119
<li> Add the ability to search the help text, either in the UI
113120
(on the [/help?cmd=/search|/search page]) or from the command-line
114121
(using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
115122
<li> Accepts an optional SUBCOMMAND argument following the
@@ -116,27 +123,55 @@
116123
COMMAND argument and only shows results for the specified
117124
subcommand, not the entire command.
118125
<li> The -u (--usage) option shows only the command-line syntax
119126
<li> The -o (--options) option shows only the command-line options
120127
</ol>
121
- * Enhancements to the ticket system:
128
+ <li>Enhancements to the [./tickets.wiki|ticket system]:
122129
<ol type="a">
123130
<li> Added the ability to attach wiki pages to a ticket for extended
124131
descriptions.
125132
<li> Added submenu to the 'View Ticket' page, to use it as
126133
template for a new ticket.
127134
<li> Added button 'Submit and New' to create multiple tickets
128135
in a row.
136
+ <li> Link the version field in ticket view to a matching checkin or tag.
137
+ <li> Show creation time in report and ticket view.
138
+ <li> Show previous comments in edit ticket as reference.
129139
</ol>
130
- * Added the "hash" query parameter to the
140
+ <li>Added the "hash" query parameter to the
131141
[/help?cmd=/whatis|/whatis webpage].
132
- * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
142
+ <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
133143
which alerts subscribers when an admin creates a new user or
134144
when a user's permissions change.
135
- * Show project description on repository list.
136
- * Diverse minor fixes and additions.
137
-
145
+ <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains
146
+ the substring "description", then the project description for each repository
147
+ is shown on the repository list page. The login-group for each project is
148
+ now only shown if the FOSSIL_REPOLIST_SHOW environment variable exists and
149
+ contains the substring "login-group". ([./cgi.wiki#repolist|More information])
150
+ <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
151
+ security:
152
+ <ol type="a">
153
+ <li> TH1 now makes a distinction between
154
+ [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
155
+ This makes it more difficult to write custom TH1 scripts that
156
+ contain XSS or SQL-injection bugs. The
157
+ [/help?cmd=vuln-report|vuln-report] setting was added to control
158
+ what Fossil does when it encounters a potential TH1
159
+ security problem.
160
+ <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
161
+ command.
162
+ <li> The "enable_htmlify" TH1 command was removed.
163
+ </ol>
164
+ <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
165
+ the frequency of reconnection attempts over time and providing feedback
166
+ to the user when the connection is down.
167
+ <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged
168
+ in, nor are links to that page displayed to users who are not logged in. Being
169
+ logged in as "anonymous" is sufficient to overcome this restriction, assuming
170
+ that "anonymous" can download tarballs and ZIP archives.
171
+ <li>Many other minor fixes and additions.
172
+</ol>
138173
139174
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
140175
141176
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
142177
that have non-ASCII filenames
143178
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,43 +1,49 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
5 * Enhancements to [/help?cmd=diff|fossil diff] and similar:
 
 
 
6 <ol type="a">
7 <li> The --from can optionally accept a directory name as its argument,
8 and uses files under that directory as the baseline for the diff.
9 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
10 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
11 are available, or a --by diff if not.
12 <li> The "Reload" button is added to --tk diffs, to bring the displayed
13 diff up to date with the latest changes on disk.
14 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
15 diffs of multiple files.
16 </ol>
17 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
18 about pending changes in a working check-out
19 * Enhancements to the [/help?cmd=ui|fossil ui] command:
20 <ol type="a">
21 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
22 start page. Or, if the new "--from PATH" option is present, the
23 default start page becomes "/ckout?exbase=PATH".
24 <li> The new "--extpage FILENAME" option opens the named file as if it
25 where in a [./serverext.wiki|CGI extension]. Example usage: the
26 person editing this change log has
27 "fossil ui --extpage www/changes.wiki" running and hence can
28 press "Reload" on the web browser to view edits.
 
 
 
29 </ol>
30 * Enhancements to [/help?cmd=merge|fossil merge]:
31 <ol type="a">
32 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
33 especially the --tk option to that command, to provide analysis
34 of the most recent merge or update operation.
35 <li> When a merge conflict occurs, a new section is added to the conflict
36 text that shows Fossil's suggested resolution to the conflict.
37 </ol>
38 * Enhancements to [/help?cmd=commit|fossil commit]:
39 <ol type="a">
40 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
41 in the check-in comment, it will alert the developer and give
42 him or her the opportunity to edit the comment before continuing.
43 This feature is controllable by the
@@ -47,18 +53,19 @@
47 <li> Added the ability to sign check-ins with SSH keys.
48 <li> Issue a warning if a user tries to commit on a check-in where the
49 branch has been changed.
50 <li> The interactive checkin comment prompt shows the formatting rules
51 set for that repository.
 
52 </ol>
53 * Deprecate the --comfmtflags and --comment-format global options and
54 no longer list them in the built-in help, but keep them working for
55 backwards compatibility.
56 Alternative TTY comment formatting can still be specified using the
57 [/help?cmd=comment-format|comment-format setting], if desired. The
58 default comment format is now called "canonical", not "legacy".
59 * Enhancements to the [/help?cmd=/timeline|/timeline page]:
60 <ol type="a">
61 <li> Added the "ml=" ("Merge-in List") query parameter that works
62 like "rl=" ("Related List") but adds "mionly" style related
63 check-ins instead of the full "rel" style.
64 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -81,35 +88,35 @@
81 <li> Accept the "Z" (Zulu-time) suffix on date arguments for the
82 "ymd" and "yw" query parameters.
83 <li> The new "min" query parameter, when added to a from=,to= query,
84 collapses long runs of check-ins on the same branch into just
85 end-points.
86 <li> The p= and d= parameters an reference different check-ins, which
87 case the timeline shows those check-ins that are both ancestors
88 of p= and descendants of d=.
89 <li> The saturation and intensity of user-specified checkin and branch
90 background colors are automatically adjusted to keep the colors
91 compatible with the current skin, unless the
92 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
93 </ol>
94 * The [/help?cmd=/docfile|/docfile webpage] was added. It works like
95 /doc but keeps the title of markdown documents with the document rather
96 that moving it up to the page title.
97 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
98 and debugging
99 * Added the "artifact_to_json(NAME)" SQL function that returns a JSON
100 decoding of the artifact described by NAME.
101 * Improvements to the [/help?cmd=patch|fossil patch] command:
102 <ol type="a">
103 <li> Fix a bug in "fossil patch create" that causes
104 [/help?cmd=revert|fossil revert] operations that happened
105 on individualfiles after a [/help?cmd=merge|fossil merge]
106 to be omitted from the patch.
107 <li> Added the [/help?cmd=patch|patch alias] command for managing
108 aliases for remote checkout names.
109 </ol>
110 * Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
111 <ol type="a">
112 <li> Add the ability to search the help text, either in the UI
113 (on the [/help?cmd=/search|/search page]) or from the command-line
114 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
115 <li> Accepts an optional SUBCOMMAND argument following the
@@ -116,27 +123,55 @@
116 COMMAND argument and only shows results for the specified
117 subcommand, not the entire command.
118 <li> The -u (--usage) option shows only the command-line syntax
119 <li> The -o (--options) option shows only the command-line options
120 </ol>
121 * Enhancements to the ticket system:
122 <ol type="a">
123 <li> Added the ability to attach wiki pages to a ticket for extended
124 descriptions.
125 <li> Added submenu to the 'View Ticket' page, to use it as
126 template for a new ticket.
127 <li> Added button 'Submit and New' to create multiple tickets
128 in a row.
 
 
 
129 </ol>
130 * Added the "hash" query parameter to the
131 [/help?cmd=/whatis|/whatis webpage].
132 * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
133 which alerts subscribers when an admin creates a new user or
134 when a user's permissions change.
135 * Show project description on repository list.
136 * Diverse minor fixes and additions.
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
139 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
140
141 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
142 that have non-ASCII filenames
143
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,43 +1,49 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_27'>Changes for version 2.27 (pending)</h2>
4
5 * <i>(pending)</i>
6
7 <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
8 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
9 <ol type="a">
10 <li> The argument to the --from option can be a directory name, causing
11 Fossil to use files under that directory as the baseline for the diff.
12 <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
13 is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
14 are available, or a --by diff if not.
15 <li> The "Reload" button is added to --tk diffs, to bring the displayed
16 diff up to date with the latest changes on disk.
17 <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
18 diffs of multiple files.
19 </ol>
20 <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
21 about pending changes in a working check-out
22 <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
23 <ol type="a">
24 <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
25 start page. Or, if the new "--from PATH" option is present, the
26 default start page becomes "/ckout?exbase=PATH".
27 <li> The new "--extpage FILENAME" option opens the named file as if it
28 where in a [./serverext.wiki|CGI extension]. Example usage: the
29 person editing this change log has
30 "fossil ui --extpage www/changes.wiki" running and hence can
31 press "Reload" on the web browser to view edits.
32 <li> Accept both IPv4 and IPv6 connections on all platforms, including
33 Windows and OpenBSD. This also applies to the "fossil server"
34 command.
35 </ol>
36 <li>Enhancements to [/help?cmd=merge|fossil merge]:
37 <ol type="a">
38 <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
39 especially the --tk option to that command, to provide analysis
40 of the most recent merge or update operation.
41 <li> When a merge conflict occurs, a new section is added to the conflict
42 text that shows Fossil's suggested resolution to the conflict.
43 </ol>
44 <li>Enhancements to [/help?cmd=commit|fossil commit]:
45 <ol type="a">
46 <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
47 in the check-in comment, it will alert the developer and give
48 him or her the opportunity to edit the comment before continuing.
49 This feature is controllable by the
@@ -47,18 +53,19 @@
53 <li> Added the ability to sign check-ins with SSH keys.
54 <li> Issue a warning if a user tries to commit on a check-in where the
55 branch has been changed.
56 <li> The interactive checkin comment prompt shows the formatting rules
57 set for that repository.
58 <li> Add the "--editor" option.
59 </ol>
60 <li>Deprecate the --comfmtflags and --comment-format global options and
61 no longer list them in the built-in help, but keep them working for
62 backwards compatibility.
63 Alternative TTY comment formatting can still be specified using the
64 [/help?cmd=comment-format|comment-format setting], if desired. The
65 default comment format is now called "canonical", not "legacy".
66 <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
67 <ol type="a">
68 <li> Added the "ml=" ("Merge-in List") query parameter that works
69 like "rl=" ("Related List") but adds "mionly" style related
70 check-ins instead of the full "rel" style.
71 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
@@ -81,35 +88,35 @@
88 <li> Accept the "Z" (Zulu-time) suffix on date arguments for the
89 "ymd" and "yw" query parameters.
90 <li> The new "min" query parameter, when added to a from=,to= query,
91 collapses long runs of check-ins on the same branch into just
92 end-points.
93 <li> The p= and d= parameters can now reference different check-ins,
94 in which case the timeline shows those check-ins that are both
95 ancestors of p= and descendants of d=.
96 <li> The saturation and intensity of user-specified checkin and branch
97 background colors are automatically adjusted to keep the colors
98 compatible with the current skin, unless the
99 [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
100 </ol>
101 <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
102 /doc but keeps the title of markdown documents with the document rather
103 that moving it up to the page title.
104 <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
105 and debugging
106 <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
107 decoding of the artifact described by NAME.
108 <li>Improvements to the [/help?cmd=patch|fossil patch] command:
109 <ol type="a">
110 <li> Fix a bug in "fossil patch create" that causes
111 [/help?cmd=revert|fossil revert] operations that happened
112 on individualfiles after a [/help?cmd=merge|fossil merge]
113 to be omitted from the patch.
114 <li> Added the [/help?cmd=patch|patch alias] command for managing
115 aliases for remote checkout names.
116 </ol>
117 <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
118 <ol type="a">
119 <li> Add the ability to search the help text, either in the UI
120 (on the [/help?cmd=/search|/search page]) or from the command-line
121 (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
122 <li> Accepts an optional SUBCOMMAND argument following the
@@ -116,27 +123,55 @@
123 COMMAND argument and only shows results for the specified
124 subcommand, not the entire command.
125 <li> The -u (--usage) option shows only the command-line syntax
126 <li> The -o (--options) option shows only the command-line options
127 </ol>
128 <li>Enhancements to the [./tickets.wiki|ticket system]:
129 <ol type="a">
130 <li> Added the ability to attach wiki pages to a ticket for extended
131 descriptions.
132 <li> Added submenu to the 'View Ticket' page, to use it as
133 template for a new ticket.
134 <li> Added button 'Submit and New' to create multiple tickets
135 in a row.
136 <li> Link the version field in ticket view to a matching checkin or tag.
137 <li> Show creation time in report and ticket view.
138 <li> Show previous comments in edit ticket as reference.
139 </ol>
140 <li>Added the "hash" query parameter to the
141 [/help?cmd=/whatis|/whatis webpage].
142 <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
143 which alerts subscribers when an admin creates a new user or
144 when a user's permissions change.
145 <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains
146 the substring "description", then the project description for each repository
147 is shown on the repository list page. The login-group for each project is
148 now only shown if the FOSSIL_REPOLIST_SHOW environment variable exists and
149 contains the substring "login-group". ([./cgi.wiki#repolist|More information])
150 <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
151 security:
152 <ol type="a">
153 <li> TH1 now makes a distinction between
154 [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
155 This makes it more difficult to write custom TH1 scripts that
156 contain XSS or SQL-injection bugs. The
157 [/help?cmd=vuln-report|vuln-report] setting was added to control
158 what Fossil does when it encounters a potential TH1
159 security problem.
160 <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
161 command.
162 <li> The "enable_htmlify" TH1 command was removed.
163 </ol>
164 <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
165 the frequency of reconnection attempts over time and providing feedback
166 to the user when the connection is down.
167 <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged
168 in, nor are links to that page displayed to users who are not logged in. Being
169 logged in as "anonymous" is sufficient to overcome this restriction, assuming
170 that "anonymous" can download tarballs and ZIP archives.
171 <li>Many other minor fixes and additions.
172 </ol>
173
174 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
175
176 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
177 that have non-ASCII filenames
178
--- www/customskin.md
+++ www/customskin.md
@@ -310,10 +310,11 @@
310310
with the "--skin ./newskin" option. If the argument to the --skin
311311
option contains a "/" character, then the five control files are
312312
read out of the directory named. You can then edit the control
313313
files in the ./newskin folder using you favorite text editor, and
314314
press "Reload" on your browser to see the effects.
315
+
315316
316317
### Disabling The Web Browser Cache During Development
317318
318319
Fossil is aggressive about asking the web browser to cache
319320
resources. While developing a new skin, it is often helpful to
@@ -526,11 +527,46 @@
526527
Iterate until the desired look is achieved.
527528
528529
4. Copy/paste the resulting css.txt, details.txt,
529530
header.txt, and footer.txt files
530531
into the CSS, details, header, and footer configuration screens
531
- under the Admin/Skins menu.
532
+ under the Admin/Skins menu. Alternately, import them using the
533
+ process described below.
534
+
535
+An alternative to step 4 is to convert the skin files into a form
536
+which can be imported into a repository using `fossil config import`.
537
+It requires compiling [a small tool from the fossil source
538
+tree](/file/tools/skintxt2config.c):
539
+
540
+>
541
+```
542
+$ cc -o s2c /path/to/fossil/checkout/tools/skintxt2config.c
543
+```
544
+
545
+With that in place, the custom skin files can be converted with:
546
+
547
+>
548
+```
549
+$ ./s2c yourskin/*.txt > skin.config
550
+```
551
+
552
+It can be imported into an arbitrary fossil repository with:
553
+
554
+>
555
+```
556
+$ fossil config import skin.config
557
+```
558
+
559
+And it can be pushed to a remote repository with:
560
+
561
+>
562
+```
563
+$ fossil config push skin
564
+```
565
+
566
+That approach has proven to be an effective way to locally develop
567
+skin changes then push them to a "live" site.
532568
533569
534570
## See Also
535571
536572
* [Customizing the Timeline Graph](customgraph.md)
537573
--- www/customskin.md
+++ www/customskin.md
@@ -310,10 +310,11 @@
310 with the "--skin ./newskin" option. If the argument to the --skin
311 option contains a "/" character, then the five control files are
312 read out of the directory named. You can then edit the control
313 files in the ./newskin folder using you favorite text editor, and
314 press "Reload" on your browser to see the effects.
 
315
316 ### Disabling The Web Browser Cache During Development
317
318 Fossil is aggressive about asking the web browser to cache
319 resources. While developing a new skin, it is often helpful to
@@ -526,11 +527,46 @@
526 Iterate until the desired look is achieved.
527
528 4. Copy/paste the resulting css.txt, details.txt,
529 header.txt, and footer.txt files
530 into the CSS, details, header, and footer configuration screens
531 under the Admin/Skins menu.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
533
534 ## See Also
535
536 * [Customizing the Timeline Graph](customgraph.md)
537
--- www/customskin.md
+++ www/customskin.md
@@ -310,10 +310,11 @@
310 with the "--skin ./newskin" option. If the argument to the --skin
311 option contains a "/" character, then the five control files are
312 read out of the directory named. You can then edit the control
313 files in the ./newskin folder using you favorite text editor, and
314 press "Reload" on your browser to see the effects.
315
316
317 ### Disabling The Web Browser Cache During Development
318
319 Fossil is aggressive about asking the web browser to cache
320 resources. While developing a new skin, it is often helpful to
@@ -526,11 +527,46 @@
527 Iterate until the desired look is achieved.
528
529 4. Copy/paste the resulting css.txt, details.txt,
530 header.txt, and footer.txt files
531 into the CSS, details, header, and footer configuration screens
532 under the Admin/Skins menu. Alternately, import them using the
533 process described below.
534
535 An alternative to step 4 is to convert the skin files into a form
536 which can be imported into a repository using `fossil config import`.
537 It requires compiling [a small tool from the fossil source
538 tree](/file/tools/skintxt2config.c):
539
540 >
541 ```
542 $ cc -o s2c /path/to/fossil/checkout/tools/skintxt2config.c
543 ```
544
545 With that in place, the custom skin files can be converted with:
546
547 >
548 ```
549 $ ./s2c yourskin/*.txt > skin.config
550 ```
551
552 It can be imported into an arbitrary fossil repository with:
553
554 >
555 ```
556 $ fossil config import skin.config
557 ```
558
559 And it can be pushed to a remote repository with:
560
561 >
562 ```
563 $ fossil config push skin
564 ```
565
566 That approach has proven to be an effective way to locally develop
567 skin changes then push them to a "live" site.
568
569
570 ## See Also
571
572 * [Customizing the Timeline Graph](customgraph.md)
573
--- www/env-opts.md
+++ www/env-opts.md
@@ -153,10 +153,16 @@
153153
154154
`FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155155
loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156156
none of the listed repositories has the `repolist_skin` property set.
157157
Can be set from the [CGI control file][cgictlfile].
158
+
159
+`FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value
160
+that contains the substring "description", then the "Project Description"
161
+column appears on the repolist page. If it contains the substring
162
+"login-group", then the Login-Group column appears on the repolist page.
163
+Can be set from the [CGI control file][cgictlfile].
158164
159165
`FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
160166
SEE as text to be hashed into the actual encryption key. This has no
161167
effect if Fossil was not compiled with SEE support enabled.
162168
163169
--- www/env-opts.md
+++ www/env-opts.md
@@ -153,10 +153,16 @@
153
154 `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155 loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156 none of the listed repositories has the `repolist_skin` property set.
157 Can be set from the [CGI control file][cgictlfile].
 
 
 
 
 
 
158
159 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
160 SEE as text to be hashed into the actual encryption key. This has no
161 effect if Fossil was not compiled with SEE support enabled.
162
163
--- www/env-opts.md
+++ www/env-opts.md
@@ -153,10 +153,16 @@
153
154 `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
155 loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
156 none of the listed repositories has the `repolist_skin` property set.
157 Can be set from the [CGI control file][cgictlfile].
158
159 `FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value
160 that contains the substring "description", then the "Project Description"
161 column appears on the repolist page. If it contains the substring
162 "login-group", then the Login-Group column appears on the repolist page.
163 Can be set from the [CGI control file][cgictlfile].
164
165 `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
166 SEE as text to be hashed into the actual encryption key. This has no
167 effect if Fossil was not compiled with SEE support enabled.
168
169
+4 -4
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
8484
the repository are consistent prior to each commit.
8585
8686
8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
8787
8888
<hr>
89
-<h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3>
89
+<h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3>
9090
9191
* [/uv/download.html|Download]
92
- * [./changes.wiki#v2_25|Change Summary]
93
- * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25]
94
- * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release]
92
+ * [./changes.wiki#v2_26|Change Summary]
93
+ * [/timeline?p=version-2.26&bt=version-2.25&y=ci|Check-ins in version 2.26]
94
+ * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release]
9595
* [/timeline?t=release|Timeline of all past releases]
9696
9797
<hr>
9898
<h3>Quick Start</h3>
9999
100100
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_25|Change Summary]
93 * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25]
94 * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release]
95 * [/timeline?t=release|Timeline of all past releases]
96
97 <hr>
98 <h3>Quick Start</h3>
99
100
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_26|Change Summary]
93 * [/timeline?p=version-2.26&bt=version-2.25&y=ci|Check-ins in version 2.26]
94 * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release]
95 * [/timeline?t=release|Timeline of all past releases]
96
97 <hr>
98 <h3>Quick Start</h3>
99
100
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
1919
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
2020
</b></pre>
2121
2222
<h2 id="workflow" name="fslclone">General Work Flow</h2>
2323
24
-Fossil works with repository files (a database in a single file with the project's
25
-complete history) and with checked-out local trees (the working directory
26
-you use to do your work).
27
-(See [./glossary.md | the glossary] for more background.)
28
-The workflow looks like this:
24
+Fossil works with [./glossary.md#repository | repository files]
25
+and [./glossary.md#check-out | check-out directories] using a
26
+workflow like this:
2927
3028
<ul>
3129
<li>Create or clone a repository file. ([/help/init|fossil init] or
3230
[/help/clone | fossil clone])
3331
<li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
4139
The following sections give a brief overview of these
4240
operations.
4341
4442
<h2 id="new">Starting A New Project</h2>
4543
46
-To start a new project with fossil create a new empty repository
47
-this way: ([/help/init | more info])
44
+To start a new project with Fossil, [/help/init | create a new empty repository]:
4845
4946
<pre><b>fossil init</b> <i>repository-filename</i>
5047
</pre>
5148
5249
You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
8279
<h2 id="clone">Cloning An Existing Repository</h2>
8380
8481
Most fossil operations interact with a repository that is on the
8582
local disk drive, not on a remote system. Hence, before accessing
8683
a remote repository it is necessary to make a local copy of that
87
-repository. Making a local copy of a remote repository is called
88
-"cloning".
84
+repository, a process called
85
+"[/help/clone | cloning]".
8986
90
-Clone a remote repository as follows: ([/help/clone | more info])
87
+This is done as follows:
9188
9289
<pre><b>fossil clone</b> <i>URL repository-filename</i>
9390
</pre>
9491
9592
The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
107104
100% complete...
108105
Extra delta compression...
109106
Vacuuming the database...
110107
project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
111108
server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
112
-admin-user: exampleuser (password is "yoWgDR42iv")>
109
+admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
113110
</b></pre>
111
+
112
+This <i>exampleuser</i> will be used by Fossil as the author of commits when
113
+you checkin changes to the repository. It is also used by Fossil when you
114
+make your repository available to others using the built-in server mode by
115
+running <tt>[/help/server | fossil server]</tt> and will also be used when
116
+running <tt>[/help/ui | fossil ui]</tt> to view the repository through
117
+the Fossil UI. See the quick start topic for setting up a
118
+<a href="#server">server</a> for more details.
114119
115120
If the remote repository requires a login, include a
116121
userid in the URL like this:
117122
118123
<pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
153158
154159
<h2 id="checkout">Checking Out A Local Tree</h2>
155160
156161
To work on a project in fossil, you need to check out a local
157162
copy of the source tree. Create the directory you want to be
158
-the root of your tree and cd into that directory. Then
159
-do this: ([/help/open | more info])
163
+the root of your tree, <tt>cd</tt> into that directory, and then:
160164
161165
<pre><b>fossil open</b> <i>repository-filename</i></pre>
162166
163
-for example:
167
+For example:
164168
165169
<pre><b>fossil open ../myclone.fossil
166170
BUILD.txt
167171
COPYRIGHT-BSD2.txt
168172
README.md
169173
170174
</tt></b></pre>
171175
172
-(or "fossil open ..\myclone.fossil" on Windows).
173
-
174176
This leaves you with the newest version of the tree
175177
checked out.
176178
From anywhere underneath the root of your local tree, you
177179
can type commands like the following to find out the status of
178180
your local tree:
@@ -320,41 +322,60 @@
320322
321323
This will get you started on identifying checkins. The
322324
<a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
323325
how timestamps can also be used.
324326
325
-<h2 id="config">Configuring Your Local Repository</h2>
327
+<h2 id="config">Accessing Your Local Repository's Web User Interface</h2>
326328
327
-When you create a new repository, either by cloning an existing
328
-project or create a new project of your own, you usually want to do some
329
-local configuration. This is easily accomplished using the web-server
330
-that is built into fossil. Start the fossil web server like this:
331
-([/help/ui | more info])
329
+After you create a new repository, you usually want to do some local
330
+configuration. This is most easily accomplished by firing up the Fossil
331
+UI:
332332
333333
<pre>
334334
<b>fossil ui</b> <i>repository-filename</i>
335335
</pre>
336336
337
-You can omit the <i>repository-filename</i> from the command above
337
+You can shorten that to just [/help/ui | <b>fossil ui</b>]
338338
if you are inside a checked-out local tree.
339339
340
-This starts a web server then automatically launches your
341
-web browser and makes it point to this web server. If your system
342
-has an unusual configuration, fossil might not be able to figure out
343
-how to start your web browser. In that case, first tell fossil
344
-where to find your web browser using a command like this:
340
+This command starts an internal web server, after which Fossil
341
+automatically launches your default browser, pointed at itself,
342
+presenting a special view of the repository, its web user interface.
343
+
344
+You may override Fossil's logic for selecting the default browser so:
345345
346346
<pre>
347347
<b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348348
</pre>
349349
350
-By default, fossil does not require a login for HTTP connections
351
-coming in from the IP loopback address 127.0.0.1. You can, and perhaps
352
-should, change this after you create a few users.
350
+When launched this way, Fossil binds its internal web server to the IP
351
+loopback address, 127.0.0.1, which it treats specially, bypassing all
352
+user controls, effectively giving visitors the
353
+[./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].
354
+
355
+Why is that a good idea, you ask? Because it is a safe
356
+presumption that only someone with direct file access to the repository
357
+database file could be using the resulting web interface. Anyone who can
358
+modify the repo DB directly could give themselves any and all access
359
+with a SQL query, or even by direct file manipulation; no amount of
360
+access control matters to such a user.
361
+
362
+(Contrast the [#server | many <i>other</i> ways] of setting Fossil up
363
+as an HTTP server, where the repo DB is on the other side of the HTTP
364
+server wall, inaccessible by all means other than Fossil's own
365
+mediation. For this reason, the "localhost bypasses access control"
366
+policy does <i>not</i> apply to these other interfaces. That is a very
367
+good thing, since without this difference in policy, it would be unsafe
368
+to bind a [/help?cmd=server | <b>fossil server</b>] instance to
369
+localhost on a high-numbered port and then reverse-proxy it out to the
370
+world via HTTPS, a practice this author does engage in, with confidence.)
353371
354
-When you are finished configuring, just press Control-C or use
355
-the <b>kill</b> command to shut down the mini-server.
372
+Once you are finished configuring Fossil, you may safely Control-C out
373
+of the <b>fossil&nbsp;ui</b> command to shut down this privileged
374
+built-in web server. Moreover, you may by grace of SQLite do this <i>at
375
+any time</i>: all changes are either committed durably to the repo DB or
376
+rolled back, in their totality. This includes configuration changes.
356377
357378
<h2 id="sharing">Sharing Changes</h2>
358379
359380
When [./concepts.wiki#workflow|autosync] is turned off,
360381
the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
464485
level of undo/redo.
465486
466487
467488
<h2 id="server">Setting Up A Server</h2>
468489
469
-Fossil can act as a stand-alone web server using one of these
470
-commands:
490
+In addition to the inward-facing <b>fossil ui</b> mode covered [#config
491
+| above], Fossil can also act as an outward-facing web server:
471492
472493
<pre>
473494
<b>[/help/server | fossil server]</b> <i>repository-filename</i>
474
-<b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
475495
</pre>
476496
477
-The <i>repository-filename</i> can be omitted when these commands
478
-are run from within an open check-out, which is a particularly useful
479
-shortcut with the <b>fossil ui</b> command.
480
-
481
-The <b>ui</b> command is intended for accessing the web user interface
482
-from a local desktop. (We sometimes call this mode "Fossil UI.")
483
-The <b>ui</b> command differs from the
484
-<b>server</b> command by binding to the loopback IP
485
-address only (thus making the web UI visible only on the
486
-local machine) and by automatically starting your default web browser,
487
-pointing it at the running UI
488
-server. The localhost restriction exists because it also gives anyone
489
-who can access the resulting web UI full control over the
490
-repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
491
-Setup capabliity].)
492
-
493
-For cross-machine collaboration, use the <b>server</b> command instead,
494
-which binds on all IP addresses, does not try to start a web browser,
495
-and enforces [./caps/ | Fossil's role-based access control system].
496
-
497
-Servers are also easily configured as:
497
+Just as with <b>fossil ui</b>, you may omit the
498
+<i>repository-filename</i> parameter when running this from within an open
499
+check-out.
500
+
501
+<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
502
+interfaces by default in this mode, and it enforces the configured
503
+[./caps/ | role-based access controls]. Further, because it is meant to
504
+provide external web service, it doesn't try to launch a local web
505
+browser pointing to a "Fossil UI" presentation; external visitors see
506
+your repository's configured home page instead.
507
+
508
+To serve varying needs, there are additional ways to serve a Fossil repo
509
+to external users:
498510
499511
<ul>
512
+<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
513
+ self-hosting repositories]
514
+<li>[./server/any/scgi.md|SCGI]
500515
<li>[./server/any/inetd.md|inetd]
501516
<li>[./server/debian/service.md|systemd]
502
-<li>[./server/any/cgi.md|CGI]
503
-<li>[./server/any/scgi.md|SCGI]
504517
</ul>
505518
506519
…along with [./server/#matrix | several other options].
507520
508
-The [./selfhost.wiki | self-hosting fossil repositories] use
509
-CGI.
510
-
511
-You might <i>need</i> to set up a server, whether you know it yet or
512
-not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
513
-article for details.
521
+We recommend that you read the [./server/whyuseaserver.wiki | Benefits
522
+of a Fossil Server] article, because you might <i>need</i> to do this
523
+and not yet know it.
514524
515525
<h2 id="proxy">HTTP Proxies</h2>
516526
517527
If you are behind a restrictive firewall that requires you to use
518528
an HTTP proxy to reach the internet, then you can configure the proxy
519529
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
19 This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
20 </b></pre>
21
22 <h2 id="workflow" name="fslclone">General Work Flow</h2>
23
24 Fossil works with repository files (a database in a single file with the project's
25 complete history) and with checked-out local trees (the working directory
26 you use to do your work).
27 (See [./glossary.md | the glossary] for more background.)
28 The workflow looks like this:
29
30 <ul>
31 <li>Create or clone a repository file. ([/help/init|fossil init] or
32 [/help/clone | fossil clone])
33 <li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
41 The following sections give a brief overview of these
42 operations.
43
44 <h2 id="new">Starting A New Project</h2>
45
46 To start a new project with fossil create a new empty repository
47 this way: ([/help/init | more info])
48
49 <pre><b>fossil init</b> <i>repository-filename</i>
50 </pre>
51
52 You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
82 <h2 id="clone">Cloning An Existing Repository</h2>
83
84 Most fossil operations interact with a repository that is on the
85 local disk drive, not on a remote system. Hence, before accessing
86 a remote repository it is necessary to make a local copy of that
87 repository. Making a local copy of a remote repository is called
88 "cloning".
89
90 Clone a remote repository as follows: ([/help/clone | more info])
91
92 <pre><b>fossil clone</b> <i>URL repository-filename</i>
93 </pre>
94
95 The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
107 100% complete...
108 Extra delta compression...
109 Vacuuming the database...
110 project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
111 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
112 admin-user: exampleuser (password is "yoWgDR42iv")>
113 </b></pre>
 
 
 
 
 
 
 
 
114
115 If the remote repository requires a login, include a
116 userid in the URL like this:
117
118 <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
153
154 <h2 id="checkout">Checking Out A Local Tree</h2>
155
156 To work on a project in fossil, you need to check out a local
157 copy of the source tree. Create the directory you want to be
158 the root of your tree and cd into that directory. Then
159 do this: ([/help/open | more info])
160
161 <pre><b>fossil open</b> <i>repository-filename</i></pre>
162
163 for example:
164
165 <pre><b>fossil open ../myclone.fossil
166 BUILD.txt
167 COPYRIGHT-BSD2.txt
168 README.md
169
170 </tt></b></pre>
171
172 (or "fossil open ..\myclone.fossil" on Windows).
173
174 This leaves you with the newest version of the tree
175 checked out.
176 From anywhere underneath the root of your local tree, you
177 can type commands like the following to find out the status of
178 your local tree:
@@ -320,41 +322,60 @@
320
321 This will get you started on identifying checkins. The
322 <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
323 how timestamps can also be used.
324
325 <h2 id="config">Configuring Your Local Repository</h2>
326
327 When you create a new repository, either by cloning an existing
328 project or create a new project of your own, you usually want to do some
329 local configuration. This is easily accomplished using the web-server
330 that is built into fossil. Start the fossil web server like this:
331 ([/help/ui | more info])
332
333 <pre>
334 <b>fossil ui</b> <i>repository-filename</i>
335 </pre>
336
337 You can omit the <i>repository-filename</i> from the command above
338 if you are inside a checked-out local tree.
339
340 This starts a web server then automatically launches your
341 web browser and makes it point to this web server. If your system
342 has an unusual configuration, fossil might not be able to figure out
343 how to start your web browser. In that case, first tell fossil
344 where to find your web browser using a command like this:
345
346 <pre>
347 <b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348 </pre>
349
350 By default, fossil does not require a login for HTTP connections
351 coming in from the IP loopback address 127.0.0.1. You can, and perhaps
352 should, change this after you create a few users.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
354 When you are finished configuring, just press Control-C or use
355 the <b>kill</b> command to shut down the mini-server.
 
 
 
356
357 <h2 id="sharing">Sharing Changes</h2>
358
359 When [./concepts.wiki#workflow|autosync] is turned off,
360 the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
464 level of undo/redo.
465
466
467 <h2 id="server">Setting Up A Server</h2>
468
469 Fossil can act as a stand-alone web server using one of these
470 commands:
471
472 <pre>
473 <b>[/help/server | fossil server]</b> <i>repository-filename</i>
474 <b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
475 </pre>
476
477 The <i>repository-filename</i> can be omitted when these commands
478 are run from within an open check-out, which is a particularly useful
479 shortcut with the <b>fossil ui</b> command.
480
481 The <b>ui</b> command is intended for accessing the web user interface
482 from a local desktop. (We sometimes call this mode "Fossil UI.")
483 The <b>ui</b> command differs from the
484 <b>server</b> command by binding to the loopback IP
485 address only (thus making the web UI visible only on the
486 local machine) and by automatically starting your default web browser,
487 pointing it at the running UI
488 server. The localhost restriction exists because it also gives anyone
489 who can access the resulting web UI full control over the
490 repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
491 Setup capabliity].)
492
493 For cross-machine collaboration, use the <b>server</b> command instead,
494 which binds on all IP addresses, does not try to start a web browser,
495 and enforces [./caps/ | Fossil's role-based access control system].
496
497 Servers are also easily configured as:
498
499 <ul>
 
 
 
500 <li>[./server/any/inetd.md|inetd]
501 <li>[./server/debian/service.md|systemd]
502 <li>[./server/any/cgi.md|CGI]
503 <li>[./server/any/scgi.md|SCGI]
504 </ul>
505
506 …along with [./server/#matrix | several other options].
507
508 The [./selfhost.wiki | self-hosting fossil repositories] use
509 CGI.
510
511 You might <i>need</i> to set up a server, whether you know it yet or
512 not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
513 article for details.
514
515 <h2 id="proxy">HTTP Proxies</h2>
516
517 If you are behind a restrictive firewall that requires you to use
518 an HTTP proxy to reach the internet, then you can configure the proxy
519
--- www/quickstart.wiki
+++ www/quickstart.wiki
@@ -19,15 +19,13 @@
19 This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
20 </b></pre>
21
22 <h2 id="workflow" name="fslclone">General Work Flow</h2>
23
24 Fossil works with [./glossary.md#repository | repository files]
25 and [./glossary.md#check-out | check-out directories] using a
26 workflow like this:
 
 
27
28 <ul>
29 <li>Create or clone a repository file. ([/help/init|fossil init] or
30 [/help/clone | fossil clone])
31 <li>Check out a local tree. ([/help/open | fossil open])
@@ -41,12 +39,11 @@
39 The following sections give a brief overview of these
40 operations.
41
42 <h2 id="new">Starting A New Project</h2>
43
44 To start a new project with Fossil, [/help/init | create a new empty repository]:
 
45
46 <pre><b>fossil init</b> <i>repository-filename</i>
47 </pre>
48
49 You can name the database anything you like, and you can place it anywhere in the filesystem.
@@ -82,14 +79,14 @@
79 <h2 id="clone">Cloning An Existing Repository</h2>
80
81 Most fossil operations interact with a repository that is on the
82 local disk drive, not on a remote system. Hence, before accessing
83 a remote repository it is necessary to make a local copy of that
84 repository, a process called
85 "[/help/clone | cloning]".
86
87 This is done as follows:
88
89 <pre><b>fossil clone</b> <i>URL repository-filename</i>
90 </pre>
91
92 The <i>URL</i> specifies the fossil repository
@@ -107,12 +104,20 @@
104 100% complete...
105 Extra delta compression...
106 Vacuuming the database...
107 project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
108 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42
109 admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
110 </b></pre>
111
112 This <i>exampleuser</i> will be used by Fossil as the author of commits when
113 you checkin changes to the repository. It is also used by Fossil when you
114 make your repository available to others using the built-in server mode by
115 running <tt>[/help/server | fossil server]</tt> and will also be used when
116 running <tt>[/help/ui | fossil ui]</tt> to view the repository through
117 the Fossil UI. See the quick start topic for setting up a
118 <a href="#server">server</a> for more details.
119
120 If the remote repository requires a login, include a
121 userid in the URL like this:
122
123 <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>
@@ -153,26 +158,23 @@
158
159 <h2 id="checkout">Checking Out A Local Tree</h2>
160
161 To work on a project in fossil, you need to check out a local
162 copy of the source tree. Create the directory you want to be
163 the root of your tree, <tt>cd</tt> into that directory, and then:
 
164
165 <pre><b>fossil open</b> <i>repository-filename</i></pre>
166
167 For example:
168
169 <pre><b>fossil open ../myclone.fossil
170 BUILD.txt
171 COPYRIGHT-BSD2.txt
172 README.md
173
174 </tt></b></pre>
175
 
 
176 This leaves you with the newest version of the tree
177 checked out.
178 From anywhere underneath the root of your local tree, you
179 can type commands like the following to find out the status of
180 your local tree:
@@ -320,41 +322,60 @@
322
323 This will get you started on identifying checkins. The
324 <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
325 how timestamps can also be used.
326
327 <h2 id="config">Accessing Your Local Repository's Web User Interface</h2>
328
329 After you create a new repository, you usually want to do some local
330 configuration. This is most easily accomplished by firing up the Fossil
331 UI:
 
 
332
333 <pre>
334 <b>fossil ui</b> <i>repository-filename</i>
335 </pre>
336
337 You can shorten that to just [/help/ui | <b>fossil ui</b>]
338 if you are inside a checked-out local tree.
339
340 This command starts an internal web server, after which Fossil
341 automatically launches your default browser, pointed at itself,
342 presenting a special view of the repository, its web user interface.
343
344 You may override Fossil's logic for selecting the default browser so:
345
346 <pre>
347 <b>fossil setting web-browser</b> <i>path-to-web-browser</i>
348 </pre>
349
350 When launched this way, Fossil binds its internal web server to the IP
351 loopback address, 127.0.0.1, which it treats specially, bypassing all
352 user controls, effectively giving visitors the
353 [./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].
354
355 Why is that a good idea, you ask? Because it is a safe
356 presumption that only someone with direct file access to the repository
357 database file could be using the resulting web interface. Anyone who can
358 modify the repo DB directly could give themselves any and all access
359 with a SQL query, or even by direct file manipulation; no amount of
360 access control matters to such a user.
361
362 (Contrast the [#server | many <i>other</i> ways] of setting Fossil up
363 as an HTTP server, where the repo DB is on the other side of the HTTP
364 server wall, inaccessible by all means other than Fossil's own
365 mediation. For this reason, the "localhost bypasses access control"
366 policy does <i>not</i> apply to these other interfaces. That is a very
367 good thing, since without this difference in policy, it would be unsafe
368 to bind a [/help?cmd=server | <b>fossil server</b>] instance to
369 localhost on a high-numbered port and then reverse-proxy it out to the
370 world via HTTPS, a practice this author does engage in, with confidence.)
371
372 Once you are finished configuring Fossil, you may safely Control-C out
373 of the <b>fossil&nbsp;ui</b> command to shut down this privileged
374 built-in web server. Moreover, you may by grace of SQLite do this <i>at
375 any time</i>: all changes are either committed durably to the repo DB or
376 rolled back, in their totality. This includes configuration changes.
377
378 <h2 id="sharing">Sharing Changes</h2>
379
380 When [./concepts.wiki#workflow|autosync] is turned off,
381 the changes you [/help/commit | commit] are only
@@ -464,55 +485,44 @@
485 level of undo/redo.
486
487
488 <h2 id="server">Setting Up A Server</h2>
489
490 In addition to the inward-facing <b>fossil ui</b> mode covered [#config
491 | above], Fossil can also act as an outward-facing web server:
492
493 <pre>
494 <b>[/help/server | fossil server]</b> <i>repository-filename</i>
 
495 </pre>
496
497 Just as with <b>fossil ui</b>, you may omit the
498 <i>repository-filename</i> parameter when running this from within an open
499 check-out.
500
501 <i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
502 interfaces by default in this mode, and it enforces the configured
503 [./caps/ | role-based access controls]. Further, because it is meant to
504 provide external web service, it doesn't try to launch a local web
505 browser pointing to a "Fossil UI" presentation; external visitors see
506 your repository's configured home page instead.
507
508 To serve varying needs, there are additional ways to serve a Fossil repo
509 to external users:
 
 
 
 
 
 
 
 
510
511 <ul>
512 <li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
513 self-hosting repositories]
514 <li>[./server/any/scgi.md|SCGI]
515 <li>[./server/any/inetd.md|inetd]
516 <li>[./server/debian/service.md|systemd]
 
 
517 </ul>
518
519 …along with [./server/#matrix | several other options].
520
521 We recommend that you read the [./server/whyuseaserver.wiki | Benefits
522 of a Fossil Server] article, because you might <i>need</i> to do this
523 and not yet know it.
 
 
 
524
525 <h2 id="proxy">HTTP Proxies</h2>
526
527 If you are behind a restrictive firewall that requires you to use
528 an HTTP proxy to reach the internet, then you can configure the proxy
529
--- www/server/debian/service.md
+++ www/server/debian/service.md
@@ -52,11 +52,11 @@
5252
suitable for sharing a Fossil repo to a workgroup on a private LAN.
5353
5454
To do this, write the following in
5555
`~/.local/share/systemd/user/fossil.service`:
5656
57
-```dosini
57
+> ```dosini
5858
[Unit]
5959
Description=Fossil user server
6060
After=network-online.target
6161
6262
[Service]
@@ -164,11 +164,11 @@
164164
It’s more complicated, but it has some nice properties.
165165
166166
We first need to define the privileged socket listener by writing
167167
`/etc/systemd/system/fossil.socket`:
168168
169
-```dosini
169
+> ```dosini
170170
[Unit]
171171
Description=Fossil socket
172172
173173
[Socket]
174174
Accept=yes
@@ -189,11 +189,11 @@
189189
documentation](../any/inetd.md).
190190
191191
Next, create the service definition file in that same directory as
192192
`[email protected]`:
193193
194
-```dosini
194
+> ```dosini
195195
[Unit]
196196
Description=Fossil socket server
197197
After=network-online.target
198198
199199
[Service]
200200
--- www/server/debian/service.md
+++ www/server/debian/service.md
@@ -52,11 +52,11 @@
52 suitable for sharing a Fossil repo to a workgroup on a private LAN.
53
54 To do this, write the following in
55 `~/.local/share/systemd/user/fossil.service`:
56
57 ```dosini
58 [Unit]
59 Description=Fossil user server
60 After=network-online.target
61
62 [Service]
@@ -164,11 +164,11 @@
164 It’s more complicated, but it has some nice properties.
165
166 We first need to define the privileged socket listener by writing
167 `/etc/systemd/system/fossil.socket`:
168
169 ```dosini
170 [Unit]
171 Description=Fossil socket
172
173 [Socket]
174 Accept=yes
@@ -189,11 +189,11 @@
189 documentation](../any/inetd.md).
190
191 Next, create the service definition file in that same directory as
192 `[email protected]`:
193
194 ```dosini
195 [Unit]
196 Description=Fossil socket server
197 After=network-online.target
198
199 [Service]
200
--- www/server/debian/service.md
+++ www/server/debian/service.md
@@ -52,11 +52,11 @@
52 suitable for sharing a Fossil repo to a workgroup on a private LAN.
53
54 To do this, write the following in
55 `~/.local/share/systemd/user/fossil.service`:
56
57 > ```dosini
58 [Unit]
59 Description=Fossil user server
60 After=network-online.target
61
62 [Service]
@@ -164,11 +164,11 @@
164 It’s more complicated, but it has some nice properties.
165
166 We first need to define the privileged socket listener by writing
167 `/etc/systemd/system/fossil.socket`:
168
169 > ```dosini
170 [Unit]
171 Description=Fossil socket
172
173 [Socket]
174 Accept=yes
@@ -189,11 +189,11 @@
189 documentation](../any/inetd.md).
190
191 Next, create the service definition file in that same directory as
192 `[email protected]`:
193
194 > ```dosini
195 [Unit]
196 Description=Fossil socket server
197 After=network-online.target
198
199 [Service]
200
+148 -28
--- www/th1.md
+++ www/th1.md
@@ -12,29 +12,43 @@
1212
time all of the test cases for SQLite were written in Tcl and Tcl could not
1313
be easily compiled on the SymbianOS. So TH1 was developed as a cut-down
1414
version of Tcl that would facilitate running the SQLite test scripts on
1515
SymbianOS.
1616
17
-Fossil was first being designed at about the same time that TH1 was
18
-being developed for testing SQLite on SymbianOS.
17
+Fossil was first being designed at about the same time.
1918
Early prototypes of Fossil were written in pure Tcl. But as the development
2019
shifted toward the use of C-code, the need arose to have a Tcl-like
2120
scripting language to help with code generation. TH1 was small and
2221
light-weight and used minimal resources and seemed ideally suited for the
2322
task.
2423
25
-The name "TH1" stands "Test Harness 1", since that was its original purpose.
24
+The name "TH1" stands for "Test Harness 1",
25
+since its original purpose was to serve as testing harness
26
+for SQLite.
2627
27
-Overview
---------
28
+Where TH1 Is Used In Fossil
29
+---------------------------
30
+
31
+ * In the header and footer for [skins](./customskin.md)
32
+ text within `<th1>...</th1>` is run as a TH1 script.
33
+ ([example](/builtin/skins/default/header.txt))
34
+
35
+ * This display of [tickets](./bugtheory.wiki) is controlled by TH1
36
+ scripts, so that the ticket format can be customized for each
37
+ project. Administrators can visit the <b>/tktsetup</b> page in
38
+ their repositories to view and customize these scripts.
39
+ ([example usage](./custom_ticket.wiki))
40
+
41
+Overview Of The Tcl/TH1 Language
42
+--------------------------------
2843
2944
TH1 is a string-processing language. All values are strings. Any numerical
3045
operations are accomplished by converting from string to numeric, performing
3146
the computation, then converting the result back into a string. (This might
3247
seem inefficient, but it is faster than people imagine, and numeric
3348
computations do not come up very often for the kinds of work that TH1 does,
34
-so it has never been a factor.)
49
+so it has never been an issue.)
3550
3651
A TH1 script consists of a sequence of commands.
3752
Each command is terminated by the first *unescaped* newline or ";" character.
3853
The text of the command (excluding the newline or semicolon terminator)
3954
is broken into space-separated tokens. The first token is the command
@@ -68,11 +82,11 @@
6882
are removed from each token by the command parser.) The third token
6983
is the `puts "hello"`, with its whitespace and newlines. The fourth token
7084
is `else` and the fifth and last token is `puts "world"`.
7185
7286
The `if` command evaluates its first argument (the second token)
73
-as an expression, and if that expression is true, evaluates its
87
+as an expression, and if that expression is true, it evaluates its
7488
second argument (the third token) as a TH1 script.
7589
If the expression is false and the third argument is `else`, then
7690
the fourth argument is evaluated as a TH1 expression.
7791
7892
So, you see, even though the example above spans five lines, it is really
@@ -106,10 +120,49 @@
106120
$repository "" info trunk]]] end]
107121
108122
Those backslashes allow the command to wrap nicely within a standard
109123
terminal width while telling the interpreter to consider those three
110124
lines as a single command.
125
+
126
+<a id="taint"></a>Tainted And Untainted Strings
127
+-----------------------------------------------
128
+
129
+Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
130
+"tainted" and "untainted" strings. Tainted strings are strings that are
131
+derived from user inputs that might contain text that is designed to subvert
132
+the script. Untainted strings are known to come from secure sources and
133
+are assumed to contain no malicious content.
134
+
135
+Beginning with Fossil version 2.26, and depending on the value of the
136
+[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
137
+strings from being used in ways that might lead to XSS or SQL-injection
138
+attacks. This feature helps to ensure that XSS and SQL-injection
139
+vulnerabilities are not *accidentally* added to Fossil when
140
+custom TH1 scripts for headers or footers or tickets are added to a
141
+repository. Note that the tainted/untainted distinction in strings does
142
+not make it impossible to introduce XSS and SQL-injections vulnerabilities
143
+using poorly-written TH1 scripts; it just makes it more difficult and
144
+less likely to happen by accident. Developers must still consider the
145
+security implications TH1 customizations they add to Fossil, and take
146
+appropriate precautions when writing custom TH1. Peer review of TH1
147
+script changes is encouraged.
148
+
149
+In Fossil version 2.26, if the vuln-report setting is set to "block"
150
+or "fatal", the [html](#html) and [query](#query) TH1 commands will
151
+fail with an error if their argument is a tainted string. This helps
152
+to prevent XSS and SQL-injection attacks, respectively. Note that
153
+the default value of the vuln-report setting is "log", which allows those
154
+commands to continue working and only writes a warning message into the
155
+error log. <b>Future versions of Fossil may change the default value
156
+of the vuln-report setting to "block" or "fatal".</b> Fossil users
157
+with customized TH1 scripts are encouraged to audit their customizations
158
+and fix any potential vulnerabilities soon, so as to avoid breakage
159
+caused by future upgrades. <b>Future versions of Fossil might also
160
+place additional restrictions on the use of tainted strings.</b>
161
+For example, it is likely that future versions of Fossil will disallow
162
+using tainted strings as script, for example as the body of a "for"
163
+loop or of a "proc".
111164
112165
113166
Summary of Core TH1 Commands
114167
----------------------------
115168
@@ -147,10 +200,13 @@
147200
* string last NEEDLE HAYSTACK ?START-INDEX?
148201
* string match PATTERN STRING
149202
* string length STRING
150203
* string range STRING FIRST LAST
151204
* string repeat STRING COUNT
205
+ * string trim STRING
206
+ * string trimleft STRING
207
+ * string trimright STRING
152208
* unset VARNAME
153209
* uplevel ?LEVEL? SCRIPT
154210
* upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155211
156212
All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +238,10 @@
182238
* [copybtn](#copybtn)
183239
* [date](#date)
184240
* [decorate](#decorate)
185241
* [defHeader](#defHeader)
186242
* [dir](#dir)
187
- * [enable\_htmlify](#enable_htmlify)
188243
* [enable\_output](#enable_output)
189244
* [encode64](#encode64)
190245
* [getParameter](#getParameter)
191246
* [glob\_match](#glob_match)
192247
* [globalState](#globalState)
@@ -214,17 +269,19 @@
214269
* [stime](#stime)
215270
* [styleHeader](#styleHeader)
216271
* [styleFooter](#styleFooter)
217272
* [styleScript](#styleScript)
218273
* [submenu](#submenu)
274
+ * [taint](#taintCmd)
219275
* [tclEval](#tclEval)
220276
* [tclExpr](#tclExpr)
221277
* [tclInvoke](#tclInvoke)
222278
* [tclIsSafe](#tclIsSafe)
223279
* [tclMakeSafe](#tclMakeSafe)
224280
* [tclReady](#tclReady)
225281
* [trace](#trace)
282
+ * [untaint](#untaintCmd)
226283
* [unversioned content](#unversioned_content)
227284
* [unversioned list](#unversioned_list)
228285
* [utime](#utime)
229286
* [verifyCsrf](#verifyCsrf)
230287
* [verifyLogin](#verifyLogin)
@@ -413,28 +470,10 @@
413470
the files matching the pattern GLOB within CHECKIN will be returned.
414471
If DETAILS is non-zero, the result will be a list-of-lists, with each
415472
element containing at least three elements: the file name, the file
416473
size (in bytes), and the file last modification time (relative to the
417474
time zone configured for the repository).
418
-
419
-<a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
-
421
- * enable\_htmlify
422
- * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
-
424
-By default, certain output from `puts` and similar commands is escaped
425
-for HTML. The first call form returns the current state of that
426
-feature: `1` for on and `0` for off. The second call form enables
427
-(non-0) or disables (0) that feature and returns the *pre-call* state
428
-of that feature (so that a second call can pass that value to restore
429
-it to its previous state). The optional `TRACE-LABEL` argument causes
430
-the TH1 tracing output (if enabled) to add a marker when the second
431
-form of this command is invoked, and includes that label and the
432
-boolean argument's value in the trace. If tracing is disabled, that
433
-argument has no effect.
434
-
435475
436476
<a id="enable_output"></a>TH1 enable\_output Command
437477
------------------------------------------------------
438478
439479
* enable\_output BOOLEAN
@@ -527,11 +566,25 @@
527566
<a id="html"></a>TH1 html Command
528567
-----------------------------------
529568
530569
* html STRING
531570
532
-Outputs the STRING escaped for HTML.
571
+Outputs the STRING literally. It is assumed that STRING contains
572
+valid HTML, or that if STRING contains any characters that are
573
+significant to HTML (such as `<`, `>`, `'`, or `&`) have already
574
+been escaped, perhaps by the [htmlize](#htmlize) command. Use the
575
+[puts](#puts) command to output text that might contain unescaped
576
+HTML markup.
577
+
578
+**Beware of XSS attacks!** If the STRING value to the html command
579
+can be controlled by a hostile user, then he might be able to sneak
580
+in malicious HTML or Javascript which could result in a
581
+cross-site scripting (XSS) attack. Be careful that all text that
582
+in STRING that might come from user input has been sanitized by the
583
+[htmlize](#htmlize) command or similar. In recent versions of Fossil,
584
+the STRING value must be [untainted](#taint) or else the "html" command
585
+will fail.
533586
534587
<a id="htmlize"></a>TH1 htmlize Command
535588
-----------------------------------------
536589
537590
* htmlize STRING
@@ -595,12 +648,16 @@
595648
<a id="puts"></a>TH1 puts Command
596649
-----------------------------------
597650
598651
* puts STRING
599652
600
-Outputs the STRING unchanged, where "unchanged" might, depending on
601
-the context, mean "with some characters escaped for HTML."
653
+Outputs STRING. Characters within STRING that have special meaning
654
+in HTML are escaped prior to being output. Thus is it safe for STRING
655
+to be derived from user inputs. See also the [html](#html) command
656
+which behaves similarly except does not escape HTML markup. This
657
+command ("puts") is safe to use on [tainted strings](#taint), but the "html"
658
+command is not.
602659
603660
<a id="query"></a>TH1 query Command
604661
-------------------------------------
605662
606663
* query ?-nocomplain? SQL CODE
@@ -608,11 +665,44 @@
608665
Runs the SQL query given by the SQL argument. For each row in the result
609666
set, run CODE.
610667
611668
In SQL, parameters such as $var are filled in using the value of variable
612669
"var". Result values are stored in variables with the column name prior
613
-to each invocation of CODE.
670
+to each invocation of CODE. The names of the variables in which results
671
+are stored can be controlled using "AS name" clauses in the SQL. As
672
+the database will often contain content that originates from untrusted
673
+users, all result values are marked as [tainted](#taint).
674
+
675
+**Beware of SQL injections in the `query` command!**
676
+The SQL argument to the query command should always be literal SQL
677
+text enclosed in {...}. The SQL argument should never be a double-quoted
678
+string or the value of a \$variable, as those constructs can lead to
679
+an SQL Injection attack. If you need to include the values of one or
680
+more TH1 variables as part of the SQL, then put \$variable inside the
681
+{...}. The \$variable keyword will then get passed down into the SQLite
682
+parser which knows to look up the value of \$variable in the TH1 symbol
683
+table. For example:
684
+
685
+~~~
686
+ query {SELECT res FROM tab1 WHERE key=$mykey} {...}
687
+~~~
688
+
689
+SQLite will see the \$mykey token in the SQL and will know to resolve it
690
+to the value of the "mykey" TH1 variable, safely and without the possibility
691
+of SQL injection. The following is unsafe:
692
+
693
+~~~
694
+ query "SELECT res FROM tab1 WHERE key='$mykey'" {...} ;# <-- UNSAFE!
695
+~~~
696
+
697
+In this second example, TH1 does the expansion of `$mykey` prior to passing
698
+the text down into SQLite. So if `$mykey` contains a single-quote character,
699
+followed by additional hostile text, that will result in an SQL injection.
700
+
701
+To help guard against SQL-injections, recent versions of Fossil require
702
+that the SQL argument be [untainted](#taint) or else the "query" command
703
+will fail.
614704
615705
<a id="randhex"></a>TH1 randhex Command
616706
-----------------------------------------
617707
618708
* randhex N
@@ -638,10 +728,12 @@
638728
* regexp ?-nocase? ?--? exp string
639729
640730
Checks the string against the specified regular expression and returns
641731
non-zero if it matches. If the regular expression is invalid or cannot
642732
be compiled, an error will be generated.
733
+
734
+See [fossil grep](./grep.md) for details on the regexp syntax.
643735
644736
<a id="reinitialize"></a>TH1 reinitialize Command
645737
---------------------------------------------------
646738
647739
* reinitialize ?FLAGS?
@@ -741,10 +833,24 @@
741833
742834
* submenu link LABEL URL
743835
744836
Add hyperlink to the submenu of the current page.
745837
838
+<a id="taintCmd"></a>TH1 taint Command
839
+-----------------------------------------
840
+
841
+ * taint STRING
842
+
843
+This command returns a copy of STRING that has been marked as
844
+[tainted](#taint). Tainted strings are strings which might be
845
+controlled by an attacker and might contain hostile inputs and
846
+are thus unsafe to use in certain contexts. For example, tainted
847
+strings should not be output as part of a webpage as they might
848
+contain rogue HTML or Javascript that could lead to an XSS
849
+vulnerability. Similarly, tainted strings should not be run as
850
+SQL since they might contain an SQL-injection vulerability.
851
+
746852
<a id="tclEval"></a>TH1 tclEval Command
747853
-----------------------------------------
748854
749855
**This command requires the Tcl integration feature.**
750856
@@ -812,10 +918,22 @@
812918
813919
* trace STRING
814920
815921
Generates a TH1 trace message if TH1 tracing is enabled.
816922
923
+<a id="untaintCmd"></a>TH1 taint Command
924
+-----------------------------------------
925
+
926
+ * untaint STRING
927
+
928
+This command returns a copy of STRING that has been marked as
929
+[untainted](#taint). Untainted strings are strings which are
930
+believed to be free of potentially hostile content. Use this
931
+command with caution, as it overwrites the tainted-string protection
932
+mechanisms that are built into TH1. If you do not understand all
933
+the implications of executing this command, then do not use it.
934
+
817935
<a id="unversioned_content"></a>TH1 unversioned content Command
818936
-----------------------------------------------------------------
819937
820938
* unversioned content FILENAME
821939
822940
--- www/th1.md
+++ www/th1.md
@@ -12,29 +12,43 @@
12 time all of the test cases for SQLite were written in Tcl and Tcl could not
13 be easily compiled on the SymbianOS. So TH1 was developed as a cut-down
14 version of Tcl that would facilitate running the SQLite test scripts on
15 SymbianOS.
16
17 Fossil was first being designed at about the same time that TH1 was
18 being developed for testing SQLite on SymbianOS.
19 Early prototypes of Fossil were written in pure Tcl. But as the development
20 shifted toward the use of C-code, the need arose to have a Tcl-like
21 scripting language to help with code generation. TH1 was small and
22 light-weight and used minimal resources and seemed ideally suited for the
23 task.
24
25 The name "TH1" stands "Test Harness 1", since that was its original purpose.
 
 
26
27 Overview
---------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
29 TH1 is a string-processing language. All values are strings. Any numerical
30 operations are accomplished by converting from string to numeric, performing
31 the computation, then converting the result back into a string. (This might
32 seem inefficient, but it is faster than people imagine, and numeric
33 computations do not come up very often for the kinds of work that TH1 does,
34 so it has never been a factor.)
35
36 A TH1 script consists of a sequence of commands.
37 Each command is terminated by the first *unescaped* newline or ";" character.
38 The text of the command (excluding the newline or semicolon terminator)
39 is broken into space-separated tokens. The first token is the command
@@ -68,11 +82,11 @@
68 are removed from each token by the command parser.) The third token
69 is the `puts "hello"`, with its whitespace and newlines. The fourth token
70 is `else` and the fifth and last token is `puts "world"`.
71
72 The `if` command evaluates its first argument (the second token)
73 as an expression, and if that expression is true, evaluates its
74 second argument (the third token) as a TH1 script.
75 If the expression is false and the third argument is `else`, then
76 the fourth argument is evaluated as a TH1 expression.
77
78 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +120,49 @@
106 $repository "" info trunk]]] end]
107
108 Those backslashes allow the command to wrap nicely within a standard
109 terminal width while telling the interpreter to consider those three
110 lines as a single command.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
112
113 Summary of Core TH1 Commands
114 ----------------------------
115
@@ -147,10 +200,13 @@
147 * string last NEEDLE HAYSTACK ?START-INDEX?
148 * string match PATTERN STRING
149 * string length STRING
150 * string range STRING FIRST LAST
151 * string repeat STRING COUNT
 
 
 
152 * unset VARNAME
153 * uplevel ?LEVEL? SCRIPT
154 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
155
156 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +238,10 @@
182 * [copybtn](#copybtn)
183 * [date](#date)
184 * [decorate](#decorate)
185 * [defHeader](#defHeader)
186 * [dir](#dir)
187 * [enable\_htmlify](#enable_htmlify)
188 * [enable\_output](#enable_output)
189 * [encode64](#encode64)
190 * [getParameter](#getParameter)
191 * [glob\_match](#glob_match)
192 * [globalState](#globalState)
@@ -214,17 +269,19 @@
214 * [stime](#stime)
215 * [styleHeader](#styleHeader)
216 * [styleFooter](#styleFooter)
217 * [styleScript](#styleScript)
218 * [submenu](#submenu)
 
219 * [tclEval](#tclEval)
220 * [tclExpr](#tclExpr)
221 * [tclInvoke](#tclInvoke)
222 * [tclIsSafe](#tclIsSafe)
223 * [tclMakeSafe](#tclMakeSafe)
224 * [tclReady](#tclReady)
225 * [trace](#trace)
 
226 * [unversioned content](#unversioned_content)
227 * [unversioned list](#unversioned_list)
228 * [utime](#utime)
229 * [verifyCsrf](#verifyCsrf)
230 * [verifyLogin](#verifyLogin)
@@ -413,28 +470,10 @@
413 the files matching the pattern GLOB within CHECKIN will be returned.
414 If DETAILS is non-zero, the result will be a list-of-lists, with each
415 element containing at least three elements: the file name, the file
416 size (in bytes), and the file last modification time (relative to the
417 time zone configured for the repository).
418
419 <a id="enable_htmlify"></a>TH1 enable\_htmlify Command
-------------------------------------------------------
420
421 * enable\_htmlify
422 * enable\_htmlify ?TRACE-LABEL? BOOLEAN
423
424 By default, certain output from `puts` and similar commands is escaped
425 for HTML. The first call form returns the current state of that
426 feature: `1` for on and `0` for off. The second call form enables
427 (non-0) or disables (0) that feature and returns the *pre-call* state
428 of that feature (so that a second call can pass that value to restore
429 it to its previous state). The optional `TRACE-LABEL` argument causes
430 the TH1 tracing output (if enabled) to add a marker when the second
431 form of this command is invoked, and includes that label and the
432 boolean argument's value in the trace. If tracing is disabled, that
433 argument has no effect.
434
435
436 <a id="enable_output"></a>TH1 enable\_output Command
437 ------------------------------------------------------
438
439 * enable\_output BOOLEAN
@@ -527,11 +566,25 @@
527 <a id="html"></a>TH1 html Command
528 -----------------------------------
529
530 * html STRING
531
532 Outputs the STRING escaped for HTML.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
534 <a id="htmlize"></a>TH1 htmlize Command
535 -----------------------------------------
536
537 * htmlize STRING
@@ -595,12 +648,16 @@
595 <a id="puts"></a>TH1 puts Command
596 -----------------------------------
597
598 * puts STRING
599
600 Outputs the STRING unchanged, where "unchanged" might, depending on
601 the context, mean "with some characters escaped for HTML."
 
 
 
 
602
603 <a id="query"></a>TH1 query Command
604 -------------------------------------
605
606 * query ?-nocomplain? SQL CODE
@@ -608,11 +665,44 @@
608 Runs the SQL query given by the SQL argument. For each row in the result
609 set, run CODE.
610
611 In SQL, parameters such as $var are filled in using the value of variable
612 "var". Result values are stored in variables with the column name prior
613 to each invocation of CODE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
615 <a id="randhex"></a>TH1 randhex Command
616 -----------------------------------------
617
618 * randhex N
@@ -638,10 +728,12 @@
638 * regexp ?-nocase? ?--? exp string
639
640 Checks the string against the specified regular expression and returns
641 non-zero if it matches. If the regular expression is invalid or cannot
642 be compiled, an error will be generated.
 
 
643
644 <a id="reinitialize"></a>TH1 reinitialize Command
645 ---------------------------------------------------
646
647 * reinitialize ?FLAGS?
@@ -741,10 +833,24 @@
741
742 * submenu link LABEL URL
743
744 Add hyperlink to the submenu of the current page.
745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
746 <a id="tclEval"></a>TH1 tclEval Command
747 -----------------------------------------
748
749 **This command requires the Tcl integration feature.**
750
@@ -812,10 +918,22 @@
812
813 * trace STRING
814
815 Generates a TH1 trace message if TH1 tracing is enabled.
816
 
 
 
 
 
 
 
 
 
 
 
 
817 <a id="unversioned_content"></a>TH1 unversioned content Command
818 -----------------------------------------------------------------
819
820 * unversioned content FILENAME
821
822
--- www/th1.md
+++ www/th1.md
@@ -12,29 +12,43 @@
12 time all of the test cases for SQLite were written in Tcl and Tcl could not
13 be easily compiled on the SymbianOS. So TH1 was developed as a cut-down
14 version of Tcl that would facilitate running the SQLite test scripts on
15 SymbianOS.
16
17 Fossil was first being designed at about the same time.
 
18 Early prototypes of Fossil were written in pure Tcl. But as the development
19 shifted toward the use of C-code, the need arose to have a Tcl-like
20 scripting language to help with code generation. TH1 was small and
21 light-weight and used minimal resources and seemed ideally suited for the
22 task.
23
24 The name "TH1" stands for "Test Harness 1",
25 since its original purpose was to serve as testing harness
26 for SQLite.
27
 
---------
28 Where TH1 Is Used In Fossil
29 ---------------------------
30
31 * In the header and footer for [skins](./customskin.md)
32 text within `<th1>...</th1>` is run as a TH1 script.
33 ([example](/builtin/skins/default/header.txt))
34
35 * This display of [tickets](./bugtheory.wiki) is controlled by TH1
36 scripts, so that the ticket format can be customized for each
37 project. Administrators can visit the <b>/tktsetup</b> page in
38 their repositories to view and customize these scripts.
39 ([example usage](./custom_ticket.wiki))
40
41 Overview Of The Tcl/TH1 Language
42 --------------------------------
43
44 TH1 is a string-processing language. All values are strings. Any numerical
45 operations are accomplished by converting from string to numeric, performing
46 the computation, then converting the result back into a string. (This might
47 seem inefficient, but it is faster than people imagine, and numeric
48 computations do not come up very often for the kinds of work that TH1 does,
49 so it has never been an issue.)
50
51 A TH1 script consists of a sequence of commands.
52 Each command is terminated by the first *unescaped* newline or ";" character.
53 The text of the command (excluding the newline or semicolon terminator)
54 is broken into space-separated tokens. The first token is the command
@@ -68,11 +82,11 @@
82 are removed from each token by the command parser.) The third token
83 is the `puts "hello"`, with its whitespace and newlines. The fourth token
84 is `else` and the fifth and last token is `puts "world"`.
85
86 The `if` command evaluates its first argument (the second token)
87 as an expression, and if that expression is true, it evaluates its
88 second argument (the third token) as a TH1 script.
89 If the expression is false and the third argument is `else`, then
90 the fourth argument is evaluated as a TH1 expression.
91
92 So, you see, even though the example above spans five lines, it is really
@@ -106,10 +120,49 @@
120 $repository "" info trunk]]] end]
121
122 Those backslashes allow the command to wrap nicely within a standard
123 terminal width while telling the interpreter to consider those three
124 lines as a single command.
125
126 <a id="taint"></a>Tainted And Untainted Strings
127 -----------------------------------------------
128
129 Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
130 "tainted" and "untainted" strings. Tainted strings are strings that are
131 derived from user inputs that might contain text that is designed to subvert
132 the script. Untainted strings are known to come from secure sources and
133 are assumed to contain no malicious content.
134
135 Beginning with Fossil version 2.26, and depending on the value of the
136 [vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
137 strings from being used in ways that might lead to XSS or SQL-injection
138 attacks. This feature helps to ensure that XSS and SQL-injection
139 vulnerabilities are not *accidentally* added to Fossil when
140 custom TH1 scripts for headers or footers or tickets are added to a
141 repository. Note that the tainted/untainted distinction in strings does
142 not make it impossible to introduce XSS and SQL-injections vulnerabilities
143 using poorly-written TH1 scripts; it just makes it more difficult and
144 less likely to happen by accident. Developers must still consider the
145 security implications TH1 customizations they add to Fossil, and take
146 appropriate precautions when writing custom TH1. Peer review of TH1
147 script changes is encouraged.
148
149 In Fossil version 2.26, if the vuln-report setting is set to "block"
150 or "fatal", the [html](#html) and [query](#query) TH1 commands will
151 fail with an error if their argument is a tainted string. This helps
152 to prevent XSS and SQL-injection attacks, respectively. Note that
153 the default value of the vuln-report setting is "log", which allows those
154 commands to continue working and only writes a warning message into the
155 error log. <b>Future versions of Fossil may change the default value
156 of the vuln-report setting to "block" or "fatal".</b> Fossil users
157 with customized TH1 scripts are encouraged to audit their customizations
158 and fix any potential vulnerabilities soon, so as to avoid breakage
159 caused by future upgrades. <b>Future versions of Fossil might also
160 place additional restrictions on the use of tainted strings.</b>
161 For example, it is likely that future versions of Fossil will disallow
162 using tainted strings as script, for example as the body of a "for"
163 loop or of a "proc".
164
165
166 Summary of Core TH1 Commands
167 ----------------------------
168
@@ -147,10 +200,13 @@
200 * string last NEEDLE HAYSTACK ?START-INDEX?
201 * string match PATTERN STRING
202 * string length STRING
203 * string range STRING FIRST LAST
204 * string repeat STRING COUNT
205 * string trim STRING
206 * string trimleft STRING
207 * string trimright STRING
208 * unset VARNAME
209 * uplevel ?LEVEL? SCRIPT
210 * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?
211
212 All of the above commands work as in the original Tcl. Refer to the
@@ -182,11 +238,10 @@
238 * [copybtn](#copybtn)
239 * [date](#date)
240 * [decorate](#decorate)
241 * [defHeader](#defHeader)
242 * [dir](#dir)
 
243 * [enable\_output](#enable_output)
244 * [encode64](#encode64)
245 * [getParameter](#getParameter)
246 * [glob\_match](#glob_match)
247 * [globalState](#globalState)
@@ -214,17 +269,19 @@
269 * [stime](#stime)
270 * [styleHeader](#styleHeader)
271 * [styleFooter](#styleFooter)
272 * [styleScript](#styleScript)
273 * [submenu](#submenu)
274 * [taint](#taintCmd)
275 * [tclEval](#tclEval)
276 * [tclExpr](#tclExpr)
277 * [tclInvoke](#tclInvoke)
278 * [tclIsSafe](#tclIsSafe)
279 * [tclMakeSafe](#tclMakeSafe)
280 * [tclReady](#tclReady)
281 * [trace](#trace)
282 * [untaint](#untaintCmd)
283 * [unversioned content](#unversioned_content)
284 * [unversioned list](#unversioned_list)
285 * [utime](#utime)
286 * [verifyCsrf](#verifyCsrf)
287 * [verifyLogin](#verifyLogin)
@@ -413,28 +470,10 @@
470 the files matching the pattern GLOB within CHECKIN will be returned.
471 If DETAILS is non-zero, the result will be a list-of-lists, with each
472 element containing at least three elements: the file name, the file
473 size (in bytes), and the file last modification time (relative to the
474 time zone configured for the repository).
 
 
-------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
476 <a id="enable_output"></a>TH1 enable\_output Command
477 ------------------------------------------------------
478
479 * enable\_output BOOLEAN
@@ -527,11 +566,25 @@
566 <a id="html"></a>TH1 html Command
567 -----------------------------------
568
569 * html STRING
570
571 Outputs the STRING literally. It is assumed that STRING contains
572 valid HTML, or that if STRING contains any characters that are
573 significant to HTML (such as `<`, `>`, `'`, or `&`) have already
574 been escaped, perhaps by the [htmlize](#htmlize) command. Use the
575 [puts](#puts) command to output text that might contain unescaped
576 HTML markup.
577
578 **Beware of XSS attacks!** If the STRING value to the html command
579 can be controlled by a hostile user, then he might be able to sneak
580 in malicious HTML or Javascript which could result in a
581 cross-site scripting (XSS) attack. Be careful that all text that
582 in STRING that might come from user input has been sanitized by the
583 [htmlize](#htmlize) command or similar. In recent versions of Fossil,
584 the STRING value must be [untainted](#taint) or else the "html" command
585 will fail.
586
587 <a id="htmlize"></a>TH1 htmlize Command
588 -----------------------------------------
589
590 * htmlize STRING
@@ -595,12 +648,16 @@
648 <a id="puts"></a>TH1 puts Command
649 -----------------------------------
650
651 * puts STRING
652
653 Outputs STRING. Characters within STRING that have special meaning
654 in HTML are escaped prior to being output. Thus is it safe for STRING
655 to be derived from user inputs. See also the [html](#html) command
656 which behaves similarly except does not escape HTML markup. This
657 command ("puts") is safe to use on [tainted strings](#taint), but the "html"
658 command is not.
659
660 <a id="query"></a>TH1 query Command
661 -------------------------------------
662
663 * query ?-nocomplain? SQL CODE
@@ -608,11 +665,44 @@
665 Runs the SQL query given by the SQL argument. For each row in the result
666 set, run CODE.
667
668 In SQL, parameters such as $var are filled in using the value of variable
669 "var". Result values are stored in variables with the column name prior
670 to each invocation of CODE. The names of the variables in which results
671 are stored can be controlled using "AS name" clauses in the SQL. As
672 the database will often contain content that originates from untrusted
673 users, all result values are marked as [tainted](#taint).
674
675 **Beware of SQL injections in the `query` command!**
676 The SQL argument to the query command should always be literal SQL
677 text enclosed in {...}. The SQL argument should never be a double-quoted
678 string or the value of a \$variable, as those constructs can lead to
679 an SQL Injection attack. If you need to include the values of one or
680 more TH1 variables as part of the SQL, then put \$variable inside the
681 {...}. The \$variable keyword will then get passed down into the SQLite
682 parser which knows to look up the value of \$variable in the TH1 symbol
683 table. For example:
684
685 ~~~
686 query {SELECT res FROM tab1 WHERE key=$mykey} {...}
687 ~~~
688
689 SQLite will see the \$mykey token in the SQL and will know to resolve it
690 to the value of the "mykey" TH1 variable, safely and without the possibility
691 of SQL injection. The following is unsafe:
692
693 ~~~
694 query "SELECT res FROM tab1 WHERE key='$mykey'" {...} ;# <-- UNSAFE!
695 ~~~
696
697 In this second example, TH1 does the expansion of `$mykey` prior to passing
698 the text down into SQLite. So if `$mykey` contains a single-quote character,
699 followed by additional hostile text, that will result in an SQL injection.
700
701 To help guard against SQL-injections, recent versions of Fossil require
702 that the SQL argument be [untainted](#taint) or else the "query" command
703 will fail.
704
705 <a id="randhex"></a>TH1 randhex Command
706 -----------------------------------------
707
708 * randhex N
@@ -638,10 +728,12 @@
728 * regexp ?-nocase? ?--? exp string
729
730 Checks the string against the specified regular expression and returns
731 non-zero if it matches. If the regular expression is invalid or cannot
732 be compiled, an error will be generated.
733
734 See [fossil grep](./grep.md) for details on the regexp syntax.
735
736 <a id="reinitialize"></a>TH1 reinitialize Command
737 ---------------------------------------------------
738
739 * reinitialize ?FLAGS?
@@ -741,10 +833,24 @@
833
834 * submenu link LABEL URL
835
836 Add hyperlink to the submenu of the current page.
837
838 <a id="taintCmd"></a>TH1 taint Command
839 -----------------------------------------
840
841 * taint STRING
842
843 This command returns a copy of STRING that has been marked as
844 [tainted](#taint). Tainted strings are strings which might be
845 controlled by an attacker and might contain hostile inputs and
846 are thus unsafe to use in certain contexts. For example, tainted
847 strings should not be output as part of a webpage as they might
848 contain rogue HTML or Javascript that could lead to an XSS
849 vulnerability. Similarly, tainted strings should not be run as
850 SQL since they might contain an SQL-injection vulerability.
851
852 <a id="tclEval"></a>TH1 tclEval Command
853 -----------------------------------------
854
855 **This command requires the Tcl integration feature.**
856
@@ -812,10 +918,22 @@
918
919 * trace STRING
920
921 Generates a TH1 trace message if TH1 tracing is enabled.
922
923 <a id="untaintCmd"></a>TH1 taint Command
924 -----------------------------------------
925
926 * untaint STRING
927
928 This command returns a copy of STRING that has been marked as
929 [untainted](#taint). Untainted strings are strings which are
930 believed to be free of potentially hostile content. Use this
931 command with caution, as it overwrites the tainted-string protection
932 mechanisms that are built into TH1. If you do not understand all
933 the implications of executing this command, then do not use it.
934
935 <a id="unversioned_content"></a>TH1 unversioned content Command
936 -----------------------------------------------------------------
937
938 * unversioned content FILENAME
939
940

Keyboard Shortcuts

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