Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Merge the latest trunk enhancements into the quickfilter branch. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | quickfilter |
| Files: | files | file ages | folders |
| SHA3-256: |
e14c75676c3eb42afc4af2408c8dce25 |
| User & Date: | drh 2025-04-25 11:01:39.497 |
Context
|
2025-04-25
| ||
| 11:50 | readd documentation for FOSSIL_REPOLIST_QUICKFILTER ... (check-in: 1a84e663e9 user: jkosche tags: quickfilter) | |
| 11:01 | Merge the latest trunk enhancements into the quickfilter branch. ... (check-in: e14c75676c user: drh tags: quickfilter) | |
| 00:00 | change input field to search, which brings clear button on some browser, as suggested in [forum:forumpost/005643ff358e5d34|forum post] ... (check-in: fa31d18bf4 user: jkosche tags: quickfilter) | |
|
2025-04-24
| ||
| 19:42 | Block an infinite loop in Th_ReportTaint() that can occur when the vuln-report setting is "fatal" and the error happens again while generating the fatal error page. ... (check-in: 76f1ddb6c2 user: drh tags: trunk) | |
Changes
Makefile.in became a regular file.
| ︙ | ︙ |
Changes to extsrc/shell.c.
| ︙ | ︙ | |||
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 |
}else{
i++;
}
}
return n;
}
#endif
/*
** Output string zUtf to stdout as w characters. If w is negative,
** then right-justify the text. W is the width in UTF-8 characters, not
** in bytes. This is different from the %*.*s specification in printf
** since with %*.*s the width is measured in bytes, not characters.
**
** Take into account zero-width and double-width Unicode characters.
** In other words, a zero-width character does not count toward the
** the w limit. A double-width character counts as two.
*/
static void utf8_width_print(FILE *out, int w, const char *zUtf){
const unsigned char *a = (const unsigned char*)zUtf;
unsigned char c;
int i = 0;
int n = 0;
int aw = w<0 ? -w : w;
if( zUtf==0 ) zUtf = "";
while( (c = a[i])!=0 ){
if( (c&0xc0)==0xc0 ){
int u;
int len = decodeUtf8(a+i, &u);
int x = cli_wcwidth(u);
if( x+n>aw ){
break;
}
i += len;
n += x;
}else if( n>=aw ){
break;
}else{
n++;
i++;
}
}
| > > > > > > > > > > > > > > > > > > > > | 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
}else{
i++;
}
}
return n;
}
#endif
/*
** Check to see if z[] is a valid VT100 escape. If it is, then
** return the number of bytes in the escape sequence. Return 0 if
** z[] is not a VT100 escape.
**
** This routine assumes that z[0] is \033 (ESC).
*/
static int isVt100(const unsigned char *z){
int i;
if( z[1]!='[' ) return 0;
i = 2;
while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
if( z[i]<0x40 || z[i]>0x7e ) return 0;
return i+1;
}
/*
** Output string zUtf to stdout as w characters. If w is negative,
** then right-justify the text. W is the width in UTF-8 characters, not
** in bytes. This is different from the %*.*s specification in printf
** since with %*.*s the width is measured in bytes, not characters.
**
** Take into account zero-width and double-width Unicode characters.
** In other words, a zero-width character does not count toward the
** the w limit. A double-width character counts as two.
*/
static void utf8_width_print(FILE *out, int w, const char *zUtf){
const unsigned char *a = (const unsigned char*)zUtf;
unsigned char c;
int i = 0;
int n = 0;
int k;
int aw = w<0 ? -w : w;
if( zUtf==0 ) zUtf = "";
while( (c = a[i])!=0 ){
if( (c&0xc0)==0xc0 ){
int u;
int len = decodeUtf8(a+i, &u);
int x = cli_wcwidth(u);
if( x+n>aw ){
break;
}
i += len;
n += x;
}else if( c==0x1b && (k = isVt100(&a[i]))>0 ){
i += k;
}else if( n>=aw ){
break;
}else{
n++;
i++;
}
}
|
| ︙ | ︙ | |||
6268 6269 6270 6271 6272 6273 6274 | ** CREATE TABLE generate_series( ** value, ** start HIDDEN, ** stop HIDDEN, ** step HIDDEN ** ); ** | | < | 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 | ** CREATE TABLE generate_series( ** value, ** start HIDDEN, ** stop HIDDEN, ** step HIDDEN ** ); ** ** The virtual table also has a rowid which is an alias for the value. ** ** Function arguments in queries against this virtual table are translated ** into equality constraints against successive hidden columns. In other ** words, the following pairs of queries are equivalent to each other: ** ** SELECT * FROM generate_series(0,100,5); ** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; |
| ︙ | ︙ | |||
6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 | ** */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #include <assert.h> #include <string.h> #include <limits.h> #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Return that member of a generate_series(...) sequence whose 0-based ** index is ix. The 0th member is given by smBase. The sequence members ** progress per ix increment by smStep. */ | > | 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 | ** */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #include <assert.h> #include <string.h> #include <limits.h> #include <math.h> #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Return that member of a generate_series(...) sequence whose 0-based ** index is ix. The 0th member is given by smBase. The sequence members ** progress per ix increment by smStep. */ |
| ︙ | ︙ | |||
6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 |
sqlite3_vtab **ppVtab,
char **pzErrUnused
){
sqlite3_vtab *pNew;
int rc;
/* Column numbers */
#define SERIES_COLUMN_VALUE 0
#define SERIES_COLUMN_START 1
#define SERIES_COLUMN_STOP 2
#define SERIES_COLUMN_STEP 3
(void)pUnused;
(void)argcUnused;
| > | 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 |
sqlite3_vtab **ppVtab,
char **pzErrUnused
){
sqlite3_vtab *pNew;
int rc;
/* Column numbers */
#define SERIES_COLUMN_ROWID (-1)
#define SERIES_COLUMN_VALUE 0
#define SERIES_COLUMN_START 1
#define SERIES_COLUMN_STOP 2
#define SERIES_COLUMN_STEP 3
(void)pUnused;
(void)argcUnused;
|
| ︙ | ︙ | |||
6571 6572 6573 6574 6575 6576 6577 | #ifndef LARGEST_UINT64 #define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) #endif /* | | < | < | 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 |
#ifndef LARGEST_UINT64
#define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
#define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
#endif
/*
** The rowid is the same as the value.
*/
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
series_cursor *pCur = (series_cursor*)cur;
*pRowid = pCur->ss.iValueNow;
return SQLITE_OK;
}
/*
** Return TRUE if the cursor has been moved off of the last
** row of output.
*/
|
| ︙ | ︙ | |||
6690 6691 6692 6693 6694 6695 6696 |
}
if( idxNum & 0x3380 ){
/* Extract the maximum range of output values determined by
** constraints on the "value" column.
*/
if( idxNum & 0x0080 ){
| > > > > > > > > | > > > > > > > > > | | | | | | > > > > > > > > > | | | | | | > | 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 |
}
if( idxNum & 0x3380 ){
/* Extract the maximum range of output values determined by
** constraints on the "value" column.
*/
if( idxNum & 0x0080 ){
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( r==ceil(r) ){
iMin = iMax = (sqlite3_int64)r;
}else{
returnNoRows = 1;
}
}else{
iMin = iMax = sqlite3_value_int64(argv[i++]);
}
}else{
if( idxNum & 0x0300 ){
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( idxNum & 0x0200 && r==ceil(r) ){
iMin = (sqlite3_int64)ceil(r+1.0);
}else{
iMin = (sqlite3_int64)ceil(r);
}
}else{
iMin = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x0200 ){
if( iMin==LARGEST_INT64 ){
returnNoRows = 1;
}else{
iMin++;
}
}
}
}
if( idxNum & 0x3000 ){
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( (idxNum & 0x2000)!=0 && r==floor(r) ){
iMax = (sqlite3_int64)(r-1.0);
}else{
iMax = (sqlite3_int64)floor(r);
}
}else{
iMax = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x2000 ){
if( iMax==SMALLEST_INT64 ){
returnNoRows = 1;
}else{
iMax--;
}
}
}
}
if( iMin>iMax ){
returnNoRows = 1;
}
}
|
| ︙ | ︙ | |||
6762 6763 6764 6765 6766 6767 6768 |
}
}
for(i=0; i<argc; i++){
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
/* If any of the constraints have a NULL value, then return no rows.
| | | 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 |
}
}
for(i=0; i<argc; i++){
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
/* If any of the constraints have a NULL value, then return no rows.
** See ticket https://sqlite.org/src/info/fac496b61722daf2 */
returnNoRows = 1;
break;
}
}
if( returnNoRows ){
pCur->ss.iBase = 1;
pCur->ss.iTerm = 0;
|
| ︙ | ︙ | |||
6865 6866 6867 6868 6869 6870 6871 |
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
aIdx[4] = i;
idxNum |= 0x40;
}
continue;
}
if( pConstraint->iColumn<SERIES_COLUMN_START ){
| | > > > | 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 |
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
aIdx[4] = i;
idxNum |= 0x40;
}
continue;
}
if( pConstraint->iColumn<SERIES_COLUMN_START ){
if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
pConstraint->iColumn==SERIES_COLUMN_ROWID)
&& pConstraint->usable
){
switch( op ){
case SQLITE_INDEX_CONSTRAINT_EQ:
case SQLITE_INDEX_CONSTRAINT_IS: {
idxNum |= 0x0080;
idxNum &= ~0x3300;
aIdx[5] = i;
aIdx[6] = -1;
|
| ︙ | ︙ | |||
24195 24196 24197 24198 24199 24200 24201 |
do{
n++;
j++;
}while( (n&7)!=0 && n<mxWidth );
i++;
continue;
}
| > > > > | | | > | 24244 24245 24246 24247 24248 24249 24250 24251 24252 24253 24254 24255 24256 24257 24258 24259 24260 24261 24262 24263 24264 24265 |
do{
n++;
j++;
}while( (n&7)!=0 && n<mxWidth );
i++;
continue;
}
if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){
i += k;
j += k;
}else{
n++;
j += 3;
i++;
}
}
if( n>=mxWidth && bWordWrap ){
/* Perhaps try to back up to a better place to break the line */
for(k=i; k>i/2; k--){
if( IsSpace(z[k-1]) ) break;
}
if( k<=i/2 ){
|
| ︙ | ︙ | |||
24263 24264 24265 24266 24267 24268 24269 |
zOut[j++] = 0x90;
zOut[j++] = 0x80 + c;
break;
case SHELL_ESC_ASCII:
zOut[j++] = '^';
zOut[j++] = 0x40 + c;
break;
| | > > > > > > | > > | 24317 24318 24319 24320 24321 24322 24323 24324 24325 24326 24327 24328 24329 24330 24331 24332 24333 24334 24335 24336 24337 24338 24339 24340 24341 |
zOut[j++] = 0x90;
zOut[j++] = 0x80 + c;
break;
case SHELL_ESC_ASCII:
zOut[j++] = '^';
zOut[j++] = 0x40 + c;
break;
case SHELL_ESC_OFF: {
int nn;
if( c==0x1b && (nn = isVt100(&z[i]))>0 ){
memcpy(&zOut[j], &z[i], nn);
j += nn;
i += nn - 1;
}else{
zOut[j++] = c;
}
break;
}
}
i++;
}
zOut[j] = 0;
return (char*)zOut;
}
|
| ︙ | ︙ | |||
25402 25403 25404 25405 25406 25407 25408 25409 25410 25411 25412 25413 25414 25415 | " --new Initialize FILE to an empty database", " --nofollow Do not follow symbolic links", " --readonly Open FILE readonly", " --zip FILE is a ZIP archive", #ifndef SQLITE_SHELL_FIDDLE ".output ?FILE? Send output to FILE or stdout if FILE is omitted", " If FILE begins with '|' then open it as a pipe.", " Options:", " --bom Prefix output with a UTF8 byte-order mark", " -e Send output to the system text editor", " --plain Use text/plain for -w option", " -w Send output to a web browser", " -x Send output as CSV to a spreadsheet", #endif | > | 25464 25465 25466 25467 25468 25469 25470 25471 25472 25473 25474 25475 25476 25477 25478 | " --new Initialize FILE to an empty database", " --nofollow Do not follow symbolic links", " --readonly Open FILE readonly", " --zip FILE is a ZIP archive", #ifndef SQLITE_SHELL_FIDDLE ".output ?FILE? Send output to FILE or stdout if FILE is omitted", " If FILE begins with '|' then open it as a pipe.", " If FILE is 'off' then output is disabled.", " Options:", " --bom Prefix output with a UTF8 byte-order mark", " -e Send output to the system text editor", " --plain Use text/plain for -w option", " -w Send output to a web browser", " -x Send output as CSV to a spreadsheet", #endif |
| ︙ | ︙ | |||
27019 27020 27021 27022 27023 27024 27025 |
const u8 *aData = sqlite3_column_blob(pStmt, 1);
int seenPageLabel = 0;
for(i=0; i<pgSz; i+=16){
const u8 *aLine = aData+i;
for(j=0; j<16 && aLine[j]==0; j++){}
if( j==16 ) continue;
if( !seenPageLabel ){
| | | 27082 27083 27084 27085 27086 27087 27088 27089 27090 27091 27092 27093 27094 27095 27096 |
const u8 *aData = sqlite3_column_blob(pStmt, 1);
int seenPageLabel = 0;
for(i=0; i<pgSz; i+=16){
const u8 *aLine = aData+i;
for(j=0; j<16 && aLine[j]==0; j++){}
if( j==16 ) continue;
if( !seenPageLabel ){
sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz);
seenPageLabel = 1;
}
sqlite3_fprintf(p->out, "| %5d:", i);
for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
sqlite3_fprintf(p->out, " ");
for(j=0; j<16; j++){
unsigned char c = (unsigned char)aLine[j];
|
| ︙ | ︙ | |||
30397 30398 30399 30400 30401 30402 30403 |
&& (cli_strncmp(azArg[0], "output", n)==0
|| cli_strncmp(azArg[0], "once", n)==0))
|| (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0)
|| (c=='w' && n==3 && cli_strcmp(azArg[0],"www")==0)
){
char *zFile = 0;
int i;
| | | | | 30460 30461 30462 30463 30464 30465 30466 30467 30468 30469 30470 30471 30472 30473 30474 30475 30476 |
&& (cli_strncmp(azArg[0], "output", n)==0
|| cli_strncmp(azArg[0], "once", n)==0))
|| (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0)
|| (c=='w' && n==3 && cli_strcmp(azArg[0],"www")==0)
){
char *zFile = 0;
int i;
int eMode = 0; /* 0: .outout/.once, 'x'=.excel, 'w'=.www */
int bOnce = 0; /* 0: .output, 1: .once, 2: .excel/.www */
int bPlain = 0; /* --plain option */
static const char *zBomUtf8 = "\357\273\277";
const char *zBom = 0;
failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
if( c=='e' ){
eMode = 'x';
bOnce = 2;
|
| ︙ | ︙ | |||
30428 30429 30430 30431 30432 30433 30434 |
}else if( c=='o' && cli_strcmp(z,"-x")==0 ){
eMode = 'x'; /* spreadsheet */
}else if( c=='o' && cli_strcmp(z,"-e")==0 ){
eMode = 'e'; /* text editor */
}else if( c=='o' && cli_strcmp(z,"-w")==0 ){
eMode = 'w'; /* Web browser */
}else{
| | > > > > > > > | > | 30491 30492 30493 30494 30495 30496 30497 30498 30499 30500 30501 30502 30503 30504 30505 30506 30507 30508 30509 30510 30511 30512 30513 30514 30515 30516 30517 30518 30519 30520 |
}else if( c=='o' && cli_strcmp(z,"-x")==0 ){
eMode = 'x'; /* spreadsheet */
}else if( c=='o' && cli_strcmp(z,"-e")==0 ){
eMode = 'e'; /* text editor */
}else if( c=='o' && cli_strcmp(z,"-w")==0 ){
eMode = 'w'; /* Web browser */
}else{
sqlite3_fprintf(p->out,
"ERROR: unknown option: \"%s\". Usage:\n", azArg[i]);
showHelp(p->out, azArg[0]);
rc = 1;
goto meta_command_exit;
}
}else if( zFile==0 && eMode==0 ){
if( cli_strcmp(z, "off")==0 ){
#ifdef _WIN32
zFile = sqlite3_mprintf("nul");
#else
zFile = sqlite3_mprintf("/dev/null");
#endif
}else{
zFile = sqlite3_mprintf("%s", z);
}
if( zFile && zFile[0]=='|' ){
while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
break;
}
}else{
sqlite3_fprintf(p->out,
"ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]);
|
| ︙ | ︙ |
Changes to extsrc/sqlite3.c.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | ** the text of this file. Search for "Begin file sqlite3.h" to find the start ** of the embedded sqlite3.h header file.) Additional code files may be needed ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ** the text of this file. Search for "Begin file sqlite3.h" to find the start ** of the embedded sqlite3.h header file.) Additional code files may be needed ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in ** d22475b81c4e26ccc50f3b5626d43b32f7a2 with changes in files: ** ** */ #ifndef SQLITE_AMALGAMATION #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE |
| ︙ | ︙ | |||
448 449 450 451 452 453 454 | ** The SQLITE_VERSION_NUMBER for any given release of SQLite will also ** be larger than the release from which it is derived. Either Y will ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** ** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the | | | | 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | ** The SQLITE_VERSION_NUMBER for any given release of SQLite will also ** be larger than the release from which it is derived. Either Y will ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** ** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the ** <a href="http://fossil-scm.org/">Fossil configuration management ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID ** string contains the date and time of the check-in (UTC) and a SHA1 ** or SHA3-256 hash of the entire source tree. If the source code has ** been edited in any way since it was last checked in, then the last ** four hexadecimal digits of the hash may be modified. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.50.0" #define SQLITE_VERSION_NUMBER 3050000 #define SQLITE_SOURCE_ID "2025-04-15 21:59:38 d22475b81c4e26ccc50f3b5626d43b32f7a2de34e5a764539554665bdda735d5" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros |
| ︙ | ︙ | |||
11944 11945 11946 11947 11948 11949 11950 | ** </ul> ** ** To clarify, if this function is called and then a changeset constructed ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** | > | | | 11944 11945 11946 11947 11948 11949 11950 11951 11952 11953 11954 11955 11956 11957 11958 11959 11960 | ** </ul> ** ** To clarify, if this function is called and then a changeset constructed ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** ** Unless the call to this function is a no-op as described above, it is an ** error if database zFrom does not exist or does not contain the required ** compatible table. ** ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ |
| ︙ | ︙ | |||
19160 19161 19162 19163 19164 19165 19166 19167 19168 19169 19170 19171 19172 19173 |
unsigned isResized:1; /* True if resizeIndexObject() has been called */
unsigned isCovering:1; /* True if this is a covering index */
unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
unsigned bHasExpr:1; /* Index contains an expression, either a literal
** expression, or a reference to a VIRTUAL column */
#ifdef SQLITE_ENABLE_STAT4
int nSample; /* Number of elements in aSample[] */
int mxSample; /* Number of slots allocated to aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
| > | 19161 19162 19163 19164 19165 19166 19167 19168 19169 19170 19171 19172 19173 19174 19175 |
unsigned isResized:1; /* True if resizeIndexObject() has been called */
unsigned isCovering:1; /* True if this is a covering index */
unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
unsigned bHasExpr:1; /* Index contains an expression, either a literal
** expression, or a reference to a VIRTUAL column */
#ifdef SQLITE_ENABLE_STAT4
int nSample; /* Number of elements in aSample[] */
int mxSample; /* Number of slots allocated to aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
|
| ︙ | ︙ | |||
30268 30269 30270 30271 30272 30273 30274 30275 30276 30277 30278 30279 30280 30281 | /* ** Include the primary Windows SDK header file. */ #include "windows.h" #ifdef __CYGWIN__ # include <sys/cygwin.h> # include <errno.h> /* amalgamator: dontcache */ #endif /* ** Determine if we are dealing with Windows NT. ** ** We ought to be able to determine if we are compiling for Windows 9x or | > > | 30270 30271 30272 30273 30274 30275 30276 30277 30278 30279 30280 30281 30282 30283 30284 30285 | /* ** Include the primary Windows SDK header file. */ #include "windows.h" #ifdef __CYGWIN__ # include <sys/cygwin.h> # include <sys/stat.h> /* amalgamator: dontcache */ # include <unistd.h> /* amalgamator: dontcache */ # include <errno.h> /* amalgamator: dontcache */ #endif /* ** Determine if we are dealing with Windows NT. ** ** We ought to be able to determine if we are compiling for Windows 9x or |
| ︙ | ︙ | |||
35442 35443 35444 35445 35446 35447 35448 |
SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nByte, int nChar){
int c;
unsigned char const *z = zIn;
unsigned char const *zEnd = &z[nByte-1];
int n = 0;
if( SQLITE_UTF16NATIVE==SQLITE_UTF16LE ) z++;
| | | 35446 35447 35448 35449 35450 35451 35452 35453 35454 35455 35456 35457 35458 35459 35460 |
SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nByte, int nChar){
int c;
unsigned char const *z = zIn;
unsigned char const *zEnd = &z[nByte-1];
int n = 0;
if( SQLITE_UTF16NATIVE==SQLITE_UTF16LE ) z++;
while( n<nChar && z<=zEnd ){
c = z[0];
z += 2;
if( c>=0xd8 && c<0xdc && z<=zEnd && z[0]>=0xdc && z[0]<0xe0 ) z += 2;
n++;
}
return (int)(z-(unsigned char const *)zIn)
- (SQLITE_UTF16NATIVE==SQLITE_UTF16LE);
|
| ︙ | ︙ | |||
47724 47725 47726 47727 47728 47729 47730 |
#if SQLITE_OS_WINCE
{ "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 },
#else
{ "FileTimeToLocalFileTime", (SYSCALL)0, 0 },
#endif
| | | | 47728 47729 47730 47731 47732 47733 47734 47735 47736 47737 47738 47739 47740 47741 47742 47743 47744 47745 47746 47747 47748 47749 47750 47751 |
#if SQLITE_OS_WINCE
{ "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 },
#else
{ "FileTimeToLocalFileTime", (SYSCALL)0, 0 },
#endif
#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \
LPFILETIME))aSyscall[11].pCurrent)
#if SQLITE_OS_WINCE
{ "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 },
#else
{ "FileTimeToSystemTime", (SYSCALL)0, 0 },
#endif
#define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \
LPSYSTEMTIME))aSyscall[12].pCurrent)
{ "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 },
#define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent)
#if defined(SQLITE_WIN32_HAS_ANSI)
|
| ︙ | ︙ | |||
48013 48014 48015 48016 48017 48018 48019 |
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
{ "LockFile", (SYSCALL)LockFile, 0 },
#else
{ "LockFile", (SYSCALL)0, 0 },
#endif
| | | 48017 48018 48019 48020 48021 48022 48023 48024 48025 48026 48027 48028 48029 48030 48031 |
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
{ "LockFile", (SYSCALL)LockFile, 0 },
#else
{ "LockFile", (SYSCALL)0, 0 },
#endif
#if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI)
#define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
DWORD))aSyscall[47].pCurrent)
#endif
#if !SQLITE_OS_WINCE
{ "LockFileEx", (SYSCALL)LockFileEx, 0 },
#else
|
| ︙ | ︙ | |||
48077 48078 48079 48080 48081 48082 48083 |
{ "Sleep", (SYSCALL)0, 0 },
#endif
#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent)
{ "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 },
| | | | 48081 48082 48083 48084 48085 48086 48087 48088 48089 48090 48091 48092 48093 48094 48095 48096 48097 48098 48099 48100 48101 48102 48103 48104 |
{ "Sleep", (SYSCALL)0, 0 },
#endif
#define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent)
{ "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 },
#define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \
LPFILETIME))aSyscall[56].pCurrent)
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
{ "UnlockFile", (SYSCALL)UnlockFile, 0 },
#else
{ "UnlockFile", (SYSCALL)0, 0 },
#endif
#if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI)
#define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \
DWORD))aSyscall[57].pCurrent)
#endif
#if !SQLITE_OS_WINCE
{ "UnlockFileEx", (SYSCALL)UnlockFileEx, 0 },
#else
|
| ︙ | ︙ | |||
48314 48315 48316 48317 48318 48319 48320 48321 48322 48323 48324 48325 48326 48327 |
{ "CancelIo", (SYSCALL)CancelIo, 0 },
#else
{ "CancelIo", (SYSCALL)0, 0 },
#endif
#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent)
}; /* End of the overrideable system calls */
/*
** This is the xSetSystemCall() method of sqlite3_vfs for all of the
** "win32" VFSes. Return SQLITE_OK upon successfully updating the
** system call pointer, or SQLITE_NOTFOUND if there is no configurable
** system call named zName.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 48318 48319 48320 48321 48322 48323 48324 48325 48326 48327 48328 48329 48330 48331 48332 48333 48334 48335 48336 48337 48338 48339 48340 48341 48342 48343 48344 48345 48346 48347 48348 48349 48350 48351 48352 48353 48354 48355 48356 48357 48358 48359 48360 48361 48362 48363 48364 48365 48366 48367 48368 48369 48370 48371 48372 48373 48374 48375 48376 48377 48378 48379 48380 48381 48382 48383 48384 48385 48386 48387 48388 |
{ "CancelIo", (SYSCALL)CancelIo, 0 },
#else
{ "CancelIo", (SYSCALL)0, 0 },
#endif
#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent)
#if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32)
{ "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 },
#else
{ "GetModuleHandleW", (SYSCALL)0, 0 },
#endif
#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent)
#ifndef _WIN32
{ "getenv", (SYSCALL)getenv, 0 },
#else
{ "getenv", (SYSCALL)0, 0 },
#endif
#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent)
#ifndef _WIN32
{ "getcwd", (SYSCALL)getcwd, 0 },
#else
{ "getcwd", (SYSCALL)0, 0 },
#endif
#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent)
#ifndef _WIN32
{ "readlink", (SYSCALL)readlink, 0 },
#else
{ "readlink", (SYSCALL)0, 0 },
#endif
#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent)
#ifndef _WIN32
{ "lstat", (SYSCALL)lstat, 0 },
#else
{ "lstat", (SYSCALL)0, 0 },
#endif
#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent)
#ifndef _WIN32
{ "__errno", (SYSCALL)__errno, 0 },
#else
{ "__errno", (SYSCALL)0, 0 },
#endif
#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)())
#ifndef _WIN32
{ "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 },
#else
{ "cygwin_conv_path", (SYSCALL)0, 0 },
#endif
#define osCygwin_conv_path ((size_t(*)(unsigned int, \
const void *, void *, size_t))aSyscall[88].pCurrent)
}; /* End of the overrideable system calls */
/*
** This is the xSetSystemCall() method of sqlite3_vfs for all of the
** "win32" VFSes. Return SQLITE_OK upon successfully updating the
** system call pointer, or SQLITE_NOTFOUND if there is no configurable
** system call named zName.
|
| ︙ | ︙ | |||
48487 48488 48489 48490 48491 48492 48493 48494 48495 48496 48497 48498 48499 48500 |
}
sqlite3_mutex_leave(pMem);
sqlite3_mutex_leave(pMainMtx);
return rc;
}
#endif /* SQLITE_WIN32_MALLOC */
/*
** This function outputs the specified (ANSI) string to the Win32 debugger
** (if available).
*/
SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){
char zDbgBuf[SQLITE_WIN32_DBG_BUF_SIZE];
| > | 48548 48549 48550 48551 48552 48553 48554 48555 48556 48557 48558 48559 48560 48561 48562 |
}
sqlite3_mutex_leave(pMem);
sqlite3_mutex_leave(pMainMtx);
return rc;
}
#endif /* SQLITE_WIN32_MALLOC */
#ifdef _WIN32
/*
** This function outputs the specified (ANSI) string to the Win32 debugger
** (if available).
*/
SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){
char zDbgBuf[SQLITE_WIN32_DBG_BUF_SIZE];
|
| ︙ | ︙ | |||
48529 48530 48531 48532 48533 48534 48535 48536 48537 48538 48539 48540 48541 48542 |
memcpy(zDbgBuf, zBuf, nMin);
fprintf(stderr, "%s", zDbgBuf);
}else{
fprintf(stderr, "%s", zBuf);
}
#endif
}
/*
** The following routine suspends the current thread for at least ms
** milliseconds. This is equivalent to the Win32 Sleep() interface.
*/
#if SQLITE_OS_WINRT
static HANDLE sleepObj = NULL;
| > | 48591 48592 48593 48594 48595 48596 48597 48598 48599 48600 48601 48602 48603 48604 48605 |
memcpy(zDbgBuf, zBuf, nMin);
fprintf(stderr, "%s", zDbgBuf);
}else{
fprintf(stderr, "%s", zBuf);
}
#endif
}
#endif /* _WIN32 */
/*
** The following routine suspends the current thread for at least ms
** milliseconds. This is equivalent to the Win32 Sleep() interface.
*/
#if SQLITE_OS_WINRT
static HANDLE sleepObj = NULL;
|
| ︙ | ︙ | |||
48829 48830 48831 48832 48833 48834 48835 48836 48837 48838 48839 48840 48841 48842 |
}
SQLITE_PRIVATE void sqlite3MemSetDefault(void){
sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
}
#endif /* SQLITE_WIN32_MALLOC */
/*
** Convert a UTF-8 string to Microsoft Unicode.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winUtf8ToUnicode(const char *zText){
int nChar;
| > | 48892 48893 48894 48895 48896 48897 48898 48899 48900 48901 48902 48903 48904 48905 48906 |
}
SQLITE_PRIVATE void sqlite3MemSetDefault(void){
sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
}
#endif /* SQLITE_WIN32_MALLOC */
#ifdef _WIN32
/*
** Convert a UTF-8 string to Microsoft Unicode.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winUtf8ToUnicode(const char *zText){
int nChar;
|
| ︙ | ︙ | |||
48854 48855 48856 48857 48858 48859 48860 48861 48862 48863 48864 48865 48866 48867 |
nChar);
if( nChar==0 ){
sqlite3_free(zWideText);
zWideText = 0;
}
return zWideText;
}
/*
** Convert a Microsoft Unicode string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUnicodeToUtf8(LPCWSTR zWideText){
| > | 48918 48919 48920 48921 48922 48923 48924 48925 48926 48927 48928 48929 48930 48931 48932 |
nChar);
if( nChar==0 ){
sqlite3_free(zWideText);
zWideText = 0;
}
return zWideText;
}
#endif /* _WIN32 */
/*
** Convert a Microsoft Unicode string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUnicodeToUtf8(LPCWSTR zWideText){
|
| ︙ | ︙ | |||
48888 48889 48890 48891 48892 48893 48894 |
/*
** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM
** code page.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
| | | | | | | | | > | 48953 48954 48955 48956 48957 48958 48959 48960 48961 48962 48963 48964 48965 48966 48967 48968 48969 48970 48971 48972 48973 48974 48975 48976 48977 48978 48979 48980 48981 48982 48983 48984 48985 48986 48987 48988 48989 |
/*
** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM
** code page.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
int nWideChar;
LPWSTR zMbcsText;
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
0);
if( nWideChar==0 ){
return 0;
}
zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) );
if( zMbcsText==0 ){
return 0;
}
nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
nWideChar);
if( nWideChar==0 ){
sqlite3_free(zMbcsText);
zMbcsText = 0;
}
return zMbcsText;
}
#ifdef _WIN32
/*
** Convert a Microsoft Unicode string to a multi-byte character string,
** using the ANSI or OEM code page.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
|
| ︙ | ︙ | |||
48937 48938 48939 48940 48941 48942 48943 48944 48945 48946 48947 48948 48949 48950 48951 48952 48953 48954 48955 48956 48957 48958 48959 48960 48961 48962 48963 48964 48965 48966 48967 48968 48969 |
nByte, 0, 0);
if( nByte == 0 ){
sqlite3_free(zText);
zText = 0;
}
return zText;
}
/*
** Convert a multi-byte character string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winMbcsToUtf8(const char *zText, int useAnsi){
char *zTextUtf8;
LPWSTR zTmpWide;
zTmpWide = winMbcsToUnicode(zText, useAnsi);
if( zTmpWide==0 ){
return 0;
}
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
sqlite3_free(zTmpWide);
return zTextUtf8;
}
/*
** Convert a UTF-8 string to a multi-byte character string.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUtf8ToMbcs(const char *zText, int useAnsi){
char *zTextMbcs;
| > > | 49003 49004 49005 49006 49007 49008 49009 49010 49011 49012 49013 49014 49015 49016 49017 49018 49019 49020 49021 49022 49023 49024 49025 49026 49027 49028 49029 49030 49031 49032 49033 49034 49035 49036 49037 |
nByte, 0, 0);
if( nByte == 0 ){
sqlite3_free(zText);
zText = 0;
}
return zText;
}
#endif /* _WIN32 */
/*
** Convert a multi-byte character string to UTF-8.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winMbcsToUtf8(const char *zText, int useAnsi){
char *zTextUtf8;
LPWSTR zTmpWide;
zTmpWide = winMbcsToUnicode(zText, useAnsi);
if( zTmpWide==0 ){
return 0;
}
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
sqlite3_free(zTmpWide);
return zTextUtf8;
}
#ifdef _WIN32
/*
** Convert a UTF-8 string to a multi-byte character string.
**
** Space to hold the returned string is obtained from sqlite3_malloc().
*/
static char *winUtf8ToMbcs(const char *zText, int useAnsi){
char *zTextMbcs;
|
| ︙ | ︙ | |||
49005 49006 49007 49008 49009 49010 49011 49012 49013 49014 49015 49016 49017 49018 49019 49020 49021 49022 49023 49024 49025 49026 49027 49028 49029 49030 49031 49032 49033 49034 49035 |
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winUnicodeToUtf8(zWideText);
}
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winMbcsToUtf8(zText, osAreFileApisANSI());
}
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
| > > | 49073 49074 49075 49076 49077 49078 49079 49080 49081 49082 49083 49084 49085 49086 49087 49088 49089 49090 49091 49092 49093 49094 49095 49096 49097 49098 49099 49100 49101 49102 49103 49104 49105 |
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winUnicodeToUtf8(zWideText);
}
#endif /* _WIN32 */
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
if( sqlite3_initialize() ) return 0;
#endif
return winMbcsToUtf8(zText, osAreFileApisANSI());
}
#ifdef _WIN32
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
|
| ︙ | ︙ | |||
49146 49147 49148 49149 49150 49151 49152 49153 49154 49155 49156 49157 49158 49159 |
*/
SQLITE_API int sqlite3_win32_set_directory(
unsigned long type, /* Identifier for directory being set or reset */
void *zValue /* New value for directory being set or reset */
){
return sqlite3_win32_set_directory16(type, zValue);
}
/*
** The return value of winGetLastErrorMsg
** is zero if the error message fits in the buffer, or non-zero
** otherwise (if the message was truncated).
*/
static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
| > | 49216 49217 49218 49219 49220 49221 49222 49223 49224 49225 49226 49227 49228 49229 49230 |
*/
SQLITE_API int sqlite3_win32_set_directory(
unsigned long type, /* Identifier for directory being set or reset */
void *zValue /* New value for directory being set or reset */
){
return sqlite3_win32_set_directory16(type, zValue);
}
#endif /* _WIN32 */
/*
** The return value of winGetLastErrorMsg
** is zero if the error message fits in the buffer, or non-zero
** otherwise (if the message was truncated).
*/
static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
|
| ︙ | ︙ | |||
49694 49695 49696 49697 49698 49699 49700 49701 49702 49703 49704 49705 49706 49707 49708 49709 49710 |
#else
if( osIsNT() ){
OVERLAPPED ovlp;
memset(&ovlp, 0, sizeof(OVERLAPPED));
ovlp.Offset = offsetLow;
ovlp.OffsetHigh = offsetHigh;
return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
}else{
return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
numBytesHigh);
}
#endif
}
/*
** Lock a region of nByte bytes starting at offset offset of file hFile.
** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock
| > > | 49765 49766 49767 49768 49769 49770 49771 49772 49773 49774 49775 49776 49777 49778 49779 49780 49781 49782 49783 |
#else
if( osIsNT() ){
OVERLAPPED ovlp;
memset(&ovlp, 0, sizeof(OVERLAPPED));
ovlp.Offset = offsetLow;
ovlp.OffsetHigh = offsetHigh;
return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
#ifdef SQLITE_WIN32_HAS_ANSI
}else{
return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
numBytesHigh);
#endif
}
#endif
}
/*
** Lock a region of nByte bytes starting at offset offset of file hFile.
** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock
|
| ︙ | ︙ | |||
49804 49805 49806 49807 49808 49809 49810 49811 49812 49813 49814 49815 49816 49817 49818 49819 49820 |
#else
if( osIsNT() ){
OVERLAPPED ovlp;
memset(&ovlp, 0, sizeof(OVERLAPPED));
ovlp.Offset = offsetLow;
ovlp.OffsetHigh = offsetHigh;
return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp);
}else{
return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
numBytesHigh);
}
#endif
}
/*
** Remove an nByte lock starting at offset iOff from HANDLE h.
*/
| > > | 49877 49878 49879 49880 49881 49882 49883 49884 49885 49886 49887 49888 49889 49890 49891 49892 49893 49894 49895 |
#else
if( osIsNT() ){
OVERLAPPED ovlp;
memset(&ovlp, 0, sizeof(OVERLAPPED));
ovlp.Offset = offsetLow;
ovlp.OffsetHigh = offsetHigh;
return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp);
#ifdef SQLITE_WIN32_HAS_ANSI
}else{
return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
numBytesHigh);
#endif
}
#endif
}
/*
** Remove an nByte lock starting at offset iOff from HANDLE h.
*/
|
| ︙ | ︙ | |||
51220 51221 51222 51223 51224 51225 51226 | } /* ** Convert a UTF-8 filename into whatever form the underlying ** operating system wants filenames in. Space to hold the result ** is obtained from malloc and must be freed by the calling | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 51295 51296 51297 51298 51299 51300 51301 51302 51303 51304 51305 51306 51307 51308 51309 51310 51311 51312 51313 51314 51315 51316 51317 51318 51319 51320 51321 51322 51323 51324 51325 51326 51327 51328 51329 51330 51331 51332 51333 51334 51335 51336 51337 51338 51339 51340 51341 51342 51343 51344 51345 51346 51347 51348 51349 51350 51351 51352 51353 51354 51355 51356 51357 51358 51359 51360 51361 51362 51363 51364 51365 51366 51367 51368 51369 51370 51371 51372 51373 51374 51375 51376 51377 51378 51379 51380 51381 51382 51383 51384 51385 51386 51387 51388 51389 51390 51391 51392 51393 |
}
/*
** Convert a UTF-8 filename into whatever form the underlying
** operating system wants filenames in. Space to hold the result
** is obtained from malloc and must be freed by the calling
** function
**
** On Cygwin, 3 possible input forms are accepted:
** - If the filename starts with "<drive>:/" or "<drive>:\",
** it is converted to UTF-16 as-is.
** - If the filename contains '/', it is assumed to be a
** Cygwin absolute path, it is converted to a win32
** absolute path in UTF-16.
** - Otherwise it must be a filename only, the win32 filename
** is returned in UTF-16.
** Note: If the function cygwin_conv_path() fails, only
** UTF-8 -> UTF-16 conversion will be done. This can only
** happen when the file path >32k, in which case winUtf8ToUnicode()
** will fail too.
*/
static void *winConvertFromUtf8Filename(const char *zFilename){
void *zConverted = 0;
if( osIsNT() ){
#ifdef __CYGWIN__
int nChar;
LPWSTR zWideFilename;
if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename)
&& winIsDirSep(zFilename[2])) ){
int nByte;
int convertflag = CCP_POSIX_TO_WIN_W;
if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE;
nByte = (int)osCygwin_conv_path(convertflag,
zFilename, 0, 0);
if( nByte>0 ){
zConverted = sqlite3MallocZero(nByte+12);
if ( zConverted==0 ){
return zConverted;
}
zWideFilename = zConverted;
/* Filenames should be prefixed, except when converted
* full path already starts with "\\?\". */
if( osCygwin_conv_path(convertflag, zFilename,
zWideFilename+4, nByte)==0 ){
if( (convertflag&CCP_RELATIVE) ){
memmove(zWideFilename, zWideFilename+4, nByte);
}else if( memcmp(zWideFilename+4, L"\\\\", 4) ){
memcpy(zWideFilename, L"\\\\?\\", 8);
}else if( zWideFilename[6]!='?' ){
memmove(zWideFilename+6, zWideFilename+4, nByte);
memcpy(zWideFilename, L"\\\\?\\UNC", 14);
}else{
memmove(zWideFilename, zWideFilename+4, nByte);
}
return zConverted;
}
sqlite3_free(zConverted);
}
}
nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
if( nChar==0 ){
return 0;
}
zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 );
if( zWideFilename==0 ){
return 0;
}
nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1,
zWideFilename, nChar);
if( nChar==0 ){
sqlite3_free(zWideFilename);
zWideFilename = 0;
}else if( nChar>MAX_PATH
&& winIsDriveLetterAndColon(zFilename)
&& winIsDirSep(zFilename[2]) ){
memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR));
zWideFilename[2] = '\\';
memcpy(zWideFilename, L"\\\\?\\", 8);
}else if( nChar>MAX_PATH
&& winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1])
&& zFilename[2] != '?' ){
memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR));
memcpy(zWideFilename, L"\\\\?\\UNC", 14);
}
zConverted = zWideFilename;
#else
zConverted = winUtf8ToUnicode(zFilename);
#endif /* __CYGWIN__ */
}
#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32)
else{
zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
}
#endif
/* caller will handle out of memory */
return zConverted;
}
|
| ︙ | ︙ | |||
52056 52057 52058 52059 52060 52061 52062 | /**************************************************************************** **************************** sqlite3_vfs methods **************************** ** ** This division contains the implementation of methods on the ** sqlite3_vfs object. */ | | | 52208 52209 52210 52211 52212 52213 52214 52215 52216 52217 52218 52219 52220 52221 52222 |
/****************************************************************************
**************************** sqlite3_vfs methods ****************************
**
** This division contains the implementation of methods on the
** sqlite3_vfs object.
*/
#if 0 /* No longer necessary */
/*
** Convert a filename from whatever the underlying operating system
** supports for filenames into UTF-8. Space to hold the result is
** obtained from malloc and must be freed by the calling function.
*/
static char *winConvertToUtf8Filename(const void *zFilename){
char *zConverted = 0;
|
| ︙ | ︙ | |||
52089 52090 52091 52092 52093 52094 52095 |
static int winMakeEndInDirSep(int nBuf, char *zBuf){
if( zBuf ){
int nLen = sqlite3Strlen30(zBuf);
if( nLen>0 ){
if( winIsDirSep(zBuf[nLen-1]) ){
return 1;
}else if( nLen+1<nBuf ){
| > | > > > > > > | 52241 52242 52243 52244 52245 52246 52247 52248 52249 52250 52251 52252 52253 52254 52255 52256 52257 52258 52259 52260 52261 52262 |
static int winMakeEndInDirSep(int nBuf, char *zBuf){
if( zBuf ){
int nLen = sqlite3Strlen30(zBuf);
if( nLen>0 ){
if( winIsDirSep(zBuf[nLen-1]) ){
return 1;
}else if( nLen+1<nBuf ){
if( !osGetenv ){
zBuf[nLen] = winGetDirSep();
}else if( winIsDriveLetterAndColon(zBuf) && winIsDirSep(zBuf[2]) ){
zBuf[nLen] = '\\';
zBuf[2]='\\';
}else{
zBuf[nLen] = '/';
}
zBuf[nLen+1] = '\0';
return 1;
}
}
}
return 0;
}
|
| ︙ | ︙ | |||
52116 52117 52118 52119 52120 52121 52122 |
}
/*
** Create a temporary file name and store the resulting pointer into pzBuf.
** The pointer returned in pzBuf must be freed via sqlite3_free().
*/
static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
| | | 52275 52276 52277 52278 52279 52280 52281 52282 52283 52284 52285 52286 52287 52288 52289 |
}
/*
** Create a temporary file name and store the resulting pointer into pzBuf.
** The pointer returned in pzBuf must be freed via sqlite3_free().
*/
static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
static const char zChars[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
size_t i, j;
DWORD pid;
int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX);
i64 nMax, nBuf, nDir, nLen;
|
| ︙ | ︙ | |||
52167 52168 52169 52170 52171 52172 52173 |
}
sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory);
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
}
#if defined(__CYGWIN__)
| | | | | | | < > > | | | 52326 52327 52328 52329 52330 52331 52332 52333 52334 52335 52336 52337 52338 52339 52340 52341 52342 52343 52344 52345 52346 52347 52348 52349 52350 52351 52352 52353 52354 52355 52356 52357 52358 52359 52360 52361 52362 52363 52364 52365 52366 52367 52368 52369 52370 52371 52372 52373 52374 52375 52376 52377 52378 52379 52380 52381 52382 52383 52384 52385 52386 52387 52388 52389 52390 52391 |
}
sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory);
}
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR));
}
#if defined(__CYGWIN__)
else if( osGetenv!=NULL ){
static const char *azDirs[] = {
0, /* getenv("SQLITE_TMPDIR") */
0, /* getenv("TMPDIR") */
0, /* getenv("TMP") */
0, /* getenv("TEMP") */
0, /* getenv("USERPROFILE") */
"/var/tmp",
"/usr/tmp",
"/tmp",
".",
0 /* List terminator */
};
unsigned int i;
const char *zDir = 0;
if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR");
if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR");
if( !azDirs[2] ) azDirs[2] = osGetenv("TMP");
if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP");
if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE");
for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){
void *zConverted;
if( zDir==0 ) continue;
/* If the path starts with a drive letter followed by the colon
** character, assume it is already a native Win32 path; otherwise,
** it must be converted to a native Win32 path via the Cygwin API
** prior to using it.
*/
{
zConverted = winConvertFromUtf8Filename(zDir);
if( !zConverted ){
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( winIsDir(zConverted) ){
sqlite3_snprintf(nMax, zBuf, "%s", zDir);
sqlite3_free(zConverted);
break;
}
sqlite3_free(zConverted);
#if 0 /* No longer necessary */
}else{
zConverted = sqlite3MallocZero( nMax+1 );
if( !zConverted ){
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( osCygwin_conv_path(
CCP_POSIX_TO_WIN_W, zDir,
zConverted, nMax+1)<0 ){
sqlite3_free(zConverted);
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n"));
return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno,
"winGetTempname2", zDir);
}
|
| ︙ | ︙ | |||
52243 52244 52245 52246 52247 52248 52249 52250 52251 52252 |
}
sqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
sqlite3_free(zUtf8);
sqlite3_free(zConverted);
break;
}
sqlite3_free(zConverted);
}
}
}
| > > > | | 52403 52404 52405 52406 52407 52408 52409 52410 52411 52412 52413 52414 52415 52416 52417 52418 52419 52420 52421 52422 52423 |
}
sqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
sqlite3_free(zUtf8);
sqlite3_free(zConverted);
break;
}
sqlite3_free(zConverted);
#endif /* No longer necessary */
}
}
}
#endif
#if !SQLITE_OS_WINRT && defined(_WIN32)
else if( osIsNT() ){
char *zMulti;
LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) );
if( !zWidePath ){
sqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
|
| ︙ | ︙ | |||
52370 52371 52372 52373 52374 52375 52376 |
while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
GetFileExInfoStandard,
&sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
if( !rc ){
return 0; /* Invalid name? */
}
attr = sAttrData.dwFileAttributes;
| | > > > > > > | 52533 52534 52535 52536 52537 52538 52539 52540 52541 52542 52543 52544 52545 52546 52547 52548 52549 52550 52551 52552 52553 52554 52555 52556 52557 52558 52559 52560 52561 52562 52563 52564 52565 52566 52567 52568 |
while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
GetFileExInfoStandard,
&sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){}
if( !rc ){
return 0; /* Invalid name? */
}
attr = sAttrData.dwFileAttributes;
#if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI)
}else{
attr = osGetFileAttributesA((char*)zConverted);
#endif
}
return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY);
}
/* forward reference */
static int winAccess(
sqlite3_vfs *pVfs, /* Not used on win32 */
const char *zFilename, /* Name of file to check */
int flags, /* Type of test to make on this file */
int *pResOut /* OUT: Result */
);
/*
** The Windows version of xAccess() accepts an extra bit in the flags
** parameter that prevents an anti-virus retry loop.
*/
#define NORETRY 0x4000
/*
** Open a file.
*/
static int winOpen(
sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
const char *zName, /* Name of the file (UTF-8) */
sqlite3_file *id, /* Write the SQLite file handle here */
|
| ︙ | ︙ | |||
52410 52411 52412 52413 52414 52415 52416 52417 52418 52419 52420 52421 52422 52423 | int isTemp = 0; #endif winVfsAppData *pAppData; winFile *pFile = (winFile*)id; void *zConverted; /* Filename in OS encoding */ const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ int cnt = 0; /* If argument zPath is a NULL pointer, this function is required to open ** a temporary file. Use this buffer to store the file name in. */ char *zTmpname = 0; /* For temporary filename, if necessary. */ int rc = SQLITE_OK; /* Function Return Code */ | > | 52579 52580 52581 52582 52583 52584 52585 52586 52587 52588 52589 52590 52591 52592 52593 | int isTemp = 0; #endif winVfsAppData *pAppData; winFile *pFile = (winFile*)id; void *zConverted; /* Filename in OS encoding */ const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ int cnt = 0; int isRO = 0; /* file is known to be accessible readonly */ /* If argument zPath is a NULL pointer, this function is required to open ** a temporary file. Use this buffer to store the file name in. */ char *zTmpname = 0; /* For temporary filename, if necessary. */ int rc = SQLITE_OK; /* Function Return Code */ |
| ︙ | ︙ | |||
52574 52575 52576 52577 52578 52579 52580 |
h = osCreateFile2((LPCWSTR)zConverted,
dwDesiredAccess,
dwShareMode,
dwCreationDisposition,
&extendedParameters);
if( h!=INVALID_HANDLE_VALUE ) break;
if( isReadWrite ){
| | | | | | | | | 52744 52745 52746 52747 52748 52749 52750 52751 52752 52753 52754 52755 52756 52757 52758 52759 52760 52761 52762 52763 52764 52765 52766 52767 52768 52769 52770 52771 52772 52773 52774 52775 52776 52777 52778 52779 52780 52781 52782 52783 52784 52785 52786 52787 52788 52789 52790 52791 52792 52793 52794 52795 52796 52797 52798 52799 52800 52801 52802 52803 52804 52805 52806 52807 52808 52809 52810 52811 52812 |
h = osCreateFile2((LPCWSTR)zConverted,
dwDesiredAccess,
dwShareMode,
dwCreationDisposition,
&extendedParameters);
if( h!=INVALID_HANDLE_VALUE ) break;
if( isReadWrite ){
int rc2;
sqlite3BeginBenignMalloc();
rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
sqlite3EndBenignMalloc();
if( rc2==SQLITE_OK && isRO ) break;
}
}while( winRetryIoerr(&cnt, &lastErrno) );
#else
do{
h = osCreateFileW((LPCWSTR)zConverted,
dwDesiredAccess,
dwShareMode, NULL,
dwCreationDisposition,
dwFlagsAndAttributes,
NULL);
if( h!=INVALID_HANDLE_VALUE ) break;
if( isReadWrite ){
int rc2;
sqlite3BeginBenignMalloc();
rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
sqlite3EndBenignMalloc();
if( rc2==SQLITE_OK && isRO ) break;
}
}while( winRetryIoerr(&cnt, &lastErrno) );
#endif
}
#ifdef SQLITE_WIN32_HAS_ANSI
else{
do{
h = osCreateFileA((LPCSTR)zConverted,
dwDesiredAccess,
dwShareMode, NULL,
dwCreationDisposition,
dwFlagsAndAttributes,
NULL);
if( h!=INVALID_HANDLE_VALUE ) break;
if( isReadWrite ){
int rc2;
sqlite3BeginBenignMalloc();
rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO);
sqlite3EndBenignMalloc();
if( rc2==SQLITE_OK && isRO ) break;
}
}while( winRetryIoerr(&cnt, &lastErrno) );
}
#endif
winLogIoerr(cnt, __LINE__);
OSTRACE(("OPEN file=%p, name=%s, access=%lx, rc=%s\n", h, zUtf8Name,
dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok"));
if( h==INVALID_HANDLE_VALUE ){
sqlite3_free(zConverted);
sqlite3_free(zTmpname);
if( isReadWrite && isRO && !isExclusive ){
return winOpen(pVfs, zName, id,
((flags|SQLITE_OPEN_READONLY) &
~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)),
pOutFlags);
}else{
pFile->lastErrno = lastErrno;
winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name);
|
| ︙ | ︙ | |||
52830 52831 52832 52833 52834 52835 52836 52837 52838 52839 52840 52841 52842 52843 52844 |
int flags, /* Type of test to make on this file */
int *pResOut /* OUT: Result */
){
DWORD attr;
int rc = 0;
DWORD lastErrno = 0;
void *zConverted;
UNUSED_PARAMETER(pVfs);
SimulateIOError( return SQLITE_IOERR_ACCESS; );
OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n",
zFilename, flags, pResOut));
if( zFilename==0 ){
*pResOut = 0;
| > > > > > > | 53000 53001 53002 53003 53004 53005 53006 53007 53008 53009 53010 53011 53012 53013 53014 53015 53016 53017 53018 53019 53020 |
int flags, /* Type of test to make on this file */
int *pResOut /* OUT: Result */
){
DWORD attr;
int rc = 0;
DWORD lastErrno = 0;
void *zConverted;
int noRetry = 0; /* Do not use winRetryIoerr() */
UNUSED_PARAMETER(pVfs);
if( (flags & NORETRY)!=0 ){
noRetry = 1;
flags &= ~NORETRY;
}
SimulateIOError( return SQLITE_IOERR_ACCESS; );
OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n",
zFilename, flags, pResOut));
if( zFilename==0 ){
*pResOut = 0;
|
| ︙ | ︙ | |||
52854 52855 52856 52857 52858 52859 52860 |
}
if( osIsNT() ){
int cnt = 0;
WIN32_FILE_ATTRIBUTE_DATA sAttrData;
memset(&sAttrData, 0, sizeof(sAttrData));
while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
GetFileExInfoStandard,
| > > | > | 53030 53031 53032 53033 53034 53035 53036 53037 53038 53039 53040 53041 53042 53043 53044 53045 53046 53047 |
}
if( osIsNT() ){
int cnt = 0;
WIN32_FILE_ATTRIBUTE_DATA sAttrData;
memset(&sAttrData, 0, sizeof(sAttrData));
while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted,
GetFileExInfoStandard,
&sAttrData))
&& !noRetry
&& winRetryIoerr(&cnt, &lastErrno)
){ /* Loop until true */}
if( rc ){
/* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file
** as if it does not exist.
*/
if( flags==SQLITE_ACCESS_EXISTS
&& sAttrData.nFileSizeHigh==0
&& sAttrData.nFileSizeLow==0 ){
|
| ︙ | ︙ | |||
52922 52923 52924 52925 52926 52927 52928 52929 52930 52931 52932 52933 52934 52935 |
*/
static BOOL winIsDriveLetterAndColon(
const char *zPathname
){
return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
}
/*
** Returns non-zero if the specified path name should be used verbatim. If
** non-zero is returned from this function, the calling function must simply
** use the provided path name verbatim -OR- resolve it into a full path name
** using the GetFullPathName Win32 API function (if available).
*/
static BOOL winIsVerbatimPathname(
| > | 53101 53102 53103 53104 53105 53106 53107 53108 53109 53110 53111 53112 53113 53114 53115 |
*/
static BOOL winIsDriveLetterAndColon(
const char *zPathname
){
return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
}
#ifdef _WIN32
/*
** Returns non-zero if the specified path name should be used verbatim. If
** non-zero is returned from this function, the calling function must simply
** use the provided path name verbatim -OR- resolve it into a full path name
** using the GetFullPathName Win32 API function (if available).
*/
static BOOL winIsVerbatimPathname(
|
| ︙ | ︙ | |||
52958 52959 52960 52961 52962 52963 52964 52965 52966 52967 52968 52969 52970 52971 52972 52973 52974 52975 52976 |
/*
** If we get to this point, the path name should almost certainly be a purely
** relative one (i.e. not a UNC name, not absolute, and not volume relative).
*/
return FALSE;
}
/*
** Turn a relative pathname into a full pathname. Write the full
** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
** bytes in size.
*/
static int winFullPathnameNoMutex(
sqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
char *zFull /* Output buffer */
){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | < | | > > > | | | 53138 53139 53140 53141 53142 53143 53144 53145 53146 53147 53148 53149 53150 53151 53152 53153 53154 53155 53156 53157 53158 53159 53160 53161 53162 53163 53164 53165 53166 53167 53168 53169 53170 53171 53172 53173 53174 53175 53176 53177 53178 53179 53180 53181 53182 53183 53184 53185 53186 53187 53188 53189 53190 53191 53192 53193 53194 53195 53196 53197 53198 53199 53200 53201 53202 53203 53204 53205 53206 53207 53208 53209 53210 53211 53212 53213 53214 53215 53216 53217 53218 53219 53220 53221 53222 53223 53224 53225 53226 53227 53228 53229 53230 53231 53232 53233 53234 53235 53236 53237 53238 53239 53240 53241 53242 53243 53244 53245 53246 53247 53248 53249 53250 53251 53252 53253 53254 53255 53256 53257 53258 53259 53260 53261 53262 53263 53264 53265 53266 53267 53268 53269 53270 53271 53272 53273 53274 53275 53276 53277 53278 53279 53280 53281 53282 53283 53284 53285 53286 53287 53288 53289 53290 53291 53292 53293 53294 53295 53296 53297 53298 53299 53300 53301 53302 53303 53304 53305 53306 53307 53308 53309 53310 53311 53312 53313 53314 53315 53316 53317 53318 53319 53320 53321 53322 53323 53324 53325 53326 53327 53328 53329 53330 53331 53332 53333 53334 53335 53336 53337 53338 53339 53340 53341 53342 53343 53344 53345 53346 53347 53348 53349 53350 53351 53352 53353 53354 53355 53356 53357 53358 53359 53360 53361 53362 53363 53364 53365 53366 53367 53368 53369 53370 53371 53372 53373 53374 53375 53376 53377 53378 53379 53380 53381 53382 53383 53384 53385 53386 53387 53388 53389 53390 53391 53392 53393 53394 53395 53396 53397 53398 53399 53400 53401 53402 |
/*
** If we get to this point, the path name should almost certainly be a purely
** relative one (i.e. not a UNC name, not absolute, and not volume relative).
*/
return FALSE;
}
#endif /* _WIN32 */
#ifdef __CYGWIN__
/*
** Simplify a filename into its canonical form
** by making the following changes:
**
** * convert any '/' to '\' (win32) or reverse (Cygwin)
** * removing any trailing and duplicate / (except for UNC paths)
** * convert /./ into just /
**
** Changes are made in-place. Return the new name length.
**
** The original filename is in z[0..]. If the path is shortened,
** no-longer used bytes will be written by '\0'.
*/
static void winSimplifyName(char *z){
int i, j;
for(i=j=0; z[i]; ++i){
if( winIsDirSep(z[i]) ){
#if !defined(SQLITE_TEST)
/* Some test-cases assume that "./foo" and "foo" are different */
if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){
++i;
continue;
}
#endif
if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){
continue;
}
z[j++] = osGetenv?'/':'\\';
}else{
z[j++] = z[i];
}
}
while(j<i) z[j++] = '\0';
}
#define SQLITE_MAX_SYMLINKS 100
static int mkFullPathname(
const char *zPath, /* Input path */
char *zOut, /* Output buffer */
int nOut /* Allocated size of buffer zOut */
){
int nPath = sqlite3Strlen30(zPath);
int iOff = 0;
if( zPath[0]!='/' ){
if( osGetcwd(zOut, nOut-2)==0 ){
return winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "getcwd", zPath);
}
iOff = sqlite3Strlen30(zOut);
zOut[iOff++] = '/';
}
if( (iOff+nPath+1)>nOut ){
/* SQLite assumes that xFullPathname() nul-terminates the output buffer
** even if it returns an error. */
zOut[iOff] = '\0';
return SQLITE_CANTOPEN_BKPT;
}
sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
return SQLITE_OK;
}
#endif /* __CYGWIN__ */
/*
** Turn a relative pathname into a full pathname. Write the full
** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
** bytes in size.
*/
static int winFullPathnameNoMutex(
sqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
char *zFull /* Output buffer */
){
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
int nByte;
void *zConverted;
char *zOut;
#endif
/* If this path name begins with "/X:" or "\\?\", where "X" is any
** alphabetic character, discard the initial "/" from the pathname.
*/
if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1)
|| winIsLongPathPrefix(zRelative+1)) ){
zRelative++;
}
SimulateIOError( return SQLITE_ERROR );
#ifdef __CYGWIN__
if( osGetcwd ){
zFull[nFull-1] = '\0';
if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){
int rc = SQLITE_OK;
int nLink = 1; /* Number of symbolic links followed so far */
const char *zIn = zRelative; /* Input path for each iteration of loop */
char *zDel = 0;
struct stat buf;
UNUSED_PARAMETER(pVfs);
do {
/* Call lstat() on path zIn. Set bLink to true if the path is a symbolic
** link, or false otherwise. */
int bLink = 0;
if( osLstat && osReadlink ) {
if( osLstat(zIn, &buf)!=0 ){
int myErrno = osErrno;
if( myErrno!=ENOENT ){
rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn);
}
}else{
bLink = ((buf.st_mode & 0170000) == 0120000);
}
if( bLink ){
if( zDel==0 ){
zDel = sqlite3MallocZero(nFull);
if( zDel==0 ) rc = SQLITE_NOMEM;
}else if( ++nLink>SQLITE_MAX_SYMLINKS ){
rc = SQLITE_CANTOPEN_BKPT;
}
if( rc==SQLITE_OK ){
nByte = osReadlink(zIn, zDel, nFull-1);
if( nByte ==(DWORD)-1 ){
rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn);
}else{
if( zDel[0]!='/' ){
int n;
for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
if( nByte+n+1>nFull ){
rc = SQLITE_CANTOPEN_BKPT;
}else{
memmove(&zDel[n], zDel, nByte+1);
memcpy(zDel, zIn, n);
nByte += n;
}
}
zDel[nByte] = '\0';
}
}
zIn = zDel;
}
}
assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' );
if( rc==SQLITE_OK && zIn!=zFull ){
rc = mkFullPathname(zIn, zFull, nFull);
}
if( bLink==0 ) break;
zIn = zFull;
}while( rc==SQLITE_OK );
sqlite3_free(zDel);
winSimplifyName(zFull);
return rc;
}
}
#endif /* __CYGWIN__ */
#if 0 /* This doesn't work correctly at all! See:
<https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
*/
SimulateIOError( return SQLITE_ERROR );
UNUSED_PARAMETER(nFull);
assert( nFull>=pVfs->mxPathname );
char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
if( !zOut ){
return SQLITE_IOERR_NOMEM_BKPT;
}
if( osCygwin_conv_path(
CCP_POSIX_TO_WIN_W,
zRelative, zOut, pVfs->mxPathname+1)<0 ){
sqlite3_free(zOut);
return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
"winFullPathname2", zRelative);
}else{
char *zUtf8 = winConvertToUtf8Filename(zOut);
if( !zUtf8 ){
sqlite3_free(zOut);
return SQLITE_IOERR_NOMEM_BKPT;
}
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
sqlite3_free(zUtf8);
sqlite3_free(zOut);
}
return SQLITE_OK;
#endif
#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32)
SimulateIOError( return SQLITE_ERROR );
/* WinCE has no concept of a relative pathname, or so I am told. */
/* WinRT has no way to convert a relative path to an absolute one. */
if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
/*
** NOTE: We are dealing with a relative path name and the data
** directory has been set. Therefore, use it as the basis
** for converting the relative path name to an absolute
** one by prepending the data directory and a backslash.
*/
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
sqlite3_data_directory, winGetDirSep(), zRelative);
}else{
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
}
return SQLITE_OK;
#endif
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
#if defined(_WIN32)
/* It's odd to simulate an io-error here, but really this is just
** using the io-error infrastructure to test that SQLite handles this
** function failing. This function could fail if, for example, the
** current working directory has been unlinked.
*/
SimulateIOError( return SQLITE_ERROR );
if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
/*
** NOTE: We are dealing with a relative path name and the data
** directory has been set. Therefore, use it as the basis
** for converting the relative path name to an absolute
** one by prepending the data directory and a backslash.
*/
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
sqlite3_data_directory, winGetDirSep(), zRelative);
return SQLITE_OK;
}
#endif
zConverted = winConvertFromUtf8Filename(zRelative);
if( zConverted==0 ){
return SQLITE_IOERR_NOMEM_BKPT;
}
if( osIsNT() ){
LPWSTR zTemp;
nByte = osGetFullPathNameW((LPCWSTR)zConverted, 0, 0, 0);
if( nByte==0 ){
sqlite3_free(zConverted);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname1", zRelative);
}
nByte += 3;
zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) );
if( zTemp==0 ){
sqlite3_free(zConverted);
return SQLITE_IOERR_NOMEM_BKPT;
}
nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0);
if( nByte==0 ){
sqlite3_free(zConverted);
sqlite3_free(zTemp);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname2", zRelative);
}
sqlite3_free(zConverted);
|
| ︙ | ︙ | |||
53133 53134 53135 53136 53137 53138 53139 |
}
sqlite3_free(zConverted);
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
sqlite3_free(zTemp);
}
#endif
if( zOut ){
| > > | > > > > > > > > > > > > > > > > > | 53426 53427 53428 53429 53430 53431 53432 53433 53434 53435 53436 53437 53438 53439 53440 53441 53442 53443 53444 53445 53446 53447 53448 53449 53450 53451 53452 53453 53454 53455 53456 53457 53458 53459 |
}
sqlite3_free(zConverted);
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
sqlite3_free(zTemp);
}
#endif
if( zOut ){
#ifdef __CYGWIN__
if( memcmp(zOut, "\\\\?\\", 4) ){
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
}else if( memcmp(zOut+4, "UNC\\", 4) ){
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4);
}else{
char *p = zOut+6;
*p = '\\';
if( osGetcwd ){
/* On Cygwin, UNC paths use forward slashes */
while( *p ){
if( *p=='\\' ) *p = '/';
++p;
}
}
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6);
}
#else
sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
#endif /* __CYGWIN__ */
sqlite3_free(zOut);
return SQLITE_OK;
}else{
return SQLITE_IOERR_NOMEM_BKPT;
}
#endif
}
|
| ︙ | ︙ | |||
53163 53164 53165 53166 53167 53168 53169 |
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
** Interfaces for opening a shared library, finding entry points
** within the shared library, and closing the shared library.
*/
static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
HANDLE h;
| | > > | 53475 53476 53477 53478 53479 53480 53481 53482 53483 53484 53485 53486 53487 53488 53489 53490 53491 |
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
** Interfaces for opening a shared library, finding entry points
** within the shared library, and closing the shared library.
*/
static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
HANDLE h;
#if 0 /* This doesn't work correctly at all! See:
<https://marc.info/?l=sqlite-users&m=139299149416314&w=2>
*/
int nFull = pVfs->mxPathname+1;
char *zFull = sqlite3MallocZero( nFull );
void *zConverted = 0;
if( zFull==0 ){
OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
return 0;
}
|
| ︙ | ︙ | |||
53530 53531 53532 53533 53534 53535 53536 |
winGetSystemCall, /* xGetSystemCall */
winNextSystemCall, /* xNextSystemCall */
};
#endif
/* Double-check that the aSyscall[] array has been constructed
** correctly. See ticket [bb3a86e890c8e96ab] */
| | | 53844 53845 53846 53847 53848 53849 53850 53851 53852 53853 53854 53855 53856 53857 53858 |
winGetSystemCall, /* xGetSystemCall */
winNextSystemCall, /* xNextSystemCall */
};
#endif
/* Double-check that the aSyscall[] array has been constructed
** correctly. See ticket [bb3a86e890c8e96ab] */
assert( ArraySize(aSyscall)==89 );
/* get memory map allocation granularity */
memset(&winSysInfo, 0, sizeof(SYSTEM_INFO));
#if SQLITE_OS_WINRT
osGetNativeSystemInfo(&winSysInfo);
#else
osGetSystemInfo(&winSysInfo);
|
| ︙ | ︙ | |||
66506 66507 66508 66509 66510 66511 66512 |
if( aIn ){
s1 = aIn[0];
s2 = aIn[1];
}else{
s1 = s2 = 0;
}
| > | < < < | 66820 66821 66822 66823 66824 66825 66826 66827 66828 66829 66830 66831 66832 66833 66834 66835 |
if( aIn ){
s1 = aIn[0];
s2 = aIn[1];
}else{
s1 = s2 = 0;
}
/* nByte is a multiple of 8 between 8 and 65536 */
assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 );
if( !nativeCksum ){
do {
s1 += BYTESWAP32(aData[0]) + s2;
s2 += BYTESWAP32(aData[1]) + s1;
aData += 2;
}while( aData<aEnd );
|
| ︙ | ︙ | |||
83724 83725 83726 83727 83728 83729 83730 | ** ** A single int or real value always converts to the same strings. But ** many different strings can be converted into the same int or real. ** If a table contains a numeric value and an index is based on the ** corresponding string value, then it is important that the string be ** derived from the numeric value, not the other way around, to ensure ** that the index and table are consistent. See ticket | | | 84036 84037 84038 84039 84040 84041 84042 84043 84044 84045 84046 84047 84048 84049 84050 | ** ** A single int or real value always converts to the same strings. But ** many different strings can be converted into the same int or real. ** If a table contains a numeric value and an index is based on the ** corresponding string value, then it is important that the string be ** derived from the numeric value, not the other way around, to ensure ** that the index and table are consistent. See ticket ** https://sqlite.org/src/info/343634942dd54ab (2018-01-31) for ** an example. ** ** This routine looks at pMem to verify that if it has both a numeric ** representation and a string representation then the string rep has ** been derived from the numeric and not the other way around. It returns ** true if everything is ok and false if there is a problem. ** |
| ︙ | ︙ | |||
93012 93013 93014 93015 93016 93017 93018 |
sqlite3_uint64 nData,
void (*xDel)(void*),
unsigned char enc
){
assert( xDel!=SQLITE_DYNAMIC );
if( enc!=SQLITE_UTF8 ){
if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE;
| | | 93324 93325 93326 93327 93328 93329 93330 93331 93332 93333 93334 93335 93336 93337 93338 |
sqlite3_uint64 nData,
void (*xDel)(void*),
unsigned char enc
){
assert( xDel!=SQLITE_DYNAMIC );
if( enc!=SQLITE_UTF8 ){
if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE;
nData &= ~(u64)1;
}
return bindText(pStmt, i, zData, nData, xDel, enc);
}
#ifndef SQLITE_OMIT_UTF16
SQLITE_API int sqlite3_bind_text16(
sqlite3_stmt *pStmt,
int i,
|
| ︙ | ︙ | |||
125164 125165 125166 125167 125168 125169 125170 |
if( resizeIndexObject(pParse, pIdx, pIdx->nKeyCol+n) ) return;
for(i=0, j=pIdx->nKeyCol; i<nPk; i++){
if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){
testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) );
pIdx->aiColumn[j] = pPk->aiColumn[i];
pIdx->azColl[j] = pPk->azColl[i];
if( pPk->aSortOrder[i] ){
| | | 125476 125477 125478 125479 125480 125481 125482 125483 125484 125485 125486 125487 125488 125489 125490 |
if( resizeIndexObject(pParse, pIdx, pIdx->nKeyCol+n) ) return;
for(i=0, j=pIdx->nKeyCol; i<nPk; i++){
if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){
testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) );
pIdx->aiColumn[j] = pPk->aiColumn[i];
pIdx->azColl[j] = pPk->azColl[i];
if( pPk->aSortOrder[i] ){
/* See ticket https://sqlite.org/src/info/bba7b69f9849b5bf */
pIdx->bAscKeyBug = 1;
}
j++;
}
}
assert( pIdx->nColumn>=pIdx->nKeyCol+n );
assert( pIdx->nColumn>=j );
|
| ︙ | ︙ | |||
126541 126542 126543 126544 126545 126546 126547 |
sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx);
if( !pIndex->bAscKeyBug ){
/* This OP_SeekEnd opcode makes index insert for a REINDEX go much
** faster by avoiding unnecessary seeks. But the optimization does
** not work for UNIQUE constraint indexes on WITHOUT ROWID tables
** with DESC primary keys, since those indexes have there keys in
** a different order from the main table.
| | | 126853 126854 126855 126856 126857 126858 126859 126860 126861 126862 126863 126864 126865 126866 126867 |
sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx);
if( !pIndex->bAscKeyBug ){
/* This OP_SeekEnd opcode makes index insert for a REINDEX go much
** faster by avoiding unnecessary seeks. But the optimization does
** not work for UNIQUE constraint indexes on WITHOUT ROWID tables
** with DESC primary keys, since those indexes have there keys in
** a different order from the main table.
** See ticket: https://sqlite.org/src/info/bba7b69f9849b5bf
*/
sqlite3VdbeAddOp1(v, OP_SeekEnd, iIdx);
}
sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord);
sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
sqlite3ReleaseTempReg(pParse, regRecord);
sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v);
|
| ︙ | ︙ | |||
126925 126926 126927 126928 126929 126930 126931 126932 126933 126934 126935 126936 126937 126938 |
pIndex->uniqNotNull = 0;
pIndex->bHasExpr = 1;
}else{
j = pCExpr->iColumn;
assert( j<=0x7fff );
if( j<0 ){
j = pTab->iPKey;
}else{
if( pTab->aCol[j].notNull==0 ){
pIndex->uniqNotNull = 0;
}
if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
pIndex->bHasVCol = 1;
pIndex->bHasExpr = 1;
| > | 127237 127238 127239 127240 127241 127242 127243 127244 127245 127246 127247 127248 127249 127250 127251 |
pIndex->uniqNotNull = 0;
pIndex->bHasExpr = 1;
}else{
j = pCExpr->iColumn;
assert( j<=0x7fff );
if( j<0 ){
j = pTab->iPKey;
pIndex->bIdxRowid = 1;
}else{
if( pTab->aCol[j].notNull==0 ){
pIndex->uniqNotNull = 0;
}
if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
pIndex->bHasVCol = 1;
pIndex->bHasExpr = 1;
|
| ︙ | ︙ | |||
136630 136631 136632 136633 136634 136635 136636 | ** ** OE_Fail and OE_Ignore must happen before any changes are made. ** OE_Update guarantees that only a single row will change, so it ** must happen before OE_Replace. Technically, OE_Abort and OE_Rollback ** could happen in any order, but they are grouped up front for ** convenience. ** | | | 136943 136944 136945 136946 136947 136948 136949 136950 136951 136952 136953 136954 136955 136956 136957 | ** ** OE_Fail and OE_Ignore must happen before any changes are made. ** OE_Update guarantees that only a single row will change, so it ** must happen before OE_Replace. Technically, OE_Abort and OE_Rollback ** could happen in any order, but they are grouped up front for ** convenience. ** ** 2018-08-14: Ticket https://sqlite.org/src/info/908f001483982c43 ** The order of constraints used to have OE_Update as (2) and OE_Abort ** and so forth as (1). But apparently PostgreSQL checks the OE_Update ** constraint before any others, so it had to be moved. ** ** Constraint checking code is generated in this order: ** (A) The rowid constraint ** (B) Unique index constraints that do not have OE_Replace as their |
| ︙ | ︙ | |||
147783 147784 147785 147786 147787 147788 147789 147790 147791 147792 147793 147794 147795 147796 |
}
sqlite3KeyInfoUnref(pKeyInfo);
}
multi_select_end:
pDest->iSdst = dest.iSdst;
pDest->nSdst = dest.nSdst;
if( pDelete ){
sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
}
return rc;
}
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
| > | 148096 148097 148098 148099 148100 148101 148102 148103 148104 148105 148106 148107 148108 148109 148110 |
}
sqlite3KeyInfoUnref(pKeyInfo);
}
multi_select_end:
pDest->iSdst = dest.iSdst;
pDest->nSdst = dest.nSdst;
pDest->iSDParm2 = dest.iSDParm2;
if( pDelete ){
sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
}
return rc;
}
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
|
| ︙ | ︙ | |||
149393 149394 149395 149396 149397 149398 149399 |
assert( pE2->op==TK_COLUMN );
if( pE2->iTable==pColumn->iTable
&& pE2->iColumn==pColumn->iColumn
){
return; /* Already present. Return without doing anything. */
}
}
| > | | 149707 149708 149709 149710 149711 149712 149713 149714 149715 149716 149717 149718 149719 149720 149721 149722 |
assert( pE2->op==TK_COLUMN );
if( pE2->iTable==pColumn->iTable
&& pE2->iColumn==pColumn->iColumn
){
return; /* Already present. Return without doing anything. */
}
}
assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
if( sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
pConst->bHasAffBlob = 1;
}
pConst->nConst++;
pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
pConst->nConst*2*sizeof(Expr*));
if( pConst->apExpr==0 ){
|
| ︙ | ︙ | |||
149468 149469 149470 149471 149472 149473 149474 |
return WRC_Continue;
}
for(i=0; i<pConst->nConst; i++){
Expr *pColumn = pConst->apExpr[i*2];
if( pColumn==pExpr ) continue;
if( pColumn->iTable!=pExpr->iTable ) continue;
if( pColumn->iColumn!=pExpr->iColumn ) continue;
| > | | 149783 149784 149785 149786 149787 149788 149789 149790 149791 149792 149793 149794 149795 149796 149797 149798 |
return WRC_Continue;
}
for(i=0; i<pConst->nConst; i++){
Expr *pColumn = pConst->apExpr[i*2];
if( pColumn==pExpr ) continue;
if( pColumn->iTable!=pExpr->iTable ) continue;
if( pColumn->iColumn!=pExpr->iColumn ) continue;
assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){
break;
}
/* A match is found. Add the EP_FixedCol property */
pConst->nChng++;
ExprClearProperty(pExpr, EP_Leaf);
ExprSetProperty(pExpr, EP_FixedCol);
assert( pExpr->pLeft==0 );
|
| ︙ | ︙ | |||
150121 150122 150123 150124 150125 150126 150127 | ** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) ** ORDER BY ... COLLATE ... ** ** This transformation is necessary because the multiSelectOrderBy() routine ** above that generates the code for a compound SELECT with an ORDER BY clause ** uses a merge algorithm that requires the same collating sequence on the ** result columns as on the ORDER BY clause. See ticket | | | 150437 150438 150439 150440 150441 150442 150443 150444 150445 150446 150447 150448 150449 150450 150451 |
** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2)
** ORDER BY ... COLLATE ...
**
** This transformation is necessary because the multiSelectOrderBy() routine
** above that generates the code for a compound SELECT with an ORDER BY clause
** uses a merge algorithm that requires the same collating sequence on the
** result columns as on the ORDER BY clause. See ticket
** http://sqlite.org/src/info/6709574d2a
**
** This transformation is only needed for EXCEPT, INTERSECT, and UNION.
** The UNION ALL operator works fine with multiSelectOrderBy() even when
** there are COLLATE terms in the ORDER BY.
*/
static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
int i;
|
| ︙ | ︙ | |||
152652 152653 152654 152655 152656 152657 152658 152659 152660 152661 152662 152663 152664 152665 |
/* Begin the database scan. */
TREETRACE(0x2,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
p->pEList, p, wctrlFlags, p->nSelectRow);
if( pWInfo==0 ) goto select_end;
if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
}
if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
}
if( sSort.pOrderBy ){
sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo);
sSort.labelOBLopt = sqlite3WhereOrderByLimitOptLabel(pWInfo);
| > > > > > > | 152968 152969 152970 152971 152972 152973 152974 152975 152976 152977 152978 152979 152980 152981 152982 152983 152984 152985 152986 152987 |
/* Begin the database scan. */
TREETRACE(0x2,pParse,p,("WhereBegin\n"));
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
p->pEList, p, wctrlFlags, p->nSelectRow);
if( pWInfo==0 ) goto select_end;
if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){
/* TUNING: For a UNION CTE, because UNION is implies DISTINCT,
** reduce the estimated output row count by 8 (LogEst 30).
** Search for tag-20250414a to see other cases */
p->nSelectRow -= 30;
}
}
if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
}
if( sSort.pOrderBy ){
sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo);
sSort.labelOBLopt = sqlite3WhereOrderByLimitOptLabel(pWInfo);
|
| ︙ | ︙ | |||
156923 156924 156925 156926 156927 156928 156929 |
/* Default behavior: Report an error if the argument to VACUUM is
** not recognized */
iDb = sqlite3TwoPartName(pParse, pNm, pNm, &pNm);
if( iDb<0 ) goto build_vacuum_end;
#else
/* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments
** to VACUUM are silently ignored. This is a back-out of a bug fix that
| | | 157245 157246 157247 157248 157249 157250 157251 157252 157253 157254 157255 157256 157257 157258 157259 |
/* Default behavior: Report an error if the argument to VACUUM is
** not recognized */
iDb = sqlite3TwoPartName(pParse, pNm, pNm, &pNm);
if( iDb<0 ) goto build_vacuum_end;
#else
/* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments
** to VACUUM are silently ignored. This is a back-out of a bug fix that
** occurred on 2016-08-19 (https://sqlite.org/src/info/083f9e6270).
** The buggy behavior is required for binary compatibility with some
** legacy applications. */
iDb = sqlite3FindDb(pParse->db, pNm);
if( iDb<0 ) iDb = 0;
#endif
}
if( iDb!=1 ){
|
| ︙ | ︙ | |||
159818 159819 159820 159821 159822 159823 159824 | } } /* ** pX is an expression of the form: (vector) IN (SELECT ...) ** In other words, it is a vector IN operator with a SELECT clause on the | | | 160140 160141 160142 160143 160144 160145 160146 160147 160148 160149 160150 160151 160152 160153 160154 | } } /* ** pX is an expression of the form: (vector) IN (SELECT ...) ** In other words, it is a vector IN operator with a SELECT clause on the ** RHS. But not all terms in the vector are indexable and the terms might ** not be in the correct order for indexing. ** ** This routine makes a copy of the input pX expression and then adjusts ** the vector on the LHS with corresponding changes to the SELECT so that ** the vector contains only index terms and those terms are in the correct ** order. The modified IN expression is returned. The caller is responsible ** for deleting the returned expression. |
| ︙ | ︙ | |||
161640 161641 161642 161643 161644 161645 161646 |
** Actually, each subexpression is converted to "xN AND w" where w is
** the "interesting" terms of z - terms that did not originate in the
** ON or USING clause of a LEFT JOIN, and terms that are usable as
** indices.
**
** This optimization also only applies if the (x1 OR x2 OR ...) term
** is not contained in the ON clause of a LEFT JOIN.
| | | 161962 161963 161964 161965 161966 161967 161968 161969 161970 161971 161972 161973 161974 161975 161976 |
** Actually, each subexpression is converted to "xN AND w" where w is
** the "interesting" terms of z - terms that did not originate in the
** ON or USING clause of a LEFT JOIN, and terms that are usable as
** indices.
**
** This optimization also only applies if the (x1 OR x2 OR ...) term
** is not contained in the ON clause of a LEFT JOIN.
** See ticket http://sqlite.org/src/info/f2369304e4
**
** 2022-02-04: Do not push down slices of a row-value comparison.
** In other words, "w" or "y" may not be a slice of a vector. Otherwise,
** the initialization of the right-hand operand of the vector comparison
** might not occur, or might occur only in an OR branch that is not
** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1.
**
|
| ︙ | ︙ | |||
167613 167614 167615 167616 167617 167618 167619 |
}else{
pNew->nOut = nOutUnadjusted;
}
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
&& pNew->u.btree.nEq<pProbe->nColumn
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
| | | 167935 167936 167937 167938 167939 167940 167941 167942 167943 167944 167945 167946 167947 167948 167949 |
}else{
pNew->nOut = nOutUnadjusted;
}
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
&& pNew->u.btree.nEq<pProbe->nColumn
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
(pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
){
if( pNew->u.btree.nEq>3 ){
sqlite3ProgressCheck(pParse);
}
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
}
pNew->nOut = saved_nOut;
|
| ︙ | ︙ | |||
171071 171072 171073 171074 171075 171076 171077 |
if( pWInfo->pOrderBy ){
whereInterstageHeuristic(pWInfo);
wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1);
if( db->mallocFailed ) goto whereBeginError;
}
/* TUNING: Assume that a DISTINCT clause on a subquery reduces
| | > | 171393 171394 171395 171396 171397 171398 171399 171400 171401 171402 171403 171404 171405 171406 171407 171408 |
if( pWInfo->pOrderBy ){
whereInterstageHeuristic(pWInfo);
wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1);
if( db->mallocFailed ) goto whereBeginError;
}
/* TUNING: Assume that a DISTINCT clause on a subquery reduces
** the output size by a factor of 8 (LogEst -30). Search for
** tag-20250414a to see other cases.
*/
if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){
WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n",
pWInfo->nRowOut, pWInfo->nRowOut-30));
pWInfo->nRowOut -= 30;
}
|
| ︙ | ︙ | |||
188063 188064 188065 188066 188067 188068 188069 188070 188071 188072 188073 188074 188075 188076 | ** May you share freely, never taking more than you give. ** ****************************************************************************** ** */ #ifndef _FTSINT_H #define _FTSINT_H #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif /* FTS3/FTS4 require virtual tables */ #ifdef SQLITE_OMIT_VIRTUALTABLE | > > > > > > > | 188386 188387 188388 188389 188390 188391 188392 188393 188394 188395 188396 188397 188398 188399 188400 188401 188402 188403 188404 188405 188406 | ** May you share freely, never taking more than you give. ** ****************************************************************************** ** */ #ifndef _FTSINT_H #define _FTSINT_H /* #include <assert.h> */ /* #include <stdlib.h> */ /* #include <stddef.h> */ /* #include <stdio.h> */ /* #include <string.h> */ /* #include <stdarg.h> */ #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif /* FTS3/FTS4 require virtual tables */ #ifdef SQLITE_OMIT_VIRTUALTABLE |
| ︙ | ︙ | |||
189015 189016 189017 189018 189019 189020 189021 | /************** Continuing where we left off in fts3.c ***********************/ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) # define SQLITE_CORE 1 #endif | < < < < < < | 189345 189346 189347 189348 189349 189350 189351 189352 189353 189354 189355 189356 189357 189358 | /************** Continuing where we left off in fts3.c ***********************/ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) # define SQLITE_CORE 1 #endif /* #include "fts3.h" */ #ifndef SQLITE_CORE /* # include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #endif |
| ︙ | ︙ | |||
198967 198968 198969 198970 198971 198972 198973 |
Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab);
UNUSED_PARAMETER(idxStr);
UNUSED_PARAMETER(nVal);
fts3tokResetCursor(pCsr);
if( idxNum==1 ){
const char *zByte = (const char *)sqlite3_value_text(apVal[0]);
| | | 199291 199292 199293 199294 199295 199296 199297 199298 199299 199300 199301 199302 199303 199304 199305 |
Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab);
UNUSED_PARAMETER(idxStr);
UNUSED_PARAMETER(nVal);
fts3tokResetCursor(pCsr);
if( idxNum==1 ){
const char *zByte = (const char *)sqlite3_value_text(apVal[0]);
sqlite3_int64 nByte = sqlite3_value_bytes(apVal[0]);
pCsr->zInput = sqlite3_malloc64(nByte+1);
if( pCsr->zInput==0 ){
rc = SQLITE_NOMEM;
}else{
if( nByte>0 ) memcpy(pCsr->zInput, zByte, nByte);
pCsr->zInput[nByte] = 0;
rc = pTab->pMod->xOpen(pTab->pTok, pCsr->zInput, nByte, &pCsr->pCsr);
|
| ︙ | ︙ | |||
205947 205948 205949 205950 205951 205952 205953 |
case FTS3_MATCHINFO_AVGLENGTH:
case FTS3_MATCHINFO_LENGTH:
case FTS3_MATCHINFO_LCS:
nVal = pInfo->nCol;
break;
case FTS3_MATCHINFO_LHITS:
| | | | | 206271 206272 206273 206274 206275 206276 206277 206278 206279 206280 206281 206282 206283 206284 206285 206286 206287 206288 206289 206290 206291 206292 206293 206294 |
case FTS3_MATCHINFO_AVGLENGTH:
case FTS3_MATCHINFO_LENGTH:
case FTS3_MATCHINFO_LCS:
nVal = pInfo->nCol;
break;
case FTS3_MATCHINFO_LHITS:
nVal = (size_t)pInfo->nCol * pInfo->nPhrase;
break;
case FTS3_MATCHINFO_LHITS_BM:
nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32);
break;
default:
assert( cArg==FTS3_MATCHINFO_HITS );
nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3;
break;
}
return nVal;
}
static int fts3MatchinfoSelectDoctotal(
|
| ︙ | ︙ | |||
207514 207515 207516 207517 207518 207519 207520 | ** Support for JSON-5 extensions was added with version 3.42.0 (2023-05-16). ** All generated JSON text still conforms strictly to RFC-8259, but text ** with JSON-5 extensions is accepted as input. ** ** Beginning with version 3.45.0 (circa 2024-01-01), these routines also ** accept BLOB values that have JSON encoded using a binary representation ** called "JSONB". The name JSONB comes from PostgreSQL, however the on-disk | | | | 207838 207839 207840 207841 207842 207843 207844 207845 207846 207847 207848 207849 207850 207851 207852 207853 | ** Support for JSON-5 extensions was added with version 3.42.0 (2023-05-16). ** All generated JSON text still conforms strictly to RFC-8259, but text ** with JSON-5 extensions is accepted as input. ** ** Beginning with version 3.45.0 (circa 2024-01-01), these routines also ** accept BLOB values that have JSON encoded using a binary representation ** called "JSONB". The name JSONB comes from PostgreSQL, however the on-disk ** format for SQLite-JSONB is completely different and incompatible with ** PostgreSQL-JSONB. ** ** Decoding and interpreting JSONB is still O(N) where N is the size of ** the input, the same as text JSON. However, the constant of proportionality ** for JSONB is much smaller due to faster parsing. The size of each ** element in JSONB is encoded in its header, so there is no need to search ** for delimiters using persnickety syntax rules. JSONB seems to be about ** 3x faster than text JSON as a result. JSONB is also tends to be slightly |
| ︙ | ︙ | |||
207572 207573 207574 207575 207576 207577 207578 | ** 12 1 byte (0-255) 2 ** 13 2 byte (0-65535) 3 ** 14 4 byte (0-4294967295) 5 ** 15 8 byte (0-1.8e19) 9 ** ** The payload size need not be expressed in its minimal form. For example, ** if the payload size is 10, the size can be expressed in any of 5 different | | | | 207896 207897 207898 207899 207900 207901 207902 207903 207904 207905 207906 207907 207908 207909 207910 207911 207912 207913 207914 207915 207916 207917 207918 207919 207920 | ** 12 1 byte (0-255) 2 ** 13 2 byte (0-65535) 3 ** 14 4 byte (0-4294967295) 5 ** 15 8 byte (0-1.8e19) 9 ** ** The payload size need not be expressed in its minimal form. For example, ** if the payload size is 10, the size can be expressed in any of 5 different ** ways: (1) (X>>4)==10, (2) (X>>4)==12 following by one 0x0a byte, ** (3) (X>>4)==13 followed by 0x00 and 0x0a, (4) (X>>4)==14 followed by ** 0x00 0x00 0x00 0x0a, or (5) (X>>4)==15 followed by 7 bytes of 0x00 and ** a single byte of 0x0a. The shorter forms are preferred, of course, but ** sometimes when generating JSONB, the payload size is not known in advance ** and it is convenient to reserve sufficient header space to cover the ** largest possible payload size and then come back later and patch up ** the size when it becomes known, resulting in a non-minimal encoding. ** ** The value (X>>4)==15 is not actually used in the current implementation ** (as SQLite is currently unable to handle BLOBs larger than about 2GB) ** but is included in the design to allow for future enhancements. ** ** The payload follows the header. NULL, TRUE, and FALSE have no payload and ** their payload size must always be zero. The payload for INT, INT5, ** FLOAT, FLOAT5, TEXT, TEXTJ, TEXT5, and TEXTROW is text. Note that the ** "..." or '...' delimiters are omitted from the various text encodings. ** The payload for ARRAY and OBJECT is a list of additional elements that |
| ︙ | ︙ | |||
208656 208657 208658 208659 208660 208661 208662 |
const void *aPayload
){
if( jsonBlobExpand(pParse, pParse->nBlob+szPayload+9) ) return;
jsonBlobAppendNode(pParse, eType, szPayload, aPayload);
}
| | | 208980 208981 208982 208983 208984 208985 208986 208987 208988 208989 208990 208991 208992 208993 208994 |
const void *aPayload
){
if( jsonBlobExpand(pParse, pParse->nBlob+szPayload+9) ) return;
jsonBlobAppendNode(pParse, eType, szPayload, aPayload);
}
/* Append a node type byte together with the payload size and
** possibly also the payload.
**
** If aPayload is not NULL, then it is a pointer to the payload which
** is also appended. If aPayload is NULL, the pParse->aBlob[] array
** is resized (if necessary) so that it is big enough to hold the
** payload, but the payload is not appended and pParse->nBlob is left
** pointing to where the first byte of payload will eventually be.
|
| ︙ | ︙ | |||
209990 209991 209992 209993 209994 209995 209996 209997 209998 209999 210000 210001 210002 210003 | nBlob = pParse->nBlob; pParse->nBlob = pParse->nBlobAlloc; (void)jsonbPayloadSize(pParse, iRoot, &sz); pParse->nBlob = nBlob; sz += pParse->delta; pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz); } /* ** Modify the JSONB blob at pParse->aBlob by removing nDel bytes of ** content beginning at iDel, and replacing them with nIns bytes of ** content given by aIns. ** ** nDel may be zero, in which case no bytes are removed. But iDel is | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 210314 210315 210316 210317 210318 210319 210320 210321 210322 210323 210324 210325 210326 210327 210328 210329 210330 210331 210332 210333 210334 210335 210336 210337 210338 210339 210340 210341 210342 210343 210344 210345 210346 210347 210348 210349 210350 210351 210352 210353 210354 210355 210356 210357 210358 210359 210360 210361 210362 210363 210364 210365 210366 210367 210368 210369 210370 210371 210372 210373 210374 210375 210376 210377 210378 210379 210380 210381 210382 210383 210384 210385 210386 210387 210388 210389 210390 210391 210392 210393 210394 210395 210396 210397 210398 210399 210400 210401 210402 210403 |
nBlob = pParse->nBlob;
pParse->nBlob = pParse->nBlobAlloc;
(void)jsonbPayloadSize(pParse, iRoot, &sz);
pParse->nBlob = nBlob;
sz += pParse->delta;
pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz);
}
/*
** If the JSONB at aIns[0..nIns-1] can be expanded (by denormalizing the
** size field) by d bytes, then write the expansion into aOut[] and
** return true. In this way, an overwrite happens without changing the
** size of the JSONB, which reduces memcpy() operations and also make it
** faster and easier to update the B-Tree entry that contains the JSONB
** in the database.
**
** If the expansion of aIns[] by d bytes cannot be (easily) accomplished
** then return false.
**
** The d parameter is guaranteed to be between 1 and 8.
**
** This routine is an optimization. A correct answer is obtained if it
** always leaves the output unchanged and returns false.
*/
static int jsonBlobOverwrite(
u8 *aOut, /* Overwrite here */
const u8 *aIns, /* New content */
u32 nIns, /* Bytes of new content */
u32 d /* Need to expand new content by this much */
){
u32 szPayload; /* Bytes of payload */
u32 i; /* New header size, after expansion & a loop counter */
u8 szHdr; /* Size of header before expansion */
/* Lookup table for finding the upper 4 bits of the first byte of the
** expanded aIns[], based on the size of the expanded aIns[] header:
**
** 2 3 4 5 6 7 8 9 */
static const u8 aType[] = { 0xc0, 0xd0, 0, 0xe0, 0, 0, 0, 0xf0 };
if( (aIns[0]&0x0f)<=2 ) return 0; /* Cannot enlarge NULL, true, false */
switch( aIns[0]>>4 ){
default: { /* aIns[] header size 1 */
if( ((1<<d)&0x116)==0 ) return 0; /* d must be 1, 2, 4, or 8 */
i = d + 1; /* New hdr sz: 2, 3, 5, or 9 */
szHdr = 1;
break;
}
case 12: { /* aIns[] header size is 2 */
if( ((1<<d)&0x8a)==0) return 0; /* d must be 1, 3, or 7 */
i = d + 2; /* New hdr sz: 2, 5, or 9 */
szHdr = 2;
break;
}
case 13: { /* aIns[] header size is 3 */
if( d!=2 && d!=6 ) return 0; /* d must be 2 or 6 */
i = d + 3; /* New hdr sz: 5 or 9 */
szHdr = 3;
break;
}
case 14: { /* aIns[] header size is 5 */
if( d!=4 ) return 0; /* d must be 4 */
i = 9; /* New hdr sz: 9 */
szHdr = 5;
break;
}
case 15: { /* aIns[] header size is 9 */
return 0; /* No solution */
}
}
assert( i>=2 && i<=9 && aType[i-2]!=0 );
aOut[0] = (aIns[0] & 0x0f) | aType[i-2];
memcpy(&aOut[i], &aIns[szHdr], nIns-szHdr);
szPayload = nIns - szHdr;
while( 1/*edit-by-break*/ ){
i--;
aOut[i] = szPayload & 0xff;
if( i==1 ) break;
szPayload >>= 8;
}
assert( (szPayload>>8)==0 );
return 1;
}
/*
** Modify the JSONB blob at pParse->aBlob by removing nDel bytes of
** content beginning at iDel, and replacing them with nIns bytes of
** content given by aIns.
**
** nDel may be zero, in which case no bytes are removed. But iDel is
|
| ︙ | ︙ | |||
210012 210013 210014 210015 210016 210017 210018 210019 210020 210021 210022 210023 210024 210025 210026 210027 210028 210029 |
JsonParse *pParse, /* The JSONB to be modified is in pParse->aBlob */
u32 iDel, /* First byte to be removed */
u32 nDel, /* Number of bytes to remove */
const u8 *aIns, /* Content to insert */
u32 nIns /* Bytes of content to insert */
){
i64 d = (i64)nIns - (i64)nDel;
if( d!=0 ){
if( pParse->nBlob + d > pParse->nBlobAlloc ){
jsonBlobExpand(pParse, pParse->nBlob+d);
if( pParse->oom ) return;
}
memmove(&pParse->aBlob[iDel+nIns],
&pParse->aBlob[iDel+nDel],
pParse->nBlob - (iDel+nDel));
pParse->nBlob += d;
pParse->delta += d;
}
| > > > > > > | > | 210412 210413 210414 210415 210416 210417 210418 210419 210420 210421 210422 210423 210424 210425 210426 210427 210428 210429 210430 210431 210432 210433 210434 210435 210436 210437 210438 210439 210440 210441 210442 210443 210444 |
JsonParse *pParse, /* The JSONB to be modified is in pParse->aBlob */
u32 iDel, /* First byte to be removed */
u32 nDel, /* Number of bytes to remove */
const u8 *aIns, /* Content to insert */
u32 nIns /* Bytes of content to insert */
){
i64 d = (i64)nIns - (i64)nDel;
if( d<0 && d>=(-8) && aIns!=0
&& jsonBlobOverwrite(&pParse->aBlob[iDel], aIns, nIns, (int)-d)
){
return;
}
if( d!=0 ){
if( pParse->nBlob + d > pParse->nBlobAlloc ){
jsonBlobExpand(pParse, pParse->nBlob+d);
if( pParse->oom ) return;
}
memmove(&pParse->aBlob[iDel+nIns],
&pParse->aBlob[iDel+nDel],
pParse->nBlob - (iDel+nDel));
pParse->nBlob += d;
pParse->delta += d;
}
if( nIns && aIns ){
memcpy(&pParse->aBlob[iDel], aIns, nIns);
}
}
/*
** Return the number of escaped newlines to be ignored.
** An escaped newline is a one of the following byte sequences:
**
** 0x5c 0x0a
|
| ︙ | ︙ | |||
210786 210787 210788 210789 210790 210791 210792 |
}else{
sqlite3_result_error_nomem(ctx);
}
return 0;
}
/* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent
| | | 211193 211194 211195 211196 211197 211198 211199 211200 211201 211202 211203 211204 211205 211206 211207 |
}else{
sqlite3_result_error_nomem(ctx);
}
return 0;
}
/* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent
** arguments come in pairs where each pair contains a JSON path and
** content to insert or set at that patch. Do the updates
** and return the result.
**
** The specific operation is determined by eEdit, which can be one
** of JEDIT_INS, JEDIT_REPL, or JEDIT_SET.
*/
static void jsonInsertIntoBlob(
|
| ︙ | ︙ | |||
227531 227532 227533 227534 227535 227536 227537 |
if( sqlite3_value_type(argv[3])!=SQLITE_BLOB
|| sqlite3_value_bytes(argv[3])!=szPage
){
if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){
/* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and
** all subsequent pages to be deleted. */
pTab->iDbTrunc = iDb;
| < | > | 227938 227939 227940 227941 227942 227943 227944 227945 227946 227947 227948 227949 227950 227951 227952 227953 |
if( sqlite3_value_type(argv[3])!=SQLITE_BLOB
|| sqlite3_value_bytes(argv[3])!=szPage
){
if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){
/* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and
** all subsequent pages to be deleted. */
pTab->iDbTrunc = iDb;
pTab->pgnoTrunc = pgno-1;
pgno = 1;
}else{
zErr = "bad page value";
goto update_fail;
}
}
if( dbpageBeginTrans(pTab)!=SQLITE_OK ){
|
| ︙ | ︙ | |||
228848 228849 228850 228851 228852 228853 228854 228855 228856 228857 228858 228859 228860 228861 |
const char *zDb /* Name of db - "main", "temp" etc. */
){
int rc = SQLITE_OK;
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
rc = sessionTableInfo(pSession, db, zDb,
pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol,
&pTab->azDflt, &pTab->aiIdx, &abPK,
((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
);
if( rc==SQLITE_OK ){
int i;
| > > | 229255 229256 229257 229258 229259 229260 229261 229262 229263 229264 229265 229266 229267 229268 229269 229270 |
const char *zDb /* Name of db - "main", "temp" etc. */
){
int rc = SQLITE_OK;
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
sqlite3_free(pTab->azCol);
pTab->abPK = 0;
rc = sessionTableInfo(pSession, db, zDb,
pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol,
&pTab->azDflt, &pTab->aiIdx, &abPK,
((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
);
if( rc==SQLITE_OK ){
int i;
|
| ︙ | ︙ | |||
229855 229856 229857 229858 229859 229860 229861 229862 229863 229864 229865 229866 229867 229868 229869 229870 229871 229872 |
if( pzErrMsg ) *pzErrMsg = 0;
if( rc==SQLITE_OK ){
char *zExpr = 0;
sqlite3 *db = pSession->db;
SessionTable *pTo; /* Table zTbl */
/* Locate and if necessary initialize the target table object */
rc = sessionFindTable(pSession, zTbl, &pTo);
if( pTo==0 ) goto diff_out;
if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){
rc = pSession->rc;
goto diff_out;
}
/* Check the table schemas match */
if( rc==SQLITE_OK ){
int bHasPk = 0;
int bMismatch = 0;
| > > | | > > > > > > > > > > > > > > > > > > | | | | > > > > > > > | > | 230264 230265 230266 230267 230268 230269 230270 230271 230272 230273 230274 230275 230276 230277 230278 230279 230280 230281 230282 230283 230284 230285 230286 230287 230288 230289 230290 230291 230292 230293 230294 230295 230296 230297 230298 230299 230300 230301 230302 230303 230304 230305 230306 230307 230308 230309 230310 230311 230312 230313 230314 230315 230316 230317 230318 230319 230320 230321 230322 230323 230324 230325 230326 230327 |
if( pzErrMsg ) *pzErrMsg = 0;
if( rc==SQLITE_OK ){
char *zExpr = 0;
sqlite3 *db = pSession->db;
SessionTable *pTo; /* Table zTbl */
/* Locate and if necessary initialize the target table object */
pSession->bAutoAttach++;
rc = sessionFindTable(pSession, zTbl, &pTo);
pSession->bAutoAttach--;
if( pTo==0 ) goto diff_out;
if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){
rc = pSession->rc;
goto diff_out;
}
/* Check the table schemas match */
if( rc==SQLITE_OK ){
int bHasPk = 0;
int bMismatch = 0;
int nCol = 0; /* Columns in zFrom.zTbl */
int bRowid = 0;
u8 *abPK = 0;
const char **azCol = 0;
char *zDbExists = 0;
/* Check that database zFrom is attached. */
zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom);
if( zDbExists==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pDbExists = 0;
rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0);
if( rc==SQLITE_ERROR ){
rc = SQLITE_OK;
nCol = -1;
}
sqlite3_finalize(pDbExists);
sqlite3_free(zDbExists);
}
if( rc==SQLITE_OK && nCol==0 ){
rc = sessionTableInfo(0, db, zFrom, zTbl,
&nCol, 0, 0, &azCol, 0, 0, &abPK,
pSession->bImplicitPK ? &bRowid : 0
);
}
if( rc==SQLITE_OK ){
if( pTo->nCol!=nCol ){
if( nCol<=0 ){
rc = SQLITE_SCHEMA;
if( pzErrMsg ){
*pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl);
}
}else{
bMismatch = 1;
}
}else{
int i;
for(i=0; i<nCol; i++){
if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
if( abPK[i] ) bHasPk = 1;
}
|
| ︙ | ︙ | |||
241867 241868 241869 241870 241871 241872 241873 |
char c = (char)p->p[i];
if( c<'0' || c>'9' ){
sqlite3Fts5ParseError(
pParse, "expected integer, got \"%.*s\"", p->n, p->p
);
return;
}
| | > | 242304 242305 242306 242307 242308 242309 242310 242311 242312 242313 242314 242315 242316 242317 242318 242319 |
char c = (char)p->p[i];
if( c<'0' || c>'9' ){
sqlite3Fts5ParseError(
pParse, "expected integer, got \"%.*s\"", p->n, p->p
);
return;
}
if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0');
/* ^^^^^^^^^^^^^^^--- Prevent integer overflow */
}
}else{
nNear = FTS5_DEFAULT_NEARDIST;
}
pNear->nNear = nNear;
}
}
|
| ︙ | ︙ | |||
256773 256774 256775 256776 256777 256778 256779 |
static void fts5SourceIdFunc(
sqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
sqlite3_value **apUnused /* Function arguments */
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
| | | 257211 257212 257213 257214 257215 257216 257217 257218 257219 257220 257221 257222 257223 257224 257225 |
static void fts5SourceIdFunc(
sqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
sqlite3_value **apUnused /* Function arguments */
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2025-04-15 21:59:38 d22475b81c4e26ccc50f3b5626d43b32f7a2de34e5a764539554665bdda735d5", -1, SQLITE_TRANSIENT);
}
/*
** Implementation of fts5_locale(LOCALE, TEXT) function.
**
** If parameter LOCALE is NULL, or a zero-length string, then a copy of
** TEXT is returned. Otherwise, both LOCALE and TEXT are interpreted as
|
| ︙ | ︙ | |||
260836 260837 260838 260839 260840 260841 260842 |
for(; i<128 && i<n; i++){
aAscii[i] = (u8)bToken;
}
iTbl++;
}
aAscii[0] = 0; /* 0x00 is never a token character */
}
| < | 261274 261275 261276 261277 261278 261279 261280 261281 261282 261283 261284 261285 261286 261287 |
for(; i<128 && i<n; i++){
aAscii[i] = (u8)bToken;
}
iTbl++;
}
aAscii[0] = 0; /* 0x00 is never a token character */
}
/*
** 2015 May 30
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
|
| ︙ | ︙ |
Changes to extsrc/sqlite3.h.
| ︙ | ︙ | |||
129 130 131 132 133 134 135 | ** The SQLITE_VERSION_NUMBER for any given release of SQLite will also ** be larger than the release from which it is derived. Either Y will ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** ** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the | | | | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | ** The SQLITE_VERSION_NUMBER for any given release of SQLite will also ** be larger than the release from which it is derived. Either Y will ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** ** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the ** <a href="http://fossil-scm.org/">Fossil configuration management ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID ** string contains the date and time of the check-in (UTC) and a SHA1 ** or SHA3-256 hash of the entire source tree. If the source code has ** been edited in any way since it was last checked in, then the last ** four hexadecimal digits of the hash may be modified. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.50.0" #define SQLITE_VERSION_NUMBER 3050000 #define SQLITE_SOURCE_ID "2025-04-15 21:59:38 d22475b81c4e26ccc50f3b5626d43b32f7a2de34e5a764539554665bdda735d5" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros |
| ︙ | ︙ | |||
11625 11626 11627 11628 11629 11630 11631 | ** </ul> ** ** To clarify, if this function is called and then a changeset constructed ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** | > | | | 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 | ** </ul> ** ** To clarify, if this function is called and then a changeset constructed ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** ** Unless the call to this function is a no-op as described above, it is an ** error if database zFrom does not exist or does not contain the required ** compatible table. ** ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ |
| ︙ | ︙ |
Changes to skins/blitz/footer.txt.
1 2 3 4 5 |
</div> <!-- end div container -->
</div> <!-- end div middle max-full-width -->
<footer>
<div class="container">
<div class="pull-right">
| | | 1 2 3 4 5 6 7 8 9 10 |
</div> <!-- end div container -->
</div> <!-- end div middle max-full-width -->
<footer>
<div class="container">
<div class="pull-right">
<a href="https://fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
</div>
This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
</div>
</footer>
|
Changes to skins/darkmode/footer.txt.
1 2 3 |
<footer>
<div class="container">
<div class="pull-right">
| | | 1 2 3 4 5 6 7 8 |
<footer>
<div class="container">
<div class="pull-right">
<a href="https://fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
</div>
This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
</div>
</footer>
|
Changes to skins/default/header.txt.
| ︙ | ︙ | |||
26 27 28 29 30 31 32 |
set logourl $baseurl
}
return $logourl
}
set logourl [getLogoUrl $baseurl]
</th1>
<a href="$logourl">
| | | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
set logourl $baseurl
}
return $logourl
}
set logourl [getLogoUrl $baseurl]
</th1>
<a href="$logourl">
<img src="$logo_image_url" border="0" alt="$<project_name>">
</a>
</div>
<div class="title">
<h1>$<project_name></h1>
<span class="page-title">$<title></span>
</div>
<div class="status">
|
| ︙ | ︙ |
Changes to skins/eagle/footer.txt.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 |
}
proc getVersion { version } {
set length [string length $version]
return [string range $version 1 [expr {$length - 2}]]
}
set version [getVersion $manifest_version]
set tclVersion [getTclVersion]
| | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
}
proc getVersion { version } {
set length [string length $version]
return [string range $version 1 [expr {$length - 2}]]
}
set version [getVersion $manifest_version]
set tclVersion [getTclVersion]
set fossilUrl https://fossil-scm.org
set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end]
</th1>
This page was generated in about
<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
<a href="$fossilUrl/">Fossil</a>
version $release_version $tclVersion
<a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
|
| ︙ | ︙ |
Changes to skins/eagle/header.txt.
| ︙ | ︙ | |||
63 64 65 66 67 68 69 |
set logourl [getLogoUrl $baseurl]
} else {
# Link logo to the top of the current repo
set logourl $baseurl
}
</th1>
<a href="$logourl">
| | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
set logourl [getLogoUrl $baseurl]
} else {
# Link logo to the top of the current repo
set logourl $baseurl
}
</th1>
<a href="$logourl">
<img src="$logo_image_url" border="0" alt="$<project_name>">
</a>
</div>
<div class="title">$<title></div>
<div class="status"><nobr><th1>
if {[info exists login]} {
puts "Logged in as $login"
} else {
|
| ︙ | ︙ |
Changes to skins/original/footer.txt.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 |
}
proc getVersion { version } {
set length [string length $version]
return [string range $version 1 [expr {$length - 2}]]
}
set version [getVersion $manifest_version]
set tclVersion [getTclVersion]
| | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
}
proc getVersion { version } {
set length [string length $version]
return [string range $version 1 [expr {$length - 2}]]
}
set version [getVersion $manifest_version]
set tclVersion [getTclVersion]
set fossilUrl https://fossil-scm.org
set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end]
</th1>
This page was generated in about
<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
<a href="$fossilUrl/">Fossil</a>
version $release_version $tclVersion
<a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
|
| ︙ | ︙ |
Changes to skins/original/header.txt.
| ︙ | ︙ | |||
57 58 59 60 61 62 63 |
set logourl $baseurl
}
return $logourl
}
set logourl [getLogoUrl $baseurl]
</th1>
<a href="$logourl">
| | | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
set logourl $baseurl
}
return $logourl
}
set logourl [getLogoUrl $baseurl]
</th1>
<a href="$logourl">
<img src="$logo_image_url" border="0" alt="$<project_name>">
</a>
</div>
<div class="title">$<title></div>
<div class="status"><nobr><th1>
if {[info exists login]} {
puts "Logged in as $login"
} else {
|
| ︙ | ︙ |
Changes to skins/xekri/css.txt.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
color: #eee;
font-family: Monospace;
font-size: 1em;
min-height: 100%;
}
body {
margin: 0;
padding: 0;
text-size-adjust: none;
}
a {
color: #40a0ff;
| > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
color: #eee;
font-family: Monospace;
font-size: 1em;
min-height: 100%;
}
body {
background-color: #333;
margin: 0;
padding: 0;
text-size-adjust: none;
}
a {
color: #40a0ff;
|
| ︙ | ︙ | |||
880 881 882 883 884 885 886 887 888 889 890 891 892 893 |
white-space: nowrap;
}
/* the format for the timeline version links */
a.timelineHistLink {
}
/**************************************
* User Edit
*/
/* layout definition for the capabilities box on the user edit detail page */
div.ueditCapBox {
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 |
white-space: nowrap;
}
/* the format for the timeline version links */
a.timelineHistLink {
}
/* Timeline graph style taken from Ardoise, with
** minor adjustments (2025-03-28) */
.tl-canvas {
margin: 0 6px 0 10px
}
.tl-rail {
width: 18px
}
.tl-mergeoffset {
width: 2px
}
.tl-nodemark {
margin-top: .8em
}
.tl-node {
width: 10px;
height: 10px;
border: 1px solid #bbb;
background: #111;
cursor: pointer
}
.tl-node.leaf:after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 4px;
height: 4px;
background: #bbb
}
.tl-node.closed-leaf svg {
position: absolute;
top: 0px;
left: 0px;
width: 10px;
height: 10px;
color: #bbb;
}
.tl-node.sel:after {
content: '';
position: absolute;
top: 1px;
left: 1px;
width: 8px;
height: 8px;
background: #ff8000
}
.tl-arrow {
width: 0;
height: 0;
transform: scale(.999);
border: 0 solid transparent
}
.tl-arrow.u {
margin-top: -1px;
border-width: 0 3px;
border-bottom: 7px solid
}
.tl-arrow.u.sm {
border-bottom: 5px solid #bbb
}
.tl-line {
background: #bbb;
width: 2px
}
.tl-arrow.merge {
height: 1px;
border-width: 2px 0
}
.tl-arrow.merge.l {
border-right: 3px solid #bbb
}
.tl-arrow.merge.r {
border-left: 3px solid #bbb
}
.tl-line.merge {
width: 1px
}
.tl-arrow.cherrypick {
height: 1px;
border-width: 2px 0;
}
.tl-arrow.cherrypick.l {
border-right: 3px solid #bbb;
}
.tl-arrow.cherrypick.r {
border-left: 3px solid #bbb;
}
.tl-line.cherrypick.h {
width: 0px;
border-top: 1px dashed #bbb;
border-left: 0px dashed #bbb;
background: rgba(255,255,255,0);
}
.tl-line.cherrypick.v {
width: 0px;
border-top: 0px dashed #bbb;
border-left: 1px dashed #bbb;
background: rgba(255,255,255,0);
}
/**************************************
* User Edit
*/
/* layout definition for the capabilities box on the user edit detail page */
div.ueditCapBox {
|
| ︙ | ︙ |
Changes to skins/xekri/header.txt.
| ︙ | ︙ | |||
63 64 65 66 67 68 69 |
set logourl [getLogoUrl $baseurl]
} else {
# Link logo to the top of the current repo
set logourl $baseurl
}
</th1>
<a href="$logourl">
| | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
set logourl [getLogoUrl $baseurl]
} else {
# Link logo to the top of the current repo
set logourl $baseurl
}
</th1>
<a href="$logourl">
<img src="$logo_image_url" border="0" alt="$<project_name>">
</a>
</div>
<div class="title">$<title></div>
<div class="status"><nobr>
<th1>
if {[info exists login]} {
puts "Logged in as $login"
|
| ︙ | ︙ |
Changes to src/add.c.
| ︙ | ︙ | |||
894 895 896 897 898 899 900 | ** ** The original name of the file is zOrig. The new filename is zNew. */ static void mv_one_file( int vid, const char *zOrig, const char *zNew, | | > > > > > > > | 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 |
**
** The original name of the file is zOrig. The new filename is zNew.
*/
static void mv_one_file(
int vid,
const char *zOrig,
const char *zNew,
int dryRunFlag,
int moveFiles
){
int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s",
zNew, filename_collation());
if( x>=0 ){
if( x==0 ){
if( !filenames_are_case_sensitive() && fossil_stricmp(zOrig,zNew)==0 ){
/* Case change only */
}else{
fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'"
" is currently under management", zOrig, zNew, zNew);
}
}else{
fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has "
"not yet been committed", zOrig, zNew, zNew);
}
}
if( moveFiles ){
if( file_size(zNew, ExtFILE) != -1 ){
fossil_fatal("cannot rename '%s' to '%s' on disk since another file"
" named '%s' already exists", zOrig, zNew, zNew);
}
}
fossil_print("RENAME %s %s\n", zOrig, zNew);
if( !dryRunFlag ){
db_multi_exec(
"UPDATE vfile SET pathname='%q' WHERE pathname='%q' %s AND vid=%d",
zNew, zOrig, filename_collation(), vid
);
|
| ︙ | ︙ | |||
1133 1134 1135 1136 1137 1138 1139 |
db_finalize(&q);
}
}
db_prepare(&q, "SELECT f, t FROM mv ORDER BY f");
while( db_step(&q)==SQLITE_ROW ){
const char *zFrom = db_column_text(&q, 0);
const char *zTo = db_column_text(&q, 1);
| | | 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 |
db_finalize(&q);
}
}
db_prepare(&q, "SELECT f, t FROM mv ORDER BY f");
while( db_step(&q)==SQLITE_ROW ){
const char *zFrom = db_column_text(&q, 0);
const char *zTo = db_column_text(&q, 1);
mv_one_file(vid, zFrom, zTo, dryRunFlag, moveFiles);
if( moveFiles ) add_file_to_move(zFrom, zTo);
}
db_finalize(&q);
undo_reset();
db_end_transaction(0);
if( moveFiles ) process_files_to_move(dryRunFlag);
fossil_free(zDest);
|
| ︙ | ︙ |
Changes to src/alerts.c.
| ︙ | ︙ | |||
49 50 51 52 53 54 55 | @ -- a - Announcements @ -- c - Check-ins @ -- f - Forum posts @ -- k - ** Special: Unsubscribed using /oneclickunsub @ -- n - New forum threads @ -- r - Replies to my own forum posts @ -- t - Ticket changes | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | @ -- a - Announcements @ -- c - Check-ins @ -- f - Forum posts @ -- k - ** Special: Unsubscribed using /oneclickunsub @ -- n - New forum threads @ -- r - Replies to my own forum posts @ -- t - Ticket changes @ -- u - Changes of users' permissions (admins only) @ -- w - Wiki changes @ -- x - Edits to forum posts @ -- Probably different codes will be added in the future. In the future @ -- we might also add a separate table that allows subscribing to email @ -- notifications for specific branches or tags or tickets. @ -- @ CREATE TABLE repository.subscriber( |
| ︙ | ︙ | |||
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
if( g.perm.Admin ){
if( fossil_strcmp(g.zPath,"subscribers") ){
style_submenu_element("Subscribers","%R/subscribers");
}
if( fossil_strcmp(g.zPath,"subscribe") ){
style_submenu_element("Add New Subscriber","%R/subscribe");
}
}
}
/*
** WEBPAGE: setup_notification
**
** Administrative page for configuring and controlling email notification.
** Normally accessible via the /Admin/Notification menu.
*/
void setup_notification(void){
static const char *const azSendMethods[] = {
"off", "Disabled",
| > > > | | > | > < > | > | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
if( g.perm.Admin ){
if( fossil_strcmp(g.zPath,"subscribers") ){
style_submenu_element("Subscribers","%R/subscribers");
}
if( fossil_strcmp(g.zPath,"subscribe") ){
style_submenu_element("Add New Subscriber","%R/subscribe");
}
if( fossil_strcmp(g.zPath,"setup_notification") ){
style_submenu_element("Notification Setup","%R/setup_notification");
}
}
}
/*
** WEBPAGE: setup_notification
**
** Administrative page for configuring and controlling email notification.
** Normally accessible via the /Admin/Notification menu.
*/
void setup_notification(void){
static const char *const azSendMethods[] = {
"off", "Disabled",
"relay", "SMTP relay",
"db", "Store in a database",
"dir", "Store in a directory",
"pipe", "Pipe to a command",
};
login_check_credentials();
if( !g.perm.Setup ){
login_needed(0);
return;
}
db_begin_transaction();
alert_submenu_common();
style_submenu_element("Send Announcement","%R/announce");
style_set_current_feature("alerts");
style_header("Email Notification Setup");
@ <form action="%R/setup_notification" method="post"><div>
@ <h1>Status   <input type="submit" name="submit" value="Refresh"></h1>
@ </form>
@ <table class="label-value">
if( alert_enabled() ){
stats_for_email();
}else{
@ <th>Disabled</th>
}
@ </table>
@ <hr>
@ <form action="%R/setup_notification" method="post"><div>
@ <h1> Configuration </h1>
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
@ <hr>
login_insert_csrf_secret();
entry_attribute("Canonical Server URL", 40, "email-url",
"eurl", "", 0);
@ <p><b>Required.</b>
@ This URL is used as the basename for hyperlinks included in
@ email alert text. Omit the trailing "/".
|
| ︙ | ︙ | |||
389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
"off", count(azSendMethods)/2, azSendMethods);
@ <p>How to send email. Requires auxiliary information from the fields
@ that follow. Hint: Use the <a href="%R/announce">/announce</a> page
@ to send test message to debug this setting.
@ (Property: "email-send-method")</p>
alert_schema(1);
entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command",
"ecmd", "sendmail -ti", 0);
@ <p>When the send method is "pipe to a command", this is the command
@ that is run. Email messages are piped into the standard input of this
@ command. The command is expected to extract the sender address,
@ recipient addresses, and subject from the header of the piped email
@ text. (Property: "email-send-command")</p>
| > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
"off", count(azSendMethods)/2, azSendMethods);
@ <p>How to send email. Requires auxiliary information from the fields
@ that follow. Hint: Use the <a href="%R/announce">/announce</a> page
@ to send test message to debug this setting.
@ (Property: "email-send-method")</p>
alert_schema(1);
entry_attribute("SMTP Relay Host", 60, "email-send-relayhost",
"esrh", "localhost", 0);
@ <p>When the send method is "SMTP relay", each email message is
@ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission
@ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally
@ append a colon and TCP port number (ex: smtp.example.com:587).
@ The default TCP port number is 25.
@ Usage Hint: If Fossil is running inside of a chroot jail, then it might
@ not be able to resolve hostnames. Work around this by using a raw IP
@ address or create a "/etc/hosts" file inside the chroot jail.
@ (Property: "email-send-relayhost")</p>
@
entry_attribute("Store Emails In This Database", 60, "email-send-db",
"esdb", "", 0);
@ <p>When the send method is "store in a database", each email message is
@ stored in an SQLite database file with the name given here.
@ (Property: "email-send-db")</p>
entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command",
"ecmd", "sendmail -ti", 0);
@ <p>When the send method is "pipe to a command", this is the command
@ that is run. Email messages are piped into the standard input of this
@ command. The command is expected to extract the sender address,
@ recipient addresses, and subject from the header of the piped email
@ text. (Property: "email-send-command")</p>
entry_attribute("Store Emails In This Directory", 60, "email-send-dir",
"esdir", "", 0);
@ <p>When the send method is "store in a directory", each email message is
@ stored as a separate file in the directory shown here.
@ (Property: "email-send-dir")</p>
@ <hr>
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
@ </div></form>
db_end_transaction(0);
style_finish_page();
}
|
| ︙ | ︙ | |||
628 629 630 631 632 633 634 |
}
}else if( fossil_strcmp(p->zDest, "pipe")==0 ){
emailerGetSetting(p, &p->zCmd, "email-send-command");
}else if( fossil_strcmp(p->zDest, "dir")==0 ){
emailerGetSetting(p, &p->zDir, "email-send-dir");
}else if( fossil_strcmp(p->zDest, "blob")==0 ){
blob_init(&p->out, 0, 0);
| | > > > | | > > > > > | 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 |
}
}else if( fossil_strcmp(p->zDest, "pipe")==0 ){
emailerGetSetting(p, &p->zCmd, "email-send-command");
}else if( fossil_strcmp(p->zDest, "dir")==0 ){
emailerGetSetting(p, &p->zDir, "email-send-dir");
}else if( fossil_strcmp(p->zDest, "blob")==0 ){
blob_init(&p->out, 0, 0);
}else if( fossil_strcmp(p->zDest, "relay")==0
|| fossil_strcmp(p->zDest, "debug-relay")==0
){
const char *zRelay = 0;
emailerGetSetting(p, &zRelay, "email-send-relayhost");
if( zRelay ){
u32 smtpFlags = SMTP_DIRECT;
if( mFlags & ALERT_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT;
blob_init(&p->out, 0, 0);
p->pSmtp = smtp_session_new(domain_of_addr(p->zFrom), zRelay,
smtpFlags, 0);
if( p->pSmtp==0 || p->pSmtp->zErr ){
emailerError(p, "Could not start SMTP session: %s",
p->pSmtp ? p->pSmtp->zErr : "reason unknown");
}else if( p->zDest[0]=='d' ){
smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out);
}
}
}
return p;
}
/*
** Scan the header of the email message in pMsg looking for the
|
| ︙ | ︙ | |||
966 967 968 969 970 971 972 |
blob_appendf(pOut, "From: %s <%s@%s>\r\n",
zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
}else{
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
}
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
| < < < | 982 983 984 985 986 987 988 989 990 991 992 993 994 995 |
blob_appendf(pOut, "From: %s <%s@%s>\r\n",
zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
}else{
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
}
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
/* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
** the current unix-time in hex, $(random) is a 64-bit random number,
** and $(from) is the domain part of the email-self setting. */
sqlite3_randomness(sizeof(r1), &r1);
r2 = time(0);
blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n",
|
| ︙ | ︙ | |||
1014 1015 1016 1017 1018 1019 1020 1021 |
}else if( p->zDir ){
char *zFile = file_time_tempname(p->zDir, ".email");
blob_write_to_file(&all, zFile);
fossil_free(zFile);
}else if( p->pSmtp ){
char **azTo = 0;
int nTo = 0;
email_header_to(pHdr, &nTo, &azTo);
| > | | > > > > > > > | 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 |
}else if( p->zDir ){
char *zFile = file_time_tempname(p->zDir, ".email");
blob_write_to_file(&all, zFile);
fossil_free(zFile);
}else if( p->pSmtp ){
char **azTo = 0;
int nTo = 0;
SmtpSession *pSmtp = p->pSmtp;
email_header_to(pHdr, &nTo, &azTo);
if( nTo>0 && !pSmtp->bFatal ){
smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
if( pSmtp->zErr && !pSmtp->bFatal ){
smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
}
if( pSmtp->zErr ){
fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry",
pSmtp->zErr);
}
email_header_to_free(nTo, azTo);
}
}else if( strcmp(p->zDest, "stdout")==0 ){
char **azTo = 0;
int nTo = 0;
int i;
email_header_to(pHdr, &nTo, &azTo);
|
| ︙ | ︙ | |||
1123 1124 1125 1126 1127 1128 1129 | */ /* ** SETTING: email-listid width=40 ** If this setting is not an empty string, then it becomes the argument to ** a "List-ID:" header that is added to all out-bound notification emails. */ /* | | | 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 | */ /* ** SETTING: email-listid width=40 ** If this setting is not an empty string, then it becomes the argument to ** a "List-ID:" header that is added to all out-bound notification emails. */ /* ** SETTING: email-send-relayhost width=40 sensitive default=127.0.0.1 ** This is the hostname and TCP port to which output email messages ** are sent when email-send-method is "relay". There should be an ** SMTP server configured as a Mail Submission Agent listening on the ** designated host and port and all times. */ |
| ︙ | ︙ | |||
1702 1703 1704 1705 1706 1707 1708 |
}
if( g.perm.RdWiki ){
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
@ Wiki</label><br>
}
if( g.perm.Admin ){
@ <label><input type="checkbox" name="su" %s(PCK("su"))> \
| | | 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 |
}
if( g.perm.RdWiki ){
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
@ Wiki</label><br>
}
if( g.perm.Admin ){
@ <label><input type="checkbox" name="su" %s(PCK("su"))> \
@ User permission changes</label>
}
di = PB("di");
@ </td></tr>
@ <tr>
@ <td class="form_label">Delivery:</td>
@ <td><select size="1" name="di">
@ <option value="0" %s(di?"":"selected")>Individual Emails</option>
|
| ︙ | ︙ | |||
2112 2113 2114 2115 2116 2117 2118 |
}
if( g.perm.Admin ){
/* Corner-case bug: if an admin assigns 'u' to a non-admin, that
** subscription will get removed if the user later edits their
** subscriptions, as non-admins are not permitted to add that
** subscription. */
@ <label><input type="checkbox" name="su" %s(su?"checked":"")>\
| | | 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 |
}
if( g.perm.Admin ){
/* Corner-case bug: if an admin assigns 'u' to a non-admin, that
** subscription will get removed if the user later edits their
** subscriptions, as non-admins are not permitted to add that
** subscription. */
@ <label><input type="checkbox" name="su" %s(su?"checked":"")>\
@ User permission changes</label>
}
@ </td></tr>
if( strchr(ssub,'k')!=0 ){
@ <tr><td></td><td> ↑
@ Note: User did a one-click unsubscribe</td></tr>
}
@ <tr>
|
| ︙ | ︙ | |||
3189 3190 3191 3192 3193 3194 3195 |
if( blob_size(&p->hdr)>0 ){
/* This alert should be sent as a separate email */
Blob fhdr, fbody;
blob_init(&fhdr, 0, 0);
blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
| > > | | | | | | | | > | 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 |
if( blob_size(&p->hdr)>0 ){
/* This alert should be sent as a separate email */
Blob fhdr, fbody;
blob_init(&fhdr, 0, 0);
blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
if( pSender->zListId && pSender->zListId[0] ){
blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
zUrl, zCode);
blob_appendf(&fhdr,
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
zUrl, zCode);
/* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
** zUrl, zCode); */
}
alert_send(pSender,&fhdr,&fbody,p->zFromName);
nSent++;
blob_reset(&fhdr);
blob_reset(&fbody);
}else{
/* Events other than forum posts are gathered together into
** a single email message */
|
| ︙ | ︙ | |||
3219 3220 3221 3222 3223 3224 3225 |
}
nHit++;
blob_append(&body, "\n", 1);
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
}
}
if( nHit==0 ) continue;
| > > | | > | | | > | 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 |
}
nHit++;
blob_append(&body, "\n", 1);
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
}
}
if( nHit==0 ) continue;
if( pSender->zListId && pSender->zListId[0] ){
blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
zUrl, zCode);
blob_appendf(&hdr,
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
zUrl, zCode);
}
alert_send(pSender,&hdr,&body,0);
nSent++;
blob_truncate(&hdr, 0);
blob_truncate(&body, 0);
}
blob_reset(&hdr);
blob_reset(&body);
|
| ︙ | ︙ | |||
3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 |
" WHERE lastContact<=%d AND lastContact>%d"
" AND NOT sdonotcall"
" AND length(sdigest)>0",
iNewWarn, iOldWarn
);
while( db_step(&q)==SQLITE_ROW ){
Blob hdr, body;
blob_init(&hdr, 0, 0);
blob_init(&body, 0, 0);
alert_renewal_msg(&hdr, &body,
| > | > > > > > > > > > | 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 |
" WHERE lastContact<=%d AND lastContact>%d"
" AND NOT sdonotcall"
" AND length(sdigest)>0",
iNewWarn, iOldWarn
);
while( db_step(&q)==SQLITE_ROW ){
Blob hdr, body;
const char *zCode = db_column_text(&q,0);
blob_init(&hdr, 0, 0);
blob_init(&body, 0, 0);
alert_renewal_msg(&hdr, &body,
zCode,
db_column_int(&q,1),
db_column_text(&q,2),
db_column_text(&q,3),
zRepoName, zUrl);
if( pSender->zListId && pSender->zListId[0] ){
blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
zUrl, zCode);
blob_appendf(&hdr,
"List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
zUrl, zCode);
}
alert_send(pSender,&hdr,&body,0);
blob_reset(&hdr);
blob_reset(&body);
}
db_finalize(&q);
if( (flags & SENDALERT_PRESERVE)==0 ){
if( iOldWarn>0 ){
|
| ︙ | ︙ | |||
3434 3435 3436 3437 3438 3439 3440 |
char *zErr;
const char *zTo = PT("to");
char *zSubject = PT("subject");
int bAll = PB("all");
int bAA = PB("aa");
int bMods = PB("mods");
const char *zSub = db_get("email-subname", "[Fossil Repo]");
| > > | > > > > > > > > > > | | 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 |
char *zErr;
const char *zTo = PT("to");
char *zSubject = PT("subject");
int bAll = PB("all");
int bAA = PB("aa");
int bMods = PB("mods");
const char *zSub = db_get("email-subname", "[Fossil Repo]");
const char *zName = P("name"); /* Debugging options */
const char *zDest = 0; /* How to send the announcement */
int bTest = 0;
Blob hdr, body;
if( fossil_strcmp(zName, "test2")==0 ){
bTest = 2;
zDest = "blob";
}else if( fossil_strcmp(zName, "test3")==0 ){
bTest = 3;
if( fossil_strcmp(db_get("email-send-method",""),"relay")==0 ){
zDest = "debug-relay";
}
}
blob_init(&body, 0, 0);
blob_init(&hdr, 0, 0);
blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
pSender = alert_sender_new(zDest, 0);
if( zTo[0] ){
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
alert_send(pSender, &hdr, &body, 0);
}
if( bAll || bAA || bMods ){
Stmt q;
int nUsed = blob_size(&body);
|
| ︙ | ︙ | |||
3477 3478 3479 3480 3481 3482 3483 |
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
zURL, zCode);
}
alert_send(pSender, &hdr, &body, 0);
}
db_finalize(&q);
}
| | | | | > > > > > > | > | | < > | > | > > > > | < > > > | | | > > > > > > > | 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 |
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
zURL, zCode);
}
alert_send(pSender, &hdr, &body, 0);
}
db_finalize(&q);
}
if( bTest && blob_size(&pSender->out) ){
/* If the URL is "/announce/test2" then no email is actually sent.
** Instead, the text of the email that would have been sent is
** displayed in the result window.
**
** If the URL is "/announce/test3" and the email-send-method is "relay"
** then the announcement is sent as it normally would be, but a
** transcript of the SMTP conversation with the MTA is shown here.
*/
blob_trim(&pSender->out);
@ <pre style='border: 2px solid blue; padding: 1ex;'>
@ %h(blob_str(&pSender->out))
@ </pre>
blob_reset(&pSender->out);
}
zErr = pSender->zErr;
pSender->zErr = 0;
alert_sender_free(pSender);
return zErr;
}
/*
** WEBPAGE: announce
**
** A web-form, available to users with the "Send-Announcement" or "A"
** capability, that allows one to send announcements to whomever
** has subscribed to receive announcements. The administrator can
** also send a message to an arbitrary email address and/or to all
** subscribers regardless of whether or not they have elected to
** receive announcements.
*/
void announce_page(void){
const char *zAction = "announce";
const char *zName = PD("name","");
/*
** Debugging Notes:
**
** /announce/test1 -> Shows query parameter values
** /announce/test2 -> Shows the formatted message but does
** not send it.
** /announce/test3 -> Sends the message, but also shows
** the SMTP transcript.
*/
login_check_credentials();
if( !g.perm.Announce ){
login_needed(0);
return;
}
if( !g.perm.Setup ){
zName = 0; /* Disable debugging feature for non-admin users */
}
style_set_current_feature("alerts");
if( fossil_strcmp(zName,"test1")==0 ){
/* Visit the /announce/test1 page to see the CGI variables */
zAction = "announce/test1";
@ <p style='border: 1px solid black; padding: 1ex;'>
cgi_print_all(0, 0, 0);
@ </p>
}else if( P("submit")!=0 && cgi_csrf_safe(2) ){
char *zErr = alert_send_announcement();
style_header("Announcement Sent");
if( zErr ){
@ <h1>Error</h1>
@ <p>The following error was reported by the
@ announcement-sending subsystem:
@ <blockquote><pre>
@ %h(zErr)
@ </pre></blockquote>
}else{
@ <p>The announcement has been sent.
@ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p>
}
style_finish_page();
return;
} else if( !alert_enabled() ){
style_header("Cannot Send Announcement");
@ <p>Either you have no subscribers yet, or email alerts are not yet
@ <a href="https://fossil-scm.org/fossil/doc/trunk/www/alerts.md">set up</a>
@ for this repository.</p>
return;
}
style_header("Send Announcement");
alert_submenu_common();
if( fossil_strcmp(zName,"test2")==0 ){
zAction = "announce/test2";
}else if( fossil_strcmp(zName,"test3")==0 ){
zAction = "announce/test3";
}
@ <form method="POST" action="%R/%s(zAction)">
login_insert_csrf_secret();
@ <table class="subscribe">
if( g.perm.Admin ){
int aa = PB("aa");
int all = PB("all");
int aMod = PB("mods");
|
| ︙ | ︙ | |||
3582 3583 3584 3585 3586 3587 3588 |
@ <tr>
@ <td class="form_label">Message:</td>
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
@ %h(PT("msg"))</textarea>
@ </tr>
@ <tr>
@ <td></td>
| | > > > > > > > > > > > > > | 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 |
@ <tr>
@ <td class="form_label">Message:</td>
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
@ %h(PT("msg"))</textarea>
@ </tr>
@ <tr>
@ <td></td>
if( fossil_strcmp(zName,"test2")==0 ){
@ <td><input type="submit" name="submit" value="Dry Run">
}else{
@ <td><input type="submit" name="submit" value="Send Message">
}
@ </tr>
@ </table>
@ </form>
if( g.perm.Setup ){
@ <hr>
@ <p>Trouble-shooting Options:</p>
@ <ol>
@ <li> <a href="%R/announce">Normal Processing</a>
@ <li> Only <a href="%R/announce/test1">show POST parameters</a>
@ - Do not send the announcement.
@ <li> <a href="%R/announce/test2">Show the email text</a> but do
@ not actually send it.
@ <li> Send the message and also <a href="%R/announce/test3">show the
@ SMTP traffic</a> when using "relay" mode.
@ </ol>
}
style_finish_page();
}
|
Changes to src/backoffice.c.
| ︙ | ︙ | |||
36 37 38 39 40 41 42 | ** Backoffice processes should die off after doing whatever work they need ** to do. In this way, we avoid having lots of idle processes in the ** process table, doing nothing on rarely accessed repositories, and ** if the Fossil binary is updated on a system, the backoffice processes ** will restart using the new binary automatically. ** ** At any point in time there should be at most two backoffice processes. | | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | ** Backoffice processes should die off after doing whatever work they need ** to do. In this way, we avoid having lots of idle processes in the ** process table, doing nothing on rarely accessed repositories, and ** if the Fossil binary is updated on a system, the backoffice processes ** will restart using the new binary automatically. ** ** At any point in time there should be at most two backoffice processes. ** There is a main process that is doing the actual work, and there is ** a second stand-by process that is waiting for the main process to finish ** and that will become the main process after a delay. ** ** After any successful web page reply, the backoffice_check_if_needed() ** routine is called. That routine checks to see if both one or both of ** the backoffice processes are already running. That routine remembers the ** status in a global variable. ** ** Later, after the repository database is closed, the ** backoffice_run_if_needed() routine is called. If the prior call ** to backoffice_check_if_needed() indicated that backoffice processing ** might be required, the run_if_needed() attempts to kick off a backoffice ** process. ** ** All work performed by the backoffice is in the backoffice_work() ** routine. */ #if defined(_WIN32) # if defined(_WIN32_WINNT) # undef _WIN32_WINNT # endif # define _WIN32_WINNT 0x501 |
| ︙ | ︙ | |||
483 484 485 486 487 488 489 | sqlite3_uint64 idSelf; int lastWarning = 0; int warningDelay = 30; static int once = 0; if( sqlite3_db_readonly(g.db, 0) ) return; if( db_is_protected(PROTECT_READONLY) ) return; | | | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
sqlite3_uint64 idSelf;
int lastWarning = 0;
int warningDelay = 30;
static int once = 0;
if( sqlite3_db_readonly(g.db, 0) ) return;
if( db_is_protected(PROTECT_READONLY) ) return;
g.zPhase = "backoffice-pending";
backoffice_error_check_one(&once);
idSelf = backofficeProcessId();
while(1){
tmNow = time(0);
db_begin_write();
backofficeReadLease(&x);
if( x.tmNext>=tmNow
|
| ︙ | ︙ | |||
508 509 510 511 512 513 514 515 516 517 518 519 520 521 |
}
if( x.tmCurrent<tmNow && backofficeProcessDone(x.idCurrent) ){
/* This process can start doing backoffice work immediately */
x.idCurrent = idSelf;
x.tmCurrent = tmNow + BKOFCE_LEASE_TIME;
x.idNext = 0;
x.tmNext = 0;
backofficeWriteLease(&x);
db_end_transaction(0);
backofficeTrace("/***** Begin Backoffice Processing %d *****/\n",
GETPID());
backoffice_work();
break;
}
| > | 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
}
if( x.tmCurrent<tmNow && backofficeProcessDone(x.idCurrent) ){
/* This process can start doing backoffice work immediately */
x.idCurrent = idSelf;
x.tmCurrent = tmNow + BKOFCE_LEASE_TIME;
x.idNext = 0;
x.tmNext = 0;
g.zPhase = "backoffice-work";
backofficeWriteLease(&x);
db_end_transaction(0);
backofficeTrace("/***** Begin Backoffice Processing %d *****/\n",
GETPID());
backoffice_work();
break;
}
|
| ︙ | ︙ | |||
541 542 543 544 545 546 547 |
/* The sleep was interrupted by a signal from another thread. */
backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
db_end_transaction(0);
break;
}
}else{
if( (sqlite3_uint64)(lastWarning+warningDelay) < tmNow ){
| > > | | > | 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
/* The sleep was interrupted by a signal from another thread. */
backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
db_end_transaction(0);
break;
}
}else{
if( (sqlite3_uint64)(lastWarning+warningDelay) < tmNow ){
sqlite3_int64 runningFor = BKOFCE_LEASE_TIME + tmNow - x.tmCurrent;
if( warningDelay>=240 && runningFor<1800 ){
fossil_warning(
"backoffice process %lld still running after %d seconds",
x.idCurrent, runningFor);
}
lastWarning = tmNow;
warningDelay *= 2;
}
if( backofficeSleep(1000) ){
/* The sleep was interrupted by a signal from another thread. */
backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
db_end_transaction(0);
|
| ︙ | ︙ | |||
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 |
}
blob_init(&log, 0, 0);
backofficeBlob = &log;
blob_appendf(&log, "%s %s", db_text(0, "SELECT datetime('now')"), zName);
}
/* Here is where the actual work of the backoffice happens */
nThis = alert_backoffice(0);
if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
nThis = hook_backoffice();
if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
/* Close the log */
if( backofficeFILE ){
if( nTotal || backofficeLogDetail ){
if( nTotal==0 ) backoffice_log("no-op");
#if !defined(_WIN32)
gettimeofday(&sEnd,0);
| > > > | 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 |
}
blob_init(&log, 0, 0);
backofficeBlob = &log;
blob_appendf(&log, "%s %s", db_text(0, "SELECT datetime('now')"), zName);
}
/* Here is where the actual work of the backoffice happens */
g.zPhase = "backoffice-alerts";
nThis = alert_backoffice(0);
if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
g.zPhase = "backoffice-hooks";
nThis = hook_backoffice();
if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
g.zPhase = "backoffice-close";
/* Close the log */
if( backofficeFILE ){
if( nTotal || backofficeLogDetail ){
if( nTotal==0 ) backoffice_log("no-op");
#if !defined(_WIN32)
gettimeofday(&sEnd,0);
|
| ︙ | ︙ |
Changes to src/branch.c.
| ︙ | ︙ | |||
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 | ** but no GLOB argument. All other options are supported, with -t being ** an implied no-op. ** ** > fossil branch new BRANCH-NAME BASIS ?OPTIONS? ** ** Create a new branch BRANCH-NAME off of check-in BASIS. ** ** Options: ** --private Branch is private (i.e., remains local) ** --bgcolor COLOR Use COLOR instead of automatic background ** --nosign Do not sign the manifest for the check-in ** that creates this branch ** --nosync Do not auto-sync prior to creating the branch ** --date-override DATE DATE to use instead of 'now' ** --user-override USER USER to use instead of the current default ** | > > > > > > < < < < < < | 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
** but no GLOB argument. All other options are supported, with -t being
** an implied no-op.
**
** > fossil branch new BRANCH-NAME BASIS ?OPTIONS?
**
** Create a new branch BRANCH-NAME off of check-in BASIS.
**
** This command is available for people who want to create a branch
** in advance. But the use of this command is discouraged. The
** preferred idiom in Fossil is to create new branches at the point
** of need, using the "--branch NAME" option to the "fossil commit"
** command.
**
** Options:
** --private Branch is private (i.e., remains local)
** --bgcolor COLOR Use COLOR instead of automatic background
** --nosign Do not sign the manifest for the check-in
** that creates this branch
** --nosync Do not auto-sync prior to creating the branch
** --date-override DATE DATE to use instead of 'now'
** --user-override USER USER to use instead of the current default
**
** Options:
** -R|--repository REPO Run commands on repository REPO
*/
void branch_cmd(void){
int n;
const char *zCmd = "list";
db_find_and_open_repository(0, 0);
|
| ︙ | ︙ | |||
868 869 870 871 872 873 874 |
const char *zMergeTo = db_column_text(&q, 3);
int nCkin = db_column_int(&q, 4);
const char *zLastCkin = db_column_text(&q, 5);
const char *zBgClr = db_column_text(&q, 6);
char *zAge = human_readable_age(rNow - rMtime);
sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
| > | | 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 |
const char *zMergeTo = db_column_text(&q, 3);
int nCkin = db_column_int(&q, 4);
const char *zLastCkin = db_column_text(&q, 5);
const char *zBgClr = db_column_text(&q, 6);
char *zAge = human_readable_age(rNow - rMtime);
sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0);
if( zBgClr==0 ){
if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){
zBgClr = 0;
}else{
zBgClr = hash_color(zBranch);
}
}
if( zBgClr && zBgClr[0] && show_colors ){
|
| ︙ | ︙ |
Changes to src/browse.c.
| ︙ | ︙ | |||
203 204 205 206 207 208 209 |
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
linkTrunk = trunkRid && rid != trunkRid;
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
if( bDocDir ) zCI = mprintf("%S", zUuid);
| | | 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
linkTrunk = trunkRid && rid != trunkRid;
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
if( bDocDir ) zCI = mprintf("%S", zUuid);
Th_StoreUnsafe("current_checkin", zCI);
}else{
zCI = 0;
}
}
assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
if( zD==0 ){
|
| ︙ | ︙ | |||
769 770 771 772 773 774 775 |
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
" FROM event WHERE objid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
| | | 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 |
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
" FROM event WHERE objid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
Th_StoreUnsafe("current_checkin", zCI);
}else{
zCI = 0;
}
}
if( zCI==0 ){
rNow = db_double(0.0, "SELECT max(mtime) FROM event");
zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
|
| ︙ | ︙ |
Changes to src/cache.c.
| ︙ | ︙ | |||
397 398 399 400 401 402 403 |
/*
** WEBPAGE: cachestat
**
** Show information about the webpage cache. Requires Setup privilege.
*/
void cache_page(void){
| | > > > > > > | | < < | > > < > > > > | | | | | > | | > > | < | | > > > > > > | > > > | > | | > > | | > > > | > | | < | 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
/*
** WEBPAGE: cachestat
**
** Show information about the webpage cache. Requires Setup privilege.
*/
void cache_page(void){
sqlite3 *db = 0;
sqlite3_stmt *pStmt;
int doInit;
char *zDbName = cacheName();
int nEntry = 0;
int mxEntry = 0;
char zBuf[100];
login_check_credentials();
if( !g.perm.Setup ){ login_needed(0); return; }
style_set_current_feature("cache");
style_header("Web Cache Status");
style_submenu_element("Refresh","%R/cachestat");
doInit = P("init")!=0 && cgi_csrf_safe(2);
db = cacheOpen(doInit);
if( db!=0 ){
if( P("clear")!=0 && cgi_csrf_safe(2) ){
sqlite3_exec(db, "DELETE FROM cache; DELETE FROM blob; VACUUM;",0,0,0);
}
cache_register_sizename(db);
pStmt = cacheStmt(db,
"SELECT key, sz, nRef, datetime(tm,'unixepoch')"
" FROM cache"
" ORDER BY (tm + 3600*min(nRef,48)) DESC"
);
if( pStmt ){
while( sqlite3_step(pStmt)==SQLITE_ROW ){
const unsigned char *zName = sqlite3_column_text(pStmt,0);
char *zHash = cache_hash_of_key((const char*)zName);
if( nEntry==0 ){
@ <h2>Current Cache Entries:</h2>
@ <ol>
}
@ <li><p>%z(href("%R/cacheget?key=%T",zName))%h(zName)</a><br>
@ size: %,lld(sqlite3_column_int64(pStmt,1)),
@ hit-count: %d(sqlite3_column_int(pStmt,2)),
@ last-access: %s(sqlite3_column_text(pStmt,3))Z \
if( zHash ){
@ → %z(href("%R/timeline?c=%S",zHash))checkin info</a>\
fossil_free(zHash);
}
@ </p></li>
nEntry++;
}
sqlite3_finalize(pStmt);
if( nEntry ){
@ </ol>
}
}
}
@ <h2>About The Web-Cache</h2>
@ <p>
@ The web-cache is a separate database file that holds cached copies
@ tarballs, ZIP archives, and other pages that are expensive to compute
@ and are likely to be reused.
@ <form method="post">
login_insert_csrf_secret();
@ <ul>
if( db==0 ){
@ <li> Web-cache is currently disabled.
@ <input type="submit" name="init" value="Enable">
}else{
bigSizeName(sizeof(zBuf), zBuf, file_size(zDbName, ExtFILE));
mxEntry = db_get_int("max-cache-entry",10);
@ <li> Filename of the cache database: <b>%h(zDbName)</b>
@ <li> Size of the cache database: %s(zBuf)
@ <li> Maximum number of entries: %d(mxEntry)
@ <li> Number of cache entries used: %d(nEntry)
@ <li> Change the max-cache-entry setting on the
@ <a href="%R/setup_settings">Settings</a> page to adjust the
@ maximum number of entries in the cache.
@ <li><input type="submit" name="clear" value="Clear the cache">
@ <li> Disable the cache by manually deleting the cache database file.
}
@ </ul>
@ </form>
fossil_free(zDbName);
if( db ) sqlite3_close(db);
style_finish_page();
}
/*
** WEBPAGE: cacheget
**
** Usage: /cacheget?key=KEY
|
| ︙ | ︙ |
Changes to src/cgi.c.
| ︙ | ︙ | |||
70 71 72 73 74 75 76 77 78 79 80 81 82 83 | # endif # include <winsock2.h> # include <ws2tcpip.h> #else # include <sys/socket.h> # include <sys/un.h> # include <netinet/in.h> # include <arpa/inet.h> # include <sys/times.h> # include <sys/time.h> # include <sys/wait.h> # include <sys/select.h> # include <errno.h> #endif | > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | # endif # include <winsock2.h> # include <ws2tcpip.h> #else # include <sys/socket.h> # include <sys/un.h> # include <netinet/in.h> # include <netdb.h> # include <arpa/inet.h> # include <sys/times.h> # include <sys/time.h> # include <sys/wait.h> # include <sys/select.h> # include <errno.h> #endif |
| ︙ | ︙ | |||
101 102 103 104 105 106 107 | #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) | | | | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) #define P_NoBot(x) cgi_parameter_no_attack((x),0) #define PD_NoBot(x,y) cgi_parameter_no_attack((x),(y)) /* ** Shortcut for the cgi_printf() routine. Instead of using the ** ** @ ... ** ** notation provided by the translate.c utility, you can also |
| ︙ | ︙ | |||
635 636 637 638 639 640 641 642 643 644 645 646 647 648 |
cgi_reset_content();
cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation);
cgi_set_status(iStat, zStat);
free(zLocation);
cgi_reply();
fossil_exit(0);
}
NORETURN void cgi_redirect(const char *zURL){
cgi_redirect_with_status(zURL, 302, "Moved Temporarily");
}
NORETURN void cgi_redirect_with_method(const char *zURL){
cgi_redirect_with_status(zURL, 307, "Temporary Redirect");
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
| > > > | 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 |
cgi_reset_content();
cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation);
cgi_set_status(iStat, zStat);
free(zLocation);
cgi_reply();
fossil_exit(0);
}
NORETURN void cgi_redirect_perm(const char *zURL){
cgi_redirect_with_status(zURL, 301, "Moved Permanently");
}
NORETURN void cgi_redirect(const char *zURL){
cgi_redirect_with_status(zURL, 302, "Moved Temporarily");
}
NORETURN void cgi_redirect_with_method(const char *zURL){
cgi_redirect_with_status(zURL, 307, "Temporary Redirect");
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
|
| ︙ | ︙ | |||
1618 1619 1620 1621 1622 1623 1624 |
cgi_set_status(418,"I'm a teapot");
cgi_reply();
fossil_errorlog("Xpossible hack attempt - 418 response on \"%s\"", zName);
exit(0);
}
/*
| | | | | | | | > > | | | 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 |
cgi_set_status(418,"I'm a teapot");
cgi_reply();
fossil_errorlog("Xpossible hack attempt - 418 response on \"%s\"", zName);
exit(0);
}
/*
** If looks_like_attack() returns true for the given string, call
** cgi_begone_spider() and does not return, else this function has no
** side effects. The range of checks performed by this function may
** be extended in the future.
**
** Checks are omitted for any logged-in user.
**
** This is the primary defense against attack. Fossil should easily be
** proof against SQL injection and XSS attacks even without without this
** routine. Rather, this is an attempt to avoid denial-of-service caused
** by persistent spiders that hammer the server with dozens or hundreds of
** probes per seconds as they look for vulnerabilities. In other
** words, this is an effort to reduce the CPU load imposed by malicious
** spiders. Though those routine might help make attacks harder, it is
** not itself an impenetrably barrier against attack and should not be
** relied upon as the only defense.
*/
void cgi_value_spider_check(const char *zTxt, const char *zName){
if( g.zLogin==0 && looks_like_attack(zTxt) ){
cgi_begone_spider(zName);
}
}
/*
** A variant of cgi_parameter() with the same semantics except that if
** cgi_parameter(zName,zDefault) returns a value other than zDefault
** then it passes that value to cgi_value_spider_check().
*/
const char *cgi_parameter_no_attack(const char *zName, const char *zDefault){
const char *zTxt = cgi_parameter(zName, zDefault);
if( zTxt!=zDefault ){
cgi_value_spider_check(zTxt, zName);
}
return zTxt;
}
|
| ︙ | ︙ | |||
2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 |
zInput++;
while( fossil_isspace(*zInput) ){ zInput++; }
}
if( zLeftOver ){ *zLeftOver = zInput; }
return zResult;
}
/*
** Determine the IP address on the other side of a connection.
** Return a pointer to a string. Or return 0 if unable.
**
** The string is held in a static buffer that is overwritten on
** each call.
*/
char *cgi_remote_ip(int fd){
| > > > > > > > > > > > | > | < < | | > | > | < < < < < < | 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 |
zInput++;
while( fossil_isspace(*zInput) ){ zInput++; }
}
if( zLeftOver ){ *zLeftOver = zInput; }
return zResult;
}
/*
** All possible forms of an IP address. Needed to work around GCC strict
** aliasing rules.
*/
typedef union {
struct sockaddr sa; /* Abstract superclass */
struct sockaddr_in sa4; /* IPv4 */
struct sockaddr_in6 sa6; /* IPv6 */
struct sockaddr_storage sas; /* Should be the maximum of the above 3 */
} address;
/*
** Determine the IP address on the other side of a connection.
** Return a pointer to a string. Or return 0 if unable.
**
** The string is held in a static buffer that is overwritten on
** each call.
*/
char *cgi_remote_ip(int fd){
address remoteAddr;
socklen_t size = sizeof(remoteAddr);
static char zHost[NI_MAXHOST];
if( getpeername(0, &remoteAddr.sa, &size) ){
return 0;
}
if( getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
NI_NUMERICHOST) ){
return 0;
}
return zHost;
}
/*
** This routine handles a single HTTP request which is coming in on
** g.httpIn and which replies on g.httpOut
**
** The HTTP request is read from g.httpIn and is used to initialize
|
| ︙ | ︙ | |||
2484 2485 2486 2487 2488 2489 2490 |
nHdr -= m+1;
}
fossil_free(zToFree);
fgetc(g.httpIn); /* Read past the "," separating header from content */
cgi_init();
}
| < | 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 |
nHdr -= m+1;
}
fossil_free(zToFree);
fgetc(g.httpIn); /* Read past the "," separating header from content */
cgi_init();
}
#if INTERFACE
/*
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */
#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */
|
| ︙ | ︙ | |||
2527 2528 2529 2530 2531 2532 2533 |
const char *zIpAddr, /* Bind to this IP address, if not null */
int flags /* HTTP_SERVER_* flags */
){
#if defined(_WIN32)
/* Use win32_http_server() instead */
fossil_exit(1);
#else
| > > | | > | > | > > > > > > > > | | > > > | | | | | | | | | | | | | | | | | | | | | > > > | | | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | > > > | > > > | > > > > > > > > > | | > > > > > | > > > > | > > > > > > > > > > > > > > > > | > > | > > > > > > > > > > > > > > > > > | > > > > | < < < > | > > > | > > > > > > > > | > | > > > > > > > > > > > > | > > > > > | | > > > | < < < > | > > > > | > | > > > > > > | | > | > > > > > > > > > > | | > | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | > > | | | < | 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 |
const char *zIpAddr, /* Bind to this IP address, if not null */
int flags /* HTTP_SERVER_* flags */
){
#if defined(_WIN32)
/* Use win32_http_server() instead */
fossil_exit(1);
#else
int listen4 = -1; /* Main socket; IPv4 or unix-domain */
int listen6 = -1; /* Aux socket for corresponding IPv6 */
int mxListen = -1; /* Maximum of listen4 and listen6 */
int connection; /* An incoming connection */
int nRequest = 0; /* Number of requests handled so far */
fd_set readfds; /* Set of file descriptors for select() */
socklen_t lenaddr; /* Length of the inaddr structure */
int child; /* PID of the child process */
int nchildren = 0; /* Number of child processes */
struct timeval delay; /* How long to wait inside select() */
struct sockaddr_in6 inaddr6; /* Address for IPv6 */
struct sockaddr_in inaddr4; /* Address for IPv4 */
struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
int opt = 1; /* setsockopt flag */
int rc; /* Result code from system calls */
int iPort = mnPort; /* Port to try to use */
const char *zRequestType; /* Type of requests to listen for */
if( flags & HTTP_SERVER_SCGI ){
zRequestType = "SCGI";
}else if( g.httpUseSSL ){
zRequestType = "TLS-encrypted HTTPS";
}else{
zRequestType = "HTTP";
}
if( flags & HTTP_SERVER_UNIXSOCKET ){
/* CASE 1: A unix socket named g.zSockName. After creation, set the
** permissions on the new socket to g.zSockMode and make the
** owner of the socket be g.zSockOwner.
*/
assert( g.zSockName!=0 );
memset(&uxaddr, 0, sizeof(uxaddr));
if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
g.zSockName, (int)sizeof(uxaddr.sun_path));
}
if( file_isdir(g.zSockName, ExtFILE)!=0 ){
if( !file_issocket(g.zSockName) ){
fossil_fatal("cannot name socket \"%s\" because another object"
" with that name already exists", g.zSockName);
}else{
unlink(g.zSockName);
}
}
uxaddr.sun_family = AF_UNIX;
strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
if( listen4<0 ){
fossil_fatal("unable to create a unix socket named %s",
g.zSockName);
}
mxListen = listen4;
listen6 = -1;
/* Set the access permission for the new socket. Default to 0660.
** But use an alternative specified by --socket-mode if available.
** Do this before bind() to avoid a race condition. */
if( g.zSockMode ){
file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
}else{
file_set_mode(g.zSockName, listen4, "0660", 1);
}
rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
/* Set the owner of the socket if requested by --socket-owner. This
** must wait until after bind(), after the filesystem object has been
** created. See https://lkml.org/lkml/2004/11/1/84 and
** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
if( g.zSockOwner ){
file_set_owner(g.zSockName, listen4, g.zSockOwner);
}
fossil_print("Listening for %s requests on unix socket %s\n",
zRequestType, g.zSockName);
fflush(stdout);
}else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
/* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
*/
assert( mnPort==mxPort );
memset(&inaddr6, 0, sizeof(inaddr6));
inaddr6.sin6_family = AF_INET6;
inaddr6.sin6_port = htons(iPort);
if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
}
listen6 = socket(AF_INET6, SOCK_STREAM, 0);
if( listen6>0 ){
opt = 1;
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
if( rc<0 ){
close(listen6);
listen6 = -1;
}
}
if( listen6<0 ){
fossil_fatal("cannot open a listening socket on [%s]:%d",
zIpAddr, mnPort);
}
mxListen = listen6;
listen4 = -1;
fossil_print("Listening for %s requests on [%s]:%d\n",
zRequestType, zIpAddr, iPort);
fflush(stdout);
}else if( zIpAddr && zIpAddr[0] ){
/* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
*/
assert( mnPort==mxPort );
memset(&inaddr4, 0, sizeof(inaddr4));
inaddr4.sin_family = AF_INET;
inaddr4.sin_port = htons(iPort);
if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
}
listen4 = socket(AF_INET, SOCK_STREAM, 0);
if( listen4>0 ){
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
if( rc<0 ){
close(listen6);
listen4 = -1;
}
}
if( listen4<0 ){
fossil_fatal("cannot open a listening socket on %s:%d",
zIpAddr, mnPort);
}
mxListen = listen4;
listen6 = -1;
fossil_print("Listening for %s requests on TCP port %s:%d\n",
zRequestType, zIpAddr, iPort);
fflush(stdout);
}else{
/* CASE 4: Listen on all available IP addresses, or on only loopback
** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
** first available in the range of mnPort..mxPort. Listen
** on both IPv4 and IPv6, if possible. The TCP port scan is done
** on IPv4.
*/
while( iPort<=mxPort ){
const char *zProto;
memset(&inaddr4, 0, sizeof(inaddr4));
inaddr4.sin_family = AF_INET;
inaddr4.sin_port = htons(iPort);
if( flags & HTTP_SERVER_LOCALHOST ){
inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
}else{
inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
}
listen4 = socket(AF_INET, SOCK_STREAM, 0);
if( listen4>0 ){
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
if( rc<0 ){
close(listen4);
listen4 = -1;
}
}
if( listen4<0 ){
iPort++;
continue;
}
mxListen = listen4;
/* If we get here, that means we found an open TCP port at iPort for
** IPv4. Try to set up a corresponding IPv6 socket on the same port.
*/
memset(&inaddr6, 0, sizeof(inaddr6));
inaddr6.sin6_family = AF_INET6;
inaddr6.sin6_port = htons(iPort);
if( flags & HTTP_SERVER_LOCALHOST ){
inaddr6.sin6_addr = in6addr_loopback;
}else{
inaddr6.sin6_addr = in6addr_any;
}
listen6 = socket(AF_INET6, SOCK_STREAM, 0);
if( listen6>0 ){
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
if( rc<0 ){
close(listen6);
listen6 = -1;
}
}
if( listen6<0 ){
zProto = "IPv4 only";
}else{
zProto = "IPv4 and IPv6";
if( listen6>listen4 ) mxListen = listen6;
}
fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
zRequestType,
(flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
iPort, zProto);
fflush(stdout);
break;
}
if( iPort>mxPort ){
fossil_fatal("no available TCP ports in the range %d..%d",
mnPort, mxPort);
}
}
/* If we get to this point, that means there is at least one listening
** socket on either listen4 or listen6 and perhaps on both. */
assert( listen4>0 || listen6>0 );
if( listen4>0 ) listen(listen4,10);
if( listen6>0 ) listen(listen6,10);
if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
assert( strstr(zBrowser,"%d")!=0 );
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
/* On Cygwin, we can do better than "echo" */
if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
fossil_warning("cannot start browser\n");
}
}else
#endif
if( fossil_system(zBrowser)<0 ){
fossil_warning("cannot start browser: %s\n", zBrowser);
}
}
/* What for incomming requests. For each request, fork() a child process
** to deal with that request. The child process returns. The parent
** keeps on listening and never returns.
*/
while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
if( wait(0)>=0 ) nchildren--;
}
#endif
delay.tv_sec = 0;
delay.tv_usec = 100000;
FD_ZERO(&readfds);
assert( listen4>0 || listen6>0 );
if( listen4>0 ) FD_SET( listen4, &readfds);
if( listen6>0 ) FD_SET( listen6, &readfds);
select( mxListen+1, &readfds, 0, 0, &delay);
if( listen4>0 && FD_ISSET(listen4, &readfds) ){
lenaddr = sizeof(inaddr4);
connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
}else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
lenaddr = sizeof(inaddr6);
connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
}else{
connection = -1;
}
if( connection>=0 ){
if( flags & HTTP_SERVER_NOFORK ){
child = 0;
}else{
child = fork();
}
if( child!=0 ){
if( child>0 ){
nchildren++;
nRequest++;
}
close(connection);
}else{
int nErr = 0, fd;
g.zSockName = 0 /* avoid deleting the socket via atexit() */;
close(0);
fd = dup(connection);
if( fd!=0 ) nErr++;
close(1);
fd = dup(connection);
if( fd!=1 ) nErr++;
if( 0 && !g.fAnyTrace ){
close(2);
fd = dup(connection);
if( fd!=2 ) nErr++;
}
close(connection);
if( listen4>0 ) close(listen4);
if( listen6>0 ) close(listen6);
g.nPendingRequest = nchildren+1;
g.nRequest = nRequest+1;
return nErr;
}
}
/* Bury dead children */
if( nchildren ){
while(1){
int iStatus = 0;
pid_t x = waitpid(-1, &iStatus, WNOHANG);
|
| ︙ | ︙ |
Changes to src/chat.c.
| ︙ | ︙ | |||
252 253 254 255 256 257 258 |
@ window.addEventListener('load', function(){
@ document.body.classList.add('chat');
@ /*^^^for skins which add their own BODY tag */;
@ window.fossil.config.chat = {
@ fromcli: %h(PB("cli")?"true":"false"),
@ alertSound: "%h(zAlert)",
@ initSize: %d(db_get_int("chat-initial-history",50)),
| | > | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
@ window.addEventListener('load', function(){
@ document.body.classList.add('chat');
@ /*^^^for skins which add their own BODY tag */;
@ window.fossil.config.chat = {
@ fromcli: %h(PB("cli")?"true":"false"),
@ alertSound: "%h(zAlert)",
@ initSize: %d(db_get_int("chat-initial-history",50)),
@ imagesInline: !!%d(db_get_boolean("chat-inline-images",1)),
@ pollTimeout: %d(db_get_int("chat-poll-timeout",420))
@ };
ajax_emit_js_preview_modes(0);
chat_emit_alert_list();
@ }, false);
@ </script>
builtin_request_js("fossil.page.chat.js");
style_finish_page();
|
| ︙ | ︙ |
Changes to src/checkin.c.
| ︙ | ︙ | |||
2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 | ** ** Options: ** --allow-conflict Allow unresolved merge conflicts ** --allow-empty Allow a commit with no changes ** --allow-fork Allow the commit to fork ** --allow-older Allow a commit older than its ancestor ** --baseline Use a baseline manifest in the commit process ** --branch NEW-BRANCH-NAME Check in to this new branch ** --close Close the branch being committed ** --date-override DATETIME Make DATETIME the time of the check-in. ** Useful when importing historical check-ins ** from another version control system. ** --delta Use a delta manifest in the commit process ** --hash Verify file status using hashing rather ** than relying on filesystem mtimes ** --if-changes Make this command a silent no-op if there ** are no changes ** --ignore-clock-skew If a clock skew is detected, ignore it and ** behave as if the user had entered 'yes' to ** the question of whether to proceed despite | > > > | 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 | ** ** Options: ** --allow-conflict Allow unresolved merge conflicts ** --allow-empty Allow a commit with no changes ** --allow-fork Allow the commit to fork ** --allow-older Allow a commit older than its ancestor ** --baseline Use a baseline manifest in the commit process ** --bgcolor COLOR Apply COLOR to this one check-in only ** --branch NEW-BRANCH-NAME Check in to this new branch ** --branchcolor COLOR Apply given COLOR to the branch ** --close Close the branch being committed ** --date-override DATETIME Make DATETIME the time of the check-in. ** Useful when importing historical check-ins ** from another version control system. ** --delta Use a delta manifest in the commit process ** --editor NAME Text editor to use for check-in comment. ** --hash Verify file status using hashing rather ** than relying on filesystem mtimes ** --if-changes Make this command a silent no-op if there ** are no changes ** --ignore-clock-skew If a clock skew is detected, ignore it and ** behave as if the user had entered 'yes' to ** the question of whether to proceed despite |
| ︙ | ︙ | |||
2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 |
noSign = db_get_boolean("omitsign", 0)|noSign;
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
useCksum = db_get_boolean("repo-cksum", 1);
bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
outputManifest = db_get_manifest_setting(0);
mxSize = db_large_file_size();
if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
verify_all_options();
/* The --no-warnings flag and the --force flag each imply
** the --no-verify-comment flag */
if( noWarningFlag || forceFlag ){
noVerifyCom = 1;
}
| > | 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 |
noSign = db_get_boolean("omitsign", 0)|noSign;
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
useCksum = db_get_boolean("repo-cksum", 1);
bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
outputManifest = db_get_manifest_setting(0);
mxSize = db_large_file_size();
if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
(void)fossil_text_editor();
verify_all_options();
/* The --no-warnings flag and the --force flag each imply
** the --no-verify-comment flag */
if( noWarningFlag || forceFlag ){
noVerifyCom = 1;
}
|
| ︙ | ︙ |
Changes to src/color.c.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
** This file contains code used to select colors based on branch and
** user names.
**
*/
#include "config.h"
#include <string.h>
#include "color.h"
/*
** Compute a hash on a branch or user name
*/
static unsigned int hash_of_name(const char *z){
unsigned int h = 0;
int i;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
** This file contains code used to select colors based on branch and
** user names.
**
*/
#include "config.h"
#include <string.h>
#include "color.h"
/*
** 140 standard CSS color names and their corresponding RGB values,
** in alphabetical order by name so that we can do a binary search
** for lookup.
*/
static const struct CssColors {
const char *zName; /* CSS Color name, lower case */
unsigned int iRGB; /* Corresponding RGB value */
} aCssColors[] = {
{ "aliceblue", 0xf0f8ff },
{ "antiquewhite", 0xfaebd7 },
{ "aqua", 0x00ffff },
{ "aquamarine", 0x7fffd4 },
{ "azure", 0xf0ffff },
{ "beige", 0xf5f5dc },
{ "bisque", 0xffe4c4 },
{ "black", 0x000000 },
{ "blanchedalmond", 0xffebcd },
{ "blue", 0x0000ff },
{ "blueviolet", 0x8a2be2 },
{ "brown", 0xa52a2a },
{ "burlywood", 0xdeb887 },
{ "cadetblue", 0x5f9ea0 },
{ "chartreuse", 0x7fff00 },
{ "chocolate", 0xd2691e },
{ "coral", 0xff7f50 },
{ "cornflowerblue", 0x6495ed },
{ "cornsilk", 0xfff8dc },
{ "crimson", 0xdc143c },
{ "cyan", 0x00ffff },
{ "darkblue", 0x00008b },
{ "darkcyan", 0x008b8b },
{ "darkgoldenrod", 0xb8860b },
{ "darkgray", 0xa9a9a9 },
{ "darkgreen", 0x006400 },
{ "darkkhaki", 0xbdb76b },
{ "darkmagenta", 0x8b008b },
{ "darkolivegreen", 0x556b2f },
{ "darkorange", 0xff8c00 },
{ "darkorchid", 0x9932cc },
{ "darkred", 0x8b0000 },
{ "darksalmon", 0xe9967a },
{ "darkseagreen", 0x8fbc8f },
{ "darkslateblue", 0x483d8b },
{ "darkslategray", 0x2f4f4f },
{ "darkturquoise", 0x00ced1 },
{ "darkviolet", 0x9400d3 },
{ "deeppink", 0xff1493 },
{ "deepskyblue", 0x00bfff },
{ "dimgray", 0x696969 },
{ "dodgerblue", 0x1e90ff },
{ "firebrick", 0xb22222 },
{ "floralwhite", 0xfffaf0 },
{ "forestgreen", 0x228b22 },
{ "fuchsia", 0xff00ff },
{ "gainsboro", 0xdcdcdc },
{ "ghostwhite", 0xf8f8ff },
{ "gold", 0xffd700 },
{ "goldenrod", 0xdaa520 },
{ "gray", 0x808080 },
{ "green", 0x008000 },
{ "greenyellow", 0xadff2f },
{ "honeydew", 0xf0fff0 },
{ "hotpink", 0xff69b4 },
{ "indianred", 0xcd5c5c },
{ "indigo", 0x4b0082 },
{ "ivory", 0xfffff0 },
{ "khaki", 0xf0e68c },
{ "lavender", 0xe6e6fa },
{ "lavenderblush", 0xfff0f5 },
{ "lawngreen", 0x7cfc00 },
{ "lemonchiffon", 0xfffacd },
{ "lightblue", 0xadd8e6 },
{ "lightcoral", 0xf08080 },
{ "lightcyan", 0xe0ffff },
{ "lightgoldenrodyellow", 0xfafad2 },
{ "lightgrey", 0xd3d3d3 },
{ "lightgreen", 0x90ee90 },
{ "lightpink", 0xffb6c1 },
{ "lightsalmon", 0xffa07a },
{ "lightseagreen", 0x20b2aa },
{ "lightskyblue", 0x87cefa },
{ "lightslategray", 0x778899 },
{ "lightsteelblue", 0xb0c4de },
{ "lightyellow", 0xffffe0 },
{ "lime", 0x00ff00 },
{ "limegreen", 0x32cd32 },
{ "linen", 0xfaf0e6 },
{ "magenta", 0xff00ff },
{ "maroon", 0x800000 },
{ "mediumaquamarine", 0x66cdaa },
{ "mediumblue", 0x0000cd },
{ "mediumorchid", 0xba55d3 },
{ "mediumpurple", 0x9370d8 },
{ "mediumseagreen", 0x3cb371 },
{ "mediumslateblue", 0x7b68ee },
{ "mediumspringgreen", 0x00fa9a },
{ "mediumturquoise", 0x48d1cc },
{ "mediumvioletred", 0xc71585 },
{ "midnightblue", 0x191970 },
{ "mintcream", 0xf5fffa },
{ "mistyrose", 0xffe4e1 },
{ "moccasin", 0xffe4b5 },
{ "navajowhite", 0xffdead },
{ "navy", 0x000080 },
{ "oldlace", 0xfdf5e6 },
{ "olive", 0x808000 },
{ "olivedrab", 0x6b8e23 },
{ "orange", 0xffa500 },
{ "orangered", 0xff4500 },
{ "orchid", 0xda70d6 },
{ "palegoldenrod", 0xeee8aa },
{ "palegreen", 0x98fb98 },
{ "paleturquoise", 0xafeeee },
{ "palevioletred", 0xd87093 },
{ "papayawhip", 0xffefd5 },
{ "peachpuff", 0xffdab9 },
{ "peru", 0xcd853f },
{ "pink", 0xffc0cb },
{ "plum", 0xdda0dd },
{ "powderblue", 0xb0e0e6 },
{ "purple", 0x800080 },
{ "red", 0xff0000 },
{ "rosybrown", 0xbc8f8f },
{ "royalblue", 0x4169e1 },
{ "saddlebrown", 0x8b4513 },
{ "salmon", 0xfa8072 },
{ "sandybrown", 0xf4a460 },
{ "seagreen", 0x2e8b57 },
{ "seashell", 0xfff5ee },
{ "sienna", 0xa0522d },
{ "silver", 0xc0c0c0 },
{ "skyblue", 0x87ceeb },
{ "slateblue", 0x6a5acd },
{ "slategray", 0x708090 },
{ "snow", 0xfffafa },
{ "springgreen", 0x00ff7f },
{ "steelblue", 0x4682b4 },
{ "tan", 0xd2b48c },
{ "teal", 0x008080 },
{ "thistle", 0xd8bfd8 },
{ "tomato", 0xff6347 },
{ "turquoise", 0x40e0d0 },
{ "violet", 0xee82ee },
{ "wheat", 0xf5deb3 },
{ "white", 0xffffff },
{ "whitesmoke", 0xf5f5f5 },
{ "yellow", 0xffff00 },
{ "yellowgreen", 0x9acd32 },
};
/*
** Attempt to translate a CSS color name into an integer that
** represents the equivalent RGB value. Ignore alpha if provided.
** If the name cannot be translated, return -1.
*/
int color_name_to_rgb(const char *zName){
if( zName==0 || zName[0]==0 ) return -1;
if( zName[0]=='#' ){
int i, v = 0;
for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){
v = v*16 + fossil_hexvalue(zName[i]);
}
if( i==4 ){
v = fossil_hexvalue(zName[1])*0x110000 +
fossil_hexvalue(zName[2])*0x1100 +
fossil_hexvalue(zName[3])*0x11;
return v;
}
if( i==7 ){
return v;
}
return -1;
}else{
int iMin = 0;
int iMax = count(aCssColors)-1;
while( iMin<=iMax ){
int iMid = (iMin+iMax)/2;
int c = sqlite3_stricmp(aCssColors[iMid].zName, zName);
if( c==0 ) return aCssColors[iMid].iRGB;
if( c<0 ){
iMin = iMid+1;
}else{
iMax = iMid-1;
}
}
return -1;
}
}
/*
** SETTING: raw-bgcolor boolean default=off
**
** Fossil usually tries to adjust user-specified background colors
** for checkins so that the text is readable and so that the color
** is not too garish. This setting disables that filter. When
** this setting is on, the user-selected background colors are shown
** exactly as requested.
*/
/*
** Shift a color provided by the user so that it is suitable
** for use as a background color in the current skin.
**
** The return value is a #HHHHHH color name contained in
** static space that is overwritten on the next call.
**
** If we cannot make sense of the background color recommendation
** that is the input, then return NULL.
**
** The iFgClr parameter is normally 0. But for testing purposes, set
** it to 1 for a black foregrounds and 2 for a white foreground.
*/
const char *reasonable_bg_color(const char *zRequested, int iFgClr){
int iRGB = color_name_to_rgb(zRequested);
int r, g, b; /* RGB components of requested color */
static int systemFg = 0; /* 1==black-foreground 2==white-foreground */
int fg; /* Foreground color to actually use */
static char zColor[10]; /* Return value */
if( iFgClr ){
fg = iFgClr;
}else if( systemFg==0 ){
if( db_get_boolean("raw-bgcolor",0) ){
fg = systemFg = 3;
}else{
fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
}
}else{
fg = systemFg;
}
if( fg>=3 ) return zRequested;
if( iRGB<0 ) return 0;
r = (iRGB>>16) & 0xff;
g = (iRGB>>8) & 0xff;
b = iRGB & 0xff;
if( fg==1 ){
/* Dark text on a light background. Adjust so that
** no color component is less than 255-K, resulting in
** a pastel background color. Color adjustment is quadratic
** so that colors that are further out of range have a greater
** adjustment. */
const int K = 79;
int k, x, m;
m = r<g ? r : g;
if( m>b ) m = b;
k = (m*m)/255 + K;
x = 255 - k;
r = (k*r)/255 + x;
g = (k*g)/255 + x;
b = (k*b)/255 + x;
}else{
/* Light text on a dark background. Adjust so that
** no color component is greater than K, resulting in
** a low-intensity, low-saturation background color.
** The color adjustment is quadratic so that colors that
** are further out of range have a greater adjustment. */
const int K = 112;
int k, m;
m = r>g ? r : g;
if( m<b ) m = b;
k = 255 - (255-K)*(m*m)/65025;
r = (k*r)/255;
g = (k*g)/255;
b = (k*b)/255;
}
sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
return zColor;
}
/*
** Compute a hash on a branch or user name
*/
static unsigned int hash_of_name(const char *z){
unsigned int h = 0;
int i;
|
| ︙ | ︙ | |||
183 184 185 186 187 188 189 |
@ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br>
}
@ <input type="submit" value="Submit">
@ <input type="submit" name="rand" value="Random">
@ </form>
style_finish_page();
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
@ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br>
}
@ <input type="submit" value="Submit">
@ <input type="submit" name="rand" value="Random">
@ </form>
style_finish_page();
}
/*
** WEBPAGE: test-bgcolor
**
** Show how user-specified background colors will be rendered
** using the reasonable_bg_color() algorithm.
*/
void test_bgcolor_page(void){
const char *zReq; /* Requested color name */
const char *zBG; /* Actual color provided */
const char *zBg1;
char zNm[10];
static const char *azDflt[] = {
"red", "orange", "yellow", "green", "blue", "indigo", "violet",
"tan", "brown", "gray",
};
const int N = count(azDflt);
int i, cnt, iClr, r, g, b;
char *zFg;
login_check_credentials();
style_set_current_feature("test");
style_header("Background Color Test");
for(i=cnt=0; i<N; i++){
sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
zReq = PD(zNm,azDflt[i]);
if( zReq==0 || zReq[0]==0 ) continue;
if( cnt==0 ){
@ <table border="1" cellspacing="0" cellpadding="10">
@ <tr>
@ <th>Requested Background
@ <th>Light mode
@ <th>Dark mode
@ </tr>
}
cnt++;
zBG = reasonable_bg_color(zReq, 0);
if( zBG==0 ){
@ <tr><td colspan="3" align="center">\
@ "%h(zReq)" is not a recognized color name</td></tr>
continue;
}
iClr = color_name_to_rgb(zReq);
r = (iClr>>16) & 0xff;
g = (iClr>>8) & 0xff;
b = iClr & 0xff;
if( 3*r + 7*g + b > 6*255 ){
zFg = "black";
}else{
zFg = "white";
}
if( zReq[0]!='#' ){
char zReqRGB[12];
sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
@ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
@ Requested color "%h(zReq)" (%h(zReqRGB))</td>
}else{
@ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
@ Requested color "%h(zReq)"</td>
}
zBg1 = reasonable_bg_color(zReq,1);
@ <td style='color:black;background-color:%h(zBg1);'>\
@ Background color for dark text: %h(zBg1)</td>
zBg1 = reasonable_bg_color(zReq,2);
@ <td style='color:white;background-color:%h(zBg1);'>\
@ Background color for light text: %h(zBg1)</td></tr>
}
if( cnt ){
@ </table>
@ <hr>
}
@ <form method="POST">
@ <p>Enter CSS color names below and see them shifted into corresponding
@ background colors above.</p>
for(i=0; i<N; i++){
sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
@ <input type="text" size="30" name='%s(zNm)' \
@ value='%h(PD(zNm,azDflt[i]))'><br>
}
@ <input type="submit" value="Submit">
@ </form>
style_finish_page();
}
|
Changes to src/db.c.
| ︙ | ︙ | |||
3367 3368 3369 3370 3371 3372 3373 |
db_end_transaction(0);
if( zTemplate ) db_detach("settingSrc");
if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
| | | 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 |
db_end_transaction(0);
if( zTemplate ) db_detach("settingSrc");
if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
g.zLogin, zPassword);
hash_user_password(g.zLogin);
}
/*
** SQL functions for debugging.
**
|
| ︙ | ︙ |
Changes to src/default.css.
| ︙ | ︙ | |||
749 750 751 752 753 754 755 756 757 758 759 760 761 762 |
}
body.tkt div.content ol.tkt-changes > li:target > ol {
border-left: 1px solid gold;
}
body.cpage-ckout .file-change-line,
body.cpage-info .file-change-line,
body.cpage-vinfo .file-change-line,
body.cpage-vdiff .file-change-line {
margin-top: 16px;
margin-bottom: 16px;
margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
display: flex;
flex-direction: row;
justify-content: space-between;
| > | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 |
}
body.tkt div.content ol.tkt-changes > li:target > ol {
border-left: 1px solid gold;
}
body.cpage-ckout .file-change-line,
body.cpage-info .file-change-line,
body.cpage-vinfo .file-change-line,
body.cpage-ci .file-change-line,
body.cpage-vdiff .file-change-line {
margin-top: 16px;
margin-bottom: 16px;
margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
display: flex;
flex-direction: row;
justify-content: space-between;
|
| ︙ | ︙ |
Changes to src/doc.c.
| ︙ | ︙ | |||
1050 1051 1052 1053 1054 1055 1056 |
/* The file is now contained in the filebody blob. Deliver the
** file to the user
*/
zMime = nMiss==0 ? P("mimetype") : 0;
if( zMime==0 ){
zMime = mimetype_from_name(zName);
}
| | | 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 |
/* The file is now contained in the filebody blob. Deliver the
** file to the user
*/
zMime = nMiss==0 ? P("mimetype") : 0;
if( zMime==0 ){
zMime = mimetype_from_name(zName);
}
Th_StoreUnsafe("doc_name", zName);
if( vid ){
Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
" FROM blob WHERE rid=%d", vid));
Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
" WHERE objid=%d AND type='ci'", vid));
}
cgi_check_for_malice();
|
| ︙ | ︙ |
Changes to src/encode.c.
| ︙ | ︙ | |||
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
j = i+1;
break;
}
}
if( j<i ) blob_append(p, zIn+j, i-j);
}
/*
** Encode a string for HTTP. This means converting lots of
** characters into the "%HH" where H is a hex digit. It also
** means converting spaces to "+".
**
** This is the opposite of DeHttpizeString below.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
j = i+1;
break;
}
}
if( j<i ) blob_append(p, zIn+j, i-j);
}
/*
** Make the given string safe for HTML by converting syntax characters
** into alternatives that do not have the special syntactic meaning.
**
** < --> U+227a
** > --> U+227b
** & --> +
** " --> U+201d
** ' --> U+2019
**
** Return a pointer to a new string obtained from fossil_malloc().
*/
char *html_lookalike(const char *z, int n){
unsigned char c;
int i = 0;
int count = 0;
unsigned char *zOut;
const unsigned char *zIn = (const unsigned char*)z;
if( n<0 ) n = strlen(z);
while( i<n ){
switch( zIn[i] ){
case '<': count += 3; break;
case '>': count += 3; break;
case '"': count += 3; break;
case '\'': count += 3; break;
case 0: n = i; break;
}
i++;
}
i = 0;
zOut = fossil_malloc( count+n+1 );
if( count==0 ){
memcpy(zOut, zIn, n);
zOut[n] = 0;
return (char*)zOut;
}
while( n-->0 ){
c = *(zIn++);
switch( c ){
case '<':
zOut[i++] = 0xe2;
zOut[i++] = 0x89;
zOut[i++] = 0xba;
break;
case '>':
zOut[i++] = 0xe2;
zOut[i++] = 0x89;
zOut[i++] = 0xbb;
break;
case '&':
zOut[i++] = '+';
break;
case '"':
zOut[i++] = 0xe2;
zOut[i++] = 0x80;
zOut[i++] = 0x9d;
break;
case '\'':
zOut[i++] = 0xe2;
zOut[i++] = 0x80;
zOut[i++] = 0x99;
break;
default:
zOut[i++] = c;
break;
}
}
zOut[i] = 0;
return (char*)zOut;
}
/*
** Encode a string for HTTP. This means converting lots of
** characters into the "%HH" where H is a hex digit. It also
** means converting spaces to "+".
**
** This is the opposite of DeHttpizeString below.
|
| ︙ | ︙ | |||
242 243 244 245 246 247 248 | *zOut = 0; return zRet; } /* ** Convert a single HEX digit to an integer */ | | | 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
*zOut = 0;
return zRet;
}
/*
** Convert a single HEX digit to an integer
*/
int fossil_hexvalue(int c){
if( c>='a' && c<='f' ){
c += 10 - 'a';
}else if( c>='A' && c<='F' ){
c += 10 - 'A';
}else if( c>='0' && c<='9' ){
c -= '0';
}else{
|
| ︙ | ︙ | |||
270 271 272 273 274 275 276 |
if( !z ) return 0;
i = j = 0;
while( z[i] ){
switch( z[i] ){
case '%':
if( z[i+1] && z[i+2] ){
| | | | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
if( !z ) return 0;
i = j = 0;
while( z[i] ){
switch( z[i] ){
case '%':
if( z[i+1] && z[i+2] ){
z[j] = fossil_hexvalue(z[i+1]) << 4;
z[j] |= fossil_hexvalue(z[i+2]);
i += 2;
}
break;
case '+':
z[j] = ' ';
break;
default:
|
| ︙ | ︙ |
Changes to src/finfo.c.
| ︙ | ︙ | |||
634 635 636 637 638 639 640 641 642 643 644 645 646 647 |
}
db_reset(&qparent);
if( zBr==0 ) zBr = "trunk";
if( uBg ){
zBgClr = user_color(zUser);
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
}
gidx = graph_add_row(pGraph,
frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
nParent, 0, aParent, zBr, zBgClr,
zUuid, 0);
if( strncmp(zDate, zPrevDate, 10) ){
sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
| > > | 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 |
}
db_reset(&qparent);
if( zBr==0 ) zBr = "trunk";
if( uBg ){
zBgClr = user_color(zUser);
}else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
}else if( zBgClr ){
zBgClr = reasonable_bg_color(zBgClr,0);
}
gidx = graph_add_row(pGraph,
frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
nParent, 0, aParent, zBr, zBgClr,
zUuid, 0);
if( strncmp(zDate, zPrevDate, 10) ){
sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
|
| ︙ | ︙ |
Changes to src/forum.c.
| ︙ | ︙ | |||
57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
*/
struct ForumThread {
ForumPost *pFirst; /* First post in chronological order */
ForumPost *pLast; /* Last post in chronological order */
ForumPost *pDisplay; /* Entries in display order */
ForumPost *pTail; /* Last on the display list */
int mxIndent; /* Maximum indentation level */
};
#endif /* INTERFACE */
/*
** Return true if the forum post with the given rid has been
** subsequently edited.
*/
| > | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
*/
struct ForumThread {
ForumPost *pFirst; /* First post in chronological order */
ForumPost *pLast; /* Last post in chronological order */
ForumPost *pDisplay; /* Entries in display order */
ForumPost *pTail; /* Last on the display list */
int mxIndent; /* Maximum indentation level */
int nArtifact; /* Number of forum artifacts in this thread */
};
#endif /* INTERFACE */
/*
** Return true if the forum post with the given rid has been
** subsequently edited.
*/
|
| ︙ | ︙ | |||
107 108 109 110 111 112 113 | ** value. Returns 0 if !p. For an edited chain of post, the tag is ** checked on the pEditHead entry, to simplify subsequent unlocking of ** the post. ** ** If bCheckIrt is true then p's thread in-response-to parents are ** checked (recursively) for closure, else only p is checked. */ | | > > > > > | | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
** value. Returns 0 if !p. For an edited chain of post, the tag is
** checked on the pEditHead entry, to simplify subsequent unlocking of
** the post.
**
** If bCheckIrt is true then p's thread in-response-to parents are
** checked (recursively) for closure, else only p is checked.
*/
static int forumpost_is_closed(
ForumThread *pThread, /* Thread that the post is a member of */
ForumPost *p, /* the forum post */
int bCheckIrt /* True to check In-Reply-To posts */
){
int mx = pThread->nArtifact+1;
while( p && (mx--)>0 ){
if( p->pEditHead ) p = p->pEditHead;
if( p->iClosed || !bCheckIrt ) return p->iClosed;
p = p->pIrt;
}
return 0;
}
|
| ︙ | ︙ | |||
407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
pPost->pNext = 0;
if( pThread->pLast==0 ){
pThread->pFirst = pPost;
}else{
pThread->pLast->pNext = pPost;
}
pThread->pLast = pPost;
/* Find the in-reply-to post. Default to the topic post if the replied-to
** post cannot be found. */
if( firt ){
pPost->pIrt = pThread->pFirst;
for(p=pThread->pFirst; p; p=p->pNext){
if( p->fpid==firt ){
| > | 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
pPost->pNext = 0;
if( pThread->pLast==0 ){
pThread->pFirst = pPost;
}else{
pThread->pLast->pNext = pPost;
}
pThread->pLast = pPost;
pThread->nArtifact++;
/* Find the in-reply-to post. Default to the topic post if the replied-to
** post cannot be found. */
if( firt ){
pPost->pIrt = pThread->pFirst;
for(p=pThread->pFirst; p; p=p->pNext){
if( p->fpid==firt ){
|
| ︙ | ︙ | |||
518 519 520 521 522 523 524 525 526 527 528 529 530 531 |
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
if( froot==0 ){
fossil_fatal("Not a forum post: \"%s\"", zName);
}
fossil_print("fpid = %d\n", fpid);
fossil_print("froot = %d\n", froot);
pThread = forumthread_create(froot, 1);
fossil_print("Chronological:\n");
fossil_print(
/* 0 1 2 3 4 5 6 7 */
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
" sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
for(p=pThread->pFirst; p; p=p->pNext){
fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
| > | 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 |
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
if( froot==0 ){
fossil_fatal("Not a forum post: \"%s\"", zName);
}
fossil_print("fpid = %d\n", fpid);
fossil_print("froot = %d\n", froot);
pThread = forumthread_create(froot, 1);
fossil_print("count = %d\n", pThread->nArtifact);
fossil_print("Chronological:\n");
fossil_print(
/* 0 1 2 3 4 5 6 7 */
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
" sid rev closed fpid pIrt pEditPrev pEditTail hash\n");
for(p=pThread->pFirst; p; p=p->pNext){
fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
|
| ︙ | ︙ | |||
563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
void forumthreadhashlist(void){
int fpid;
int froot;
const char *zName = P("name");
ForumThread *pThread;
ForumPost *p;
char *fuuid;
login_check_credentials();
if( !g.perm.Admin ){
return;
}
if( zName==0 ){
webpage_error("Missing \"name=\" query parameter");
| > | 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 |
void forumthreadhashlist(void){
int fpid;
int froot;
const char *zName = P("name");
ForumThread *pThread;
ForumPost *p;
char *fuuid;
Stmt q;
login_check_credentials();
if( !g.perm.Admin ){
return;
}
if( zName==0 ){
webpage_error("Missing \"name=\" query parameter");
|
| ︙ | ︙ | |||
597 598 599 600 601 602 603 604 605 606 607 608 609 610 |
@ <pre>
pThread = forumthread_create(froot, 1);
for(p=pThread->pFirst; p; p=p->pNext){
@ %h(p->zUuid)
}
forumthread_delete(pThread);
@ </pre>
style_finish_page();
}
/*
** Render a forum post for display
*/
void forum_render(
| > > > > > > > > > > > > > > > > > | 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 |
@ <pre>
pThread = forumthread_create(froot, 1);
for(p=pThread->pFirst; p; p=p->pNext){
@ %h(p->zUuid)
}
forumthread_delete(pThread);
@ </pre>
@ <hr>
@ <h2>Related FORUMPOST Table Content</h2>
@ <table border="1" cellpadding="4" cellspacing="0">
@ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime
db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)"
" FROM forumpost"
" WHERE froot=%d"
" ORDER BY fmtime", froot);
while( db_step(&q)==SQLITE_ROW ){
@ <tr><td>%d(db_column_int(&q,0))\
@ <td>%d(db_column_int(&q,1))\
@ <td>%d(db_column_int(&q,2))\
@ <td>%d(db_column_int(&q,3))\
@ <td>%h(db_column_text(&q,4))</tr>
}
@ </table>
db_finalize(&q);
style_finish_page();
}
/*
** Render a forum post for display
*/
void forum_render(
|
| ︙ | ︙ | |||
723 724 725 726 727 728 729 730 731 732 733 734 735 736 | } /* ** Display a single post in a forum thread. */ static void forum_display_post( ForumPost *p, /* Forum post to display */ int iIndentScale, /* Indent scale factor */ int bRaw, /* True to omit the border */ int bUnf, /* True to leave the post unformatted */ int bHist, /* True if showing edit history */ int bSelect, /* True if this is the selected post */ char *zQuery /* Common query string */ | > | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | } /* ** Display a single post in a forum thread. */ static void forum_display_post( ForumThread *pThread, /* The thread that this post is a member of */ ForumPost *p, /* Forum post to display */ int iIndentScale, /* Indent scale factor */ int bRaw, /* True to omit the border */ int bUnf, /* True to leave the post unformatted */ int bHist, /* True if showing edit history */ int bSelect, /* True if this is the selected post */ char *zQuery /* Common query string */ |
| ︙ | ︙ | |||
745 746 747 748 749 750 751 | int iIndent; /* Indent level */ int iClosed; /* True if (sub)thread is closed */ const char *zMimetype;/* Formatting MIME type */ /* Get the manifest for the post. Abort if not found (e.g. shunned). */ pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); if( !pManifest ) return; | | | | 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 |
int iIndent; /* Indent level */
int iClosed; /* True if (sub)thread is closed */
const char *zMimetype;/* Formatting MIME type */
/* Get the manifest for the post. Abort if not found (e.g. shunned). */
pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
if( !pManifest ) return;
iClosed = forumpost_is_closed(pThread, p, 1);
/* When not in raw mode, create the border around the post. */
if( !bRaw ){
/* Open the <div> enclosing the post. Set the class string to mark the post
** as selected and/or obsolete. */
iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
@ <div id='forum%d(p->fpid)' class='forumTime\
@ %s(bSelect ? " forumSel" : "")\
@ %s(iClosed ? " forumClosed" : "")\
@ %s(p->pEditTail ? " forumObs" : "")' \
if( iIndent && iIndentScale ){
|
| ︙ | ︙ | |||
1025 1026 1027 1028 1029 1030 1031 |
p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
if( !bHist && p->pEditTail ) p = p->pEditTail;
}
/* Display the appropriate subset of posts in sequence. */
while( p ){
/* Display the post. */
| | | 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 |
p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
if( !bHist && p->pEditTail ) p = p->pEditTail;
}
/* Display the appropriate subset of posts in sequence. */
while( p ){
/* Display the post. */
forum_display_post(pThread, p, iIndentScale, mode==FD_RAW,
bUnf, bHist, p==pSelect, zQuery);
/* Advance to the next post in the thread. */
if( mode==FD_CHRONO ){
/* Chronological mode: display posts (optionally including edits) in their
** original commit order. */
if( bHist ){
|
| ︙ | ︙ |
Changes to src/fossil.dom.js.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 |
},
createElemFactory: function(eType){
return function(){
return document.createElement(eType);
};
},
remove: function(e){
| | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
},
createElemFactory: function(eType){
return function(){
return document.createElement(eType);
};
},
remove: function(e){
if(e?.forEach){
e.forEach(
(x)=>x?.parentNode?.removeChild(x)
);
}else{
e?.parentNode?.removeChild(e);
}
return e;
},
/**
Removes all child DOM elements from the given element
and returns that element.
|
| ︙ | ︙ |
Changes to src/fossil.fetch.js.
| ︙ | ︙ | |||
25 26 27 28 29 30 31 | - onload: callback(responseData) (default = output response to the console). In the context of the callback, the options object is "this", noting that this call may have amended the options object with state other than what the caller provided. - onerror: callback(Error object) (default = output error message to console.error() and fossil.error()). Triggered if the request | | < | | | > | > > > > > > > > > | > > > > > | > > > > > > > > > > > > > > > > > > > | > > > > | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
- onload: callback(responseData) (default = output response to the
console). In the context of the callback, the options object is
"this", noting that this call may have amended the options object
with state other than what the caller provided.
- onerror: callback(Error object) (default = output error message
to console.error() and fossil.error()). Triggered if the request
generates any response other than HTTP 200, or if the beforesend()
or onload() handler throws an exception. In the context of the
callback, the options object is "this". This function is intended
to be used solely for error reporting, not error recovery. Special
cases for the Error object:
1. Timeouts unfortunately show up as a series of 2 events: an
HTTP 0 followed immediately by an XHR.ontimeout(). The former
cannot(?) be unambiguously identified as the trigger for the
pending timeout, so we have no option but to pass it on as-is
instead of flagging it as a timeout response. The latter will
trigger the client-provided ontimeout() if it's available (see
below), else it calls the onerror() callback. An error object
passed to ontimeout() by fetch() will have (.name='timeout',
.status=XHR.status).
2. Else if the response contains a JSON-format exception on the
server, it will have (.name='json-error',
status=XHR.status). Any JSON-format result object which has a
property named "error" is considered to be a server-generated
error.
3. Else if it gets a non 2xx HTTP code then it will have
(.name='http',.status=XHR.status).
4. If onerror() throws, the exception is suppressed but may
generate a console error message.
- ontimeout: callback(Error object). If set, timeout errors are
reported here, else they are reported through onerror().
Unfortunately, XHR fires two events for a timeout: an
onreadystatechange() and an ontimeout(), in that order. From the
former, however, we cannot unambiguously identify the error as
having been caused by a timeout, so clients which set ontimeout()
will get _two_ callback calls: one with with an HTTP error response
followed immediately by an ontimeout() response. Error objects
passed to this will have (.name='timeout', .status=xhr.HttpStatus).
In the context of the callback, the options object is "this", Like
onerror(), any exceptions thrown by the ontimeout() handler are
suppressed, but may generate a console error message. The onerror()
handler is _not_ called in this case.
- method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE!
- payload: anything acceptable by XHR2.send(ARG) (DOMString,
Document, FormData, Blob, File, ArrayBuffer), or a plain object or
array, either of which gets JSON.stringify()'d. If payload is set
then the method is automatically set to 'POST'. By default XHR2
will set the content type based on the payload type. If an
object/array is converted to JSON, the contentType option is
automatically set to 'application/json', and if JSON.stringify() of
that value fails then the exception is propagated to this
function's caller. (beforesend(), aftersend(), and onerror() are
NOT triggered in that case.)
- contentType: Optional request content type when POSTing. Ignored
if the method is not 'POST'.
- responseType: optional string. One of ("text", "arraybuffer",
"blob", or "document") (as specified by XHR2). Default = "text".
As an extension, it supports "json", which tells it that the
response is expected to be text and that it should be JSON.parse()d
before passing it on to the onload() callback. If parsing of such
an object fails, the onload callback is not called, and the
onerror() callback is passed the exception from the parsing error.
If the parsed JSON object has an "error" property, it is assumed to
be an error string, which is used to populate a new Error object,
which will gets (.name="json") set on it.
- urlParams: string|object. If a string, it is assumed to be a
URI-encoded list of params in the form "key1=val1&key2=val2...",
with NO leading '?'. If it is an object, all of its properties get
converted to that form. Either way, the parameters get appended to
the URL before submitting the request.
- responseHeaders: If true, the onload() callback is passed an
additional argument: a map of all of the response headers. If it's
a string value, the 2nd argument passed to onload() is instead the
value of that single header. If it's an array, it's treated as a
list of headers to return, and the 2nd argument is a map of those
header values. When a map is passed on, all of its keys are
lower-cased. When a given header is requested and that header is
set multiple times, their values are (per the XHR docs)
concatenated together with "," between them.
- beforesend/aftersend: optional callbacks which are called
without arguments immediately before the request is submitted
and immediately after it is received, regardless of success or
error. In the context of the callback, the options object is
the "this". These can be used to, e.g., keep track of in-flight
requests and update the UI accordingly, e.g. disabling/enabling
|
| ︙ | ︙ | |||
131 132 133 134 135 136 137 |
const value = parts.join(': ');
rc[header.toLowerCase()] = value;
});
return rc;
};
}
if('/'===uri[0]) uri = uri.substr(1);
| | | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
const value = parts.join(': ');
rc[header.toLowerCase()] = value;
});
return rc;
};
}
if('/'===uri[0]) uri = uri.substr(1);
if(!opt) opt = {}/* should arguably be Object.create(null) */;
else if('function'===typeof opt) opt={onload:opt};
if(!opt.onload) opt.onload = f.onload;
if(!opt.onerror) opt.onerror = f.onerror;
if(!opt.beforesend) opt.beforesend = f.beforesend;
if(!opt.aftersend) opt.aftersend = f.aftersend;
let payload = opt.payload, jsonResponse = false;
if(undefined!==payload){
|
| ︙ | ︙ | |||
162 163 164 165 166 167 168 |
list. We use it as a flag to tell us to JSON.parse()
the response. */
jsonResponse = true;
x.responseType = 'text';
}else{
x.responseType = opt.responseType||'text';
}
| | | > > > > > > > > > > > > > > > > > > | > | > > > > > > | > > > > > > > | > > > > | | | | | | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
list. We use it as a flag to tell us to JSON.parse()
the response. */
jsonResponse = true;
x.responseType = 'text';
}else{
x.responseType = opt.responseType||'text';
}
x.ontimeout = function(ev){
try{opt.aftersend()}catch(e){/*ignore*/}
const err = new Error("XHR timeout of "+x.timeout+"ms expired.");
err.status = x.status;
err.name = 'timeout';
//console.warn("fetch.ontimeout",ev);
try{
(opt.ontimeout || opt.onerror)(err);
}catch(e){
/*ignore*/
console.error("fossil.fetch()'s ontimeout() handler threw",e);
}
};
/* Ensure that if onerror() throws, it's ignored. */
const origOnError = opt.onerror;
opt.onerror = (arg)=>{
try{ origOnError.call(this, arg) }
catch(e){
/*ignored*/
console.error("fossil.fetch()'s onerror() threw",e);
}
};
x.onreadystatechange = function(ev){
//console.warn("onreadystatechange", x.readyState, ev.target.responseText);
if(XMLHttpRequest.DONE !== x.readyState) return;
try{opt.aftersend()}catch(e){/*ignore*/}
if(false && 0===x.status){
/* For reasons unknown, we _sometimes_ trigger x.status==0 in FF
when the /chat page starts up, but not in Chrome nor in other
apps. Insofar as has been determined, this happens before a
request is actually sent and it appears to have no
side-effects on the app other than to generate an error
(i.e. no requests/responses are missing). This is a silly
workaround which may or may not bite us later. If so, it can
be removed at the cost of an unsightly console error message
in FF.
2025-04-10: that behavior is now also in Chrome and enabling
this workaround causes our timeout errors to never arrive.
*/
return;
}
if(200!==x.status){
//console.warn("Error response",ev.target);
let err;
try{
const j = JSON.parse(x.response);
if(j.error){
err = new Error(j.error);
err.name = 'json.error';
}
}catch(ex){/*ignore*/}
if( !err ){
/* We can't tell from here whether this was a timeout-capable
request which timed out on our end or was one which is a
genuine error. We also don't know whether the server timed
out the connection before we did. */
err = new Error("HTTP response status "+x.status+".")
err.name = 'http';
}
err.status = x.status;
opt.onerror(err);
return;
}
const orh = opt.responseHeaders;
let head;
if(true===orh){
head = f.parseResponseHeaders(x.getAllResponseHeaders());
}else if('string'===typeof orh){
head = x.getResponseHeader(orh);
}else if(orh instanceof Array){
head = {};
orh.forEach((s)=>{
if('string' === typeof s) head[s.toLowerCase()] = x.getResponseHeader(s);
});
}
try{
const args = [(jsonResponse && x.response)
? JSON.parse(x.response) : x.response];
if(head) args.push(head);
opt.onload.apply(opt, args);
}catch(err){
opt.onerror(err);
}
}/*onreadystatechange()*/;
try{opt.beforesend()}
catch(err){
opt.onerror(err);
return;
}
x.open(opt.method||'GET', url.join(''), true);
if('POST'===opt.method && 'string'===typeof opt.contentType){
x.setRequestHeader('Content-Type',opt.contentType);
}
x.timeout = +opt.timeout || f.timeout;
|
| ︙ | ︙ |
Changes to src/fossil.page.chat.js.
|
| | | 1 2 3 4 5 6 7 8 |
-/**
This file contains the client-side implementation of fossil's /chat
application.
*/
window.fossil.onPageLoad(function(){
const F = window.fossil, D = F.dom;
const E1 = function(selector){
const e = document.querySelector(selector);
|
| ︙ | ︙ | |||
127 128 129 130 131 132 133 |
resized.$disabled = true/*gets deleted when setup is finished*/;
window.addEventListener('resize', F.debounce(resized, 250), false);
return resized;
})();
fossil.FRK = ForceResizeKludge/*for debugging*/;
const Chat = ForceResizeKludge.chat = (function(){
const cs = { // the "Chat" object (result of this function)
| > > | | | | | > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
resized.$disabled = true/*gets deleted when setup is finished*/;
window.addEventListener('resize', F.debounce(resized, 250), false);
return resized;
})();
fossil.FRK = ForceResizeKludge/*for debugging*/;
const Chat = ForceResizeKludge.chat = (function(){
const cs = { // the "Chat" object (result of this function)
beVerbose: false
//!!window.location.hostname.match("localhost")
/* if true then certain, mostly extraneous, error messages and
log messages may be sent to the console. */,
playedBeep: false /* used for the beep-once setting */,
e:{/*map of certain DOM elements.*/
messageInjectPoint: E1('#message-inject-point'),
pageTitle: E1('head title'),
loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
inputArea: E1("#chat-input-area"),
inputLineWrapper: E1('#chat-input-line-wrapper'),
fileSelectWrapper: E1('#chat-input-file-area'),
viewMessages: E1('#chat-messages-wrapper'),
btnSubmit: E1('#chat-button-submit'),
btnAttach: E1('#chat-button-attach'),
inputX: E1('#chat-input-field-x'),
input1: E1('#chat-input-field-single'),
inputM: E1('#chat-input-field-multi'),
inputFile: E1('#chat-input-file'),
contentDiv: E1('div.content'),
viewConfig: E1('#chat-config'),
viewPreview: E1('#chat-preview'),
previewContent: E1('#chat-preview-content'),
viewSearch: E1('#chat-search'),
searchContent: E1('#chat-search-content'),
btnPreview: E1('#chat-button-preview'),
views: document.querySelectorAll('.chat-view'),
activeUserListWrapper: E1('#chat-user-list-wrapper'),
activeUserList: E1('#chat-user-list'),
eMsgPollError: undefined /* current connection error MessageMidget */,
pollErrorMarker: document.body /* element to toggle 'connection-error' CSS class on */
},
me: F.user.name,
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
pageIsActive: 'visible'===document.visibilityState,
changesSincePageHidden: 0,
notificationBubbleColor: 'white',
|
| ︙ | ︙ | |||
177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
new Date((J - 2440587.5) * 86400000) */
},
filterState:{
activeUser: undefined,
match: function(uname){
return this.activeUser===uname || !this.activeUser;
}
},
/**
Gets (no args) or sets (1 arg) the current input text field
value, taking into account single- vs multi-line input. The
getter returns a trim()'d string and the setter returns this
object. As a special case, if arguments[0] is a boolean
value, it behaves like a getter and, if arguments[0]===true
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
new Date((J - 2440587.5) * 86400000) */
},
filterState:{
activeUser: undefined,
match: function(uname){
return this.activeUser===uname || !this.activeUser;
}
},
/**
The timer object is used to control connection throttling
when connection errors arrise. It starts off with a polling
delay of $initialDelay ms. If there's a connection error,
that gets bumped by some value for each subsequent error, up
to some max value.
The timing of resetting the delay when service returns is,
because of the long-poll connection and our lack of low-level
insight into the connection at this level, a bit wonky.
*/
timer:{
/* setTimeout() ID for (delayed) starting a Chat.poll(), so
that it runs at controlled intervals (which change when a
connection drops and recovers). */
tidPendingPoll: undefined,
tidClearPollErr: undefined /*setTimeout() timer id for
reconnection determination. See
clearPollErrOnWait(). */,
$initialDelay: 1000 /* initial polling interval (ms) */,
currentDelay: 1000 /* current polling interval */,
maxDelay: 60000 * 5 /* max interval when backing off for
connection errors */,
minDelay: 5000 /* minimum delay time for a back-off/retry
attempt. */,
errCount: 0 /* Current poller connection error count */,
minErrForNotify: 4 /* Don't warn for connection errors until this
many have occurred */,
pollTimeout: (1 && window.location.hostname.match(
"localhost" /*presumably local dev mode*/
)) ? 15000
: (+F.config.chat.pollTimeout>0
? (1000 * (F.config.chat.pollTimeout - Math.floor(F.config.chat.pollTimeout * 0.1)))
/* ^^^^^^^^^^^^ we want our timeouts to be slightly shorter
than the server's so that we can distingished timed-out
polls on our end from HTTP errors (if the server times
out). */
: 30000),
/** Returns a random fudge value for reconnect attempt times,
intended to keep the /chat server from getting hammered if
all clients which were just disconnected all reconnect at
the same instant. */
randomInterval: function(factor){
return Math.floor(Math.random() * factor);
},
/** Increments the reconnection delay, within some min/max range. */
incrDelay: function(){
if( this.maxDelay > this.currentDelay ){
if(this.currentDelay < this.minDelay){
this.currentDelay = this.minDelay + this.randomInterval(this.minDelay);
}else{
this.currentDelay = this.currentDelay*2 + this.randomInterval(this.currentDelay);
}
}
return this.currentDelay;
},
/** Resets the delay counter to v || its initial value. */
resetDelay: function(ms=0){
return this.currentDelay = ms || this.$initialDelay;
},
/** Returns true if the timer is set to delayed mode. */
isDelayed: function(){
return (this.currentDelay > this.$initialDelay) ? this.currentDelay : 0;
},
/**
Cancels any in-progress pending-poll timer and starts a new
one with the given delay, defaulting to this.resetDelay().
*/
startPendingPollTimer: function(delay){
this.cancelPendingPollTimer().tidPendingPoll
= setTimeout( Chat.poll, delay || Chat.timer.resetDelay() );
return this;
},
/**
Cancels any still-active timer set to trigger the next
Chat.poll().
*/
cancelPendingPollTimer: function(){
if( this.tidPendingPoll ){
clearTimeout(this.tidPendingPoll);
this.tidPendingPoll = 0;
}
return this;
},
/**
Cancels any pending reconnection attempt back-off timer..
*/
cancelReconnectCheckTimer: function(){
if( this.tidClearPollErr ){
clearTimeout(this.tidClearPollErr);
this.tidClearPollErr = 0;
}
return this;
}
},
/**
Gets (no args) or sets (1 arg) the current input text field
value, taking into account single- vs multi-line input. The
getter returns a trim()'d string and the setter returns this
object. As a special case, if arguments[0] is a boolean
value, it behaves like a getter and, if arguments[0]===true
|
| ︙ | ︙ | |||
604 605 606 607 608 609 610 |
return this;
},
/**
If animations are enabled, passes its arguments
to D.addClassBriefly(), else this is a no-op.
If cb is a function, it is called after the
| | | | 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 |
return this;
},
/**
If animations are enabled, passes its arguments
to D.addClassBriefly(), else this is a no-op.
If cb is a function, it is called after the
CSS class is removed. Returns this object;
*/
animate: function f(e,a,cb){
if(!f.$disabled){
D.addClassBriefly(e, a, 0, cb);
}
return this;
}
}/*Chat object*/;
cs.e.inputFields = [ cs.e.input1, cs.e.inputM, cs.e.inputX ];
cs.e.inputFields.$currentIndex = 0;
cs.e.inputFields.forEach(function(e,ndx){
if(ndx===cs.e.inputFields.$currentIndex) D.removeClass(e,'hidden');
else D.addClass(e,'hidden');
});
if(D.attr(cs.e.inputX,'contenteditable','plaintext-only').isContentEditable){
|
| ︙ | ︙ | |||
643 644 645 646 647 648 649 650 651 652 653 654 655 656 |
Accepts any argument types valid for fossil.toast.error().
*/
cs.reportError = function(/*msg args*/){
const args = argsToArray(arguments);
console.error("chat error:",args);
F.toast.error.apply(F.toast, args);
};
/**
Reports an error in the form of a new message in the chat
feed. All arguments are appended to the message's content area
using fossil.dom.append(), so may be of any type supported by
that function.
*/
cs.reportErrorAsMessage = function f(/*msg args*/){
| > > < > | > | | > > > > > > > > > > > > > > > > > > > > > > > | 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 |
Accepts any argument types valid for fossil.toast.error().
*/
cs.reportError = function(/*msg args*/){
const args = argsToArray(arguments);
console.error("chat error:",args);
F.toast.error.apply(F.toast, args);
};
let InternalMsgId = 0;
/**
Reports an error in the form of a new message in the chat
feed. All arguments are appended to the message's content area
using fossil.dom.append(), so may be of any type supported by
that function.
*/
cs.reportErrorAsMessage = function f(/*msg args*/){
const args = argsToArray(arguments).map(function(v){
return (v instanceof Error) ? v.message : v;
});
if(Chat.beVerbose){
console.error("chat error:",args);
}
const d = new Date().toISOString(),
mw = new this.MessageWidget({
isError: true,
xfrom: undefined,
msgid: "error-"+(++InternalMsgId),
mtime: d,
lmtime: d,
xmsg: args
});
this.injectMessageElem(mw.e.body);
mw.scrollIntoView();
return mw;
};
/**
For use by the connection poller to send a "connection
restored" message.
*/
cs.reportReconnection = function f(/*msg args*/){
const args = argsToArray(arguments).map(function(v){
return (v instanceof Error) ? v.message : v;
});
const d = new Date().toISOString(),
mw = new this.MessageWidget({
isError: false,
xfrom: undefined,
msgid: "reconnect-"+(++InternalMsgId),
mtime: d,
lmtime: d,
xmsg: args
});
this.injectMessageElem(mw.e.body);
mw.scrollIntoView();
return mw;
};
cs.getMessageElemById = function(id){
return qs('[data-msgid="'+id+'"]');
};
/** Finds the last .message-widget element and returns it or
|
| ︙ | ︙ | |||
688 689 690 691 692 693 694 |
};
/**
LOCALLY deletes a message element by the message ID or passing
the .message-row element. Returns true if it removes an element,
else false.
*/
| | > > > > > > > > > > > > > > | > | > | 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 |
};
/**
LOCALLY deletes a message element by the message ID or passing
the .message-row element. Returns true if it removes an element,
else false.
*/
cs.deleteMessageElem = function(id, silent){
var e;
if(id instanceof HTMLElement){
e = id;
id = e.dataset.msgid;
delete e.dataset.msgid;
if( e?.dataset?.alsoRemove ){
const xId = e.dataset.alsoRemove;
delete e.dataset.alsoRemove;
this.deleteMessageElem( xId );
}
}else if(id instanceof Chat.MessageWidget) {
if( this.e.eMsgPollError === e ){
this.e.eMsgPollError = undefined;
}
if(id.e?.body){
this.deleteMessageElem(id.e.body);
}
return;
} else{
e = this.getMessageElemById(id);
}
if(e && id){
D.remove(e);
if(e===this.e.newestMessage){
this.fetchLastMessageElem();
}
if( !silent ){
F.toast.message("Deleted message "+id+".");
}
}
return !!e;
};
/**
Toggles the given message between its parsed and plain-text
representations. It requires a server round-trip to collect the
|
| ︙ | ︙ | |||
774 775 776 777 778 779 780 781 782 783 784 785 786 787 |
}
// We need to fetch the plain-text version...
const self = this;
F.fetch('chat-fetch-one',{
urlParams:{ name: id, raw: true},
responseType: 'json',
onload: function(msg){
content.$elems[1] = D.append(D.pre(),msg.xmsg);
content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/;
self.toggleTextMode(e);
},
aftersend:function(){
delete e.$isToggling;
Chat.ajaxEnd();
| > | 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 |
}
// We need to fetch the plain-text version...
const self = this;
F.fetch('chat-fetch-one',{
urlParams:{ name: id, raw: true},
responseType: 'json',
onload: function(msg){
reportConnectionOkay('chat-fetch-one');
content.$elems[1] = D.append(D.pre(),msg.xmsg);
content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/;
self.toggleTextMode(e);
},
aftersend:function(){
delete e.$isToggling;
Chat.ajaxEnd();
|
| ︙ | ︙ | |||
832 833 834 835 836 837 838 |
e = id;
id = e.dataset.msgid;
}else{
e = this.getMessageElemById(id);
}
if(!(e instanceof HTMLElement)) return;
if(this.userMayDelete(e)){
| < > > | > | 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 |
e = id;
id = e.dataset.msgid;
}else{
e = this.getMessageElemById(id);
}
if(!(e instanceof HTMLElement)) return;
if(this.userMayDelete(e)){
F.fetch("chat-delete/" + id, {
responseType: 'json',
onload:(r)=>{
reportConnectionOkay('chat-delete');
this.deleteMessageElem(r);
},
onerror:(err)=>this.reportErrorAsMessage(err)
});
}else{
this.deleteMessageElem(id);
}
};
document.addEventListener('visibilitychange', function(ev){
|
| ︙ | ︙ | |||
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 |
}
};
ctor.prototype = {
scrollIntoView: function(){
this.e.content.scrollIntoView();
},
setMessage: function(m){
const ds = this.e.body.dataset;
ds.timestamp = m.mtime;
ds.lmtime = m.lmtime;
ds.msgid = m.msgid;
ds.xfrom = m.xfrom || '';
if(m.xfrom === Chat.me){
| > | 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 |
}
};
ctor.prototype = {
scrollIntoView: function(){
this.e.content.scrollIntoView();
},
//remove: function(silent){Chat.deleteMessageElem(this, silent);},
setMessage: function(m){
const ds = this.e.body.dataset;
ds.timestamp = m.mtime;
ds.lmtime = m.lmtime;
ds.msgid = m.msgid;
ds.xfrom = m.xfrom || '';
if(m.xfrom === Chat.me){
|
| ︙ | ︙ | |||
1210 1211 1212 1213 1214 1215 1216 |
const toolbar = D.addClass(D.div(), 'toolbar');
D.append(this.e, toolbar);
const btnDeleteLocal = D.button("Delete locally");
D.append(toolbar, btnDeleteLocal);
const self = this;
btnDeleteLocal.addEventListener('click', function(){
self.hide();
| | > > > > > > > > > > | 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 |
const toolbar = D.addClass(D.div(), 'toolbar');
D.append(this.e, toolbar);
const btnDeleteLocal = D.button("Delete locally");
D.append(toolbar, btnDeleteLocal);
const self = this;
btnDeleteLocal.addEventListener('click', function(){
self.hide();
Chat.deleteMessageElem(eMsg)
});
if( eMsg.classList.contains('notification') ){
const btnDeletePoll = D.button("Delete /chat notifications?");
D.append(toolbar, btnDeletePoll);
btnDeletePoll.addEventListener('click', function(){
self.hide();
Chat.e.viewMessages.querySelectorAll(
'.message-widget.notification:not(.resend-message)'
).forEach(e=>Chat.deleteMessageElem(e, true));
});
}
if(Chat.userMayDelete(eMsg)){
const btnDeleteGlobal = D.button("Delete globally");
D.append(toolbar, btnDeleteGlobal);
F.confirmer(btnDeleteGlobal,{
pinSize: true,
ticks: F.config.confirmerButtonTicks,
confirmText: "Confirm delete?",
|
| ︙ | ︙ | |||
1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 |
urlParams:{
q: '',
n: nFetch,
i: iFirst
},
responseType: "json",
onload:function(jx){
if( bDown ) jx.msgs.reverse();
jx.msgs.forEach((m) => {
m.isSearchResult = true;
var mw = new Chat.MessageWidget(m);
if( bDown ){
/* Inject the message below this object's body, or
append it to Chat.e.searchContent if this element
| > | 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 |
urlParams:{
q: '',
n: nFetch,
i: iFirst
},
responseType: "json",
onload:function(jx){
reportConnectionOkay('chat-query');
if( bDown ) jx.msgs.reverse();
jx.msgs.forEach((m) => {
m.isSearchResult = true;
var mw = new Chat.MessageWidget(m);
if( bDown ){
/* Inject the message below this object's body, or
append it to Chat.e.searchContent if this element
|
| ︙ | ︙ | |||
1522 1523 1524 1525 1526 1527 1528 |
D.append(dd, D.br(), img);
const reader = new FileReader();
reader.onload = (e)=>img.setAttribute('src', e.target.result);
reader.readAsDataURL(blob);
}
};
Chat.e.inputFile.addEventListener('change', function(ev){
| | | 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 |
D.append(dd, D.br(), img);
const reader = new FileReader();
reader.onload = (e)=>img.setAttribute('src', e.target.result);
reader.readAsDataURL(blob);
}
};
Chat.e.inputFile.addEventListener('change', function(ev){
updateDropZoneContent(this?.files[0])
});
/* Handle image paste from clipboard. TODO: figure out how we can
paste non-image binary data as if it had been selected via the
file selection element. */
const pasteListener = function(event){
const items = event.clipboardData.items,
item = items[0];
|
| ︙ | ︙ | |||
1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 |
const w = D.addClass(D.div(), 'failed-message');
D.append(w, D.append(
D.span(),"This message was not successfully sent to the server:"
));
if(state.msg){
const ta = D.textarea();
ta.value = state.msg;
D.append(w,ta);
}
if(state.blob){
D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed")));
//console.debug("blob = ",state.blob);
}
const buttons = D.addClass(D.div(), 'buttons');
D.append(w, buttons);
D.append(buttons, D.button("Discard message?", function(){
const theMsg = findMessageWidgetParent(w);
if(theMsg) Chat.deleteMessageElem(theMsg);
}));
D.append(buttons, D.button("Edit message and try again?", function(){
if(state.msg) Chat.inputValue(state.msg);
if(state.blob) BlobXferState.updateDropZoneContent(state.blob);
const theMsg = findMessageWidgetParent(w);
if(theMsg) Chat.deleteMessageElem(theMsg);
}));
| > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 |
const w = D.addClass(D.div(), 'failed-message');
D.append(w, D.append(
D.span(),"This message was not successfully sent to the server:"
));
if(state.msg){
const ta = D.textarea();
ta.value = state.msg;
ta.setAttribute('readonly','true');
D.append(w,ta);
}
if(state.blob){
D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed")));
//console.debug("blob = ",state.blob);
}
const buttons = D.addClass(D.div(), 'buttons');
D.append(w, buttons);
D.append(buttons, D.button("Discard message?", function(){
const theMsg = findMessageWidgetParent(w);
if(theMsg) Chat.deleteMessageElem(theMsg);
}));
D.append(buttons, D.button("Edit message and try again?", function(){
if(state.msg) Chat.inputValue(state.msg);
if(state.blob) BlobXferState.updateDropZoneContent(state.blob);
const theMsg = findMessageWidgetParent(w);
if(theMsg) Chat.deleteMessageElem(theMsg);
}));
D.addClass(Chat.reportErrorAsMessage(w).e.body, "resend-message");
};
/* Assume the connection has been established, reset the
Chat.timer.tidClearPollErr, and (if showMsg and
!!Chat.e.eMsgPollError) alert the user that the outage appears to
be over. Also schedule Chat.poll() to run in the very near
future. */
const reportConnectionOkay = function(dbgContext, showMsg = true){
if(Chat.beVerbose){
console.warn('reportConnectionOkay', dbgContext,
'Chat.e.pollErrorMarker classes =',
Chat.e.pollErrorMarker.classList,
'Chat.timer.tidClearPollErr =',Chat.timer.tidClearPollErr,
'Chat.timer =',Chat.timer);
}
if( Chat.timer.errCount ){
D.removeClass(Chat.e.pollErrorMarker, 'connection-error');
Chat.timer.errCount = 0;
}
Chat.timer.cancelReconnectCheckTimer().startPendingPollTimer();
if( Chat.e.eMsgPollError ) {
const oldErrMsg = Chat.e.eMsgPollError;
Chat.e.eMsgPollError = undefined;
if( showMsg ){
if(Chat.beVerbose){
console.log("Poller Connection restored.");
}
const m = Chat.reportReconnection("Poller connection restored.");
if( oldErrMsg ){
D.remove(oldErrMsg.e?.body.querySelector('button.retry-now'));
}
m.e.body.dataset.alsoRemove = oldErrMsg?.e?.body?.dataset?.msgid;
D.addClass(m.e.body,'poller-connection');
}
}
};
/**
Submits the contents of the message input field (if not empty)
and/or the file attachment field to the server. If both are
empty, this is a no-op.
|
| ︙ | ︙ | |||
1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 |
payload: fd,
responseType: 'text',
onerror:function(err){
self.reportErrorAsMessage(err);
recoverFailedMessage(fallback);
},
onload:function(txt){
if(!txt) return/*success response*/;
try{
const json = JSON.parse(txt);
self.newContent({msgs:[json]});
}catch(e){
self.reportError(e);
}
| > | 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 |
payload: fd,
responseType: 'text',
onerror:function(err){
self.reportErrorAsMessage(err);
recoverFailedMessage(fallback);
},
onload:function(txt){
reportConnectionOkay('chat-send');
if(!txt) return/*success response*/;
try{
const json = JSON.parse(txt);
self.newContent({msgs:[json]});
}catch(e){
self.reportError(e);
}
|
| ︙ | ︙ | |||
2124 2125 2126 2127 2128 2129 2130 |
const v = Chat.inputValue();
Chat.inputValue('');
Chat.e.inputFields.$currentIndex = a[2];
Chat.inputValue(v);
D.removeClass(a[0], 'hidden');
D.addClass(a[1], 'hidden');
}
| | | 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 |
const v = Chat.inputValue();
Chat.inputValue('');
Chat.e.inputFields.$currentIndex = a[2];
Chat.inputValue(v);
D.removeClass(a[0], 'hidden');
D.addClass(a[1], 'hidden');
}
Chat.e.inputLineWrapper.classList[
s.value ? 'add' : 'remove'
]('compact');
Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
});
Chat.settings.addListener('edit-ctrl-send',function(s){
const label = (s.value ? "Ctrl-" : "")+"Enter submits message.";
Chat.e.inputFields.forEach((e)=>{
|
| ︙ | ︙ | |||
2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 |
fd.append('content', txt);
fd.append('filename','chat.md'
/*filename needed for mimetype determination*/);
fd.append('render_mode',F.page.previewModes.wiki);
F.fetch('ajax/preview-text',{
payload: fd,
onload: function(html){
Chat.setPreviewText(html);
F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr'));
},
onerror: function(e){
F.fetch.onerror(e);
Chat.setPreviewText("ERROR: "+(
e.message || 'Unknown error fetching preview!'
| > | 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 |
fd.append('content', txt);
fd.append('filename','chat.md'
/*filename needed for mimetype determination*/);
fd.append('render_mode',F.page.previewModes.wiki);
F.fetch('ajax/preview-text',{
payload: fd,
onload: function(html){
reportConnectionOkay('ajax/preview-text');
Chat.setPreviewText(html);
F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr'));
},
onerror: function(e){
F.fetch.onerror(e);
Chat.setPreviewText("ERROR: "+(
e.message || 'Unknown error fetching preview!'
|
| ︙ | ︙ | |||
2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 |
},
responseType: 'json',
onerror:function(err){
Chat.reportErrorAsMessage(err);
Chat._isBatchLoading = false;
},
onload:function(x){
let gotMessages = x.msgs.length;
newcontent(x,true);
Chat._isBatchLoading = false;
Chat.updateActiveUserList();
if(Chat._gotServerError){
Chat._gotServerError = false;
return;
| > | 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 |
},
responseType: 'json',
onerror:function(err){
Chat.reportErrorAsMessage(err);
Chat._isBatchLoading = false;
},
onload:function(x){
reportConnectionOkay('loadOldMessages()');
let gotMessages = x.msgs.length;
newcontent(x,true);
Chat._isBatchLoading = false;
Chat.updateActiveUserList();
if(Chat._gotServerError){
Chat._gotServerError = false;
return;
|
| ︙ | ︙ | |||
2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 |
payload: fd,
responseType: 'json',
onerror:function(err){
Chat.setCurrentView(Chat.e.viewMessages);
Chat.reportErrorAsMessage(err);
},
onload:function(jx){
let previd = 0;
D.clearElement(eMsgTgt);
jx.msgs.forEach((m)=>{
m.isSearchResult = true;
const mw = new Chat.MessageWidget(m);
const spacer = new Chat.SearchCtxLoader({
first: jx.first,
| > | 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 |
payload: fd,
responseType: 'json',
onerror:function(err){
Chat.setCurrentView(Chat.e.viewMessages);
Chat.reportErrorAsMessage(err);
},
onload:function(jx){
reportConnectionOkay('submitSearch()');
let previd = 0;
D.clearElement(eMsgTgt);
jx.msgs.forEach((m)=>{
m.isSearchResult = true;
const mw = new Chat.MessageWidget(m);
const spacer = new Chat.SearchCtxLoader({
first: jx.first,
|
| ︙ | ︙ | |||
2442 2443 2444 2445 2446 2447 2448 |
term );
}
}
}
);
}/*Chat.submitSearch()*/;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | < | > > | > | > | > | > > > > | < > > > > > > > | < | < < < > > > > > > > > > > > > | < > | < > > > > > > > > > | > > > > > > > > > > > | > > | > > > | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > | | | > > > > > > | > > | < | < > | | | | 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 |
term );
}
}
}
);
}/*Chat.submitSearch()*/;
/*
To be called from F.fetch('chat-poll') beforesend() handler. If
we're currently in delayed-retry mode and a connection is
started, try to reset the delay after N time waiting on that
connection. The fact that the connection is waiting to respond,
rather than outright failing, is a good hint that the outage is
over and we can reset the back-off timer.
Without this, recovery of a connection error won't be reported
until after the long-poll completes by either receiving new
messages or timing out. Once a long-poll is in progress, though,
we "know" that it's up and running again, so can update the UI and
connection timer to reflect that. That's the job this function
does.
Only one of these asynchronous checks will ever be active
concurrently and only if Chat.timer.isDelayed() is true. i.e. if
this timer is active or Chat.timer.isDelayed() is false, this is a
no-op.
*/
const chatPollBeforeSend = function(){
//console.warn('chatPollBeforeSend outer', Chat.timer.tidClearPollErr, Chat.timer.currentDelay);
if( !Chat.timer.tidClearPollErr && Chat.timer.isDelayed() ){
Chat.timer.tidClearPollErr = setTimeout(()=>{
//console.warn('chatPollBeforeSend inner');
Chat.timer.tidClearPollErr = 0;
if( poll.running ){
/* This chat-poll F.fetch() is still underway, so let's
assume the connection is back up until/unless it times
out or breaks again. */
reportConnectionOkay('chatPollBeforeSend', true);
}
}, Chat.timer.$initialDelay * 4/*kinda arbitrary: not too long for UI wait and
not too short as to make connection unlikely. */ );
}
};
/**
Deal with the last poll() response and maybe re-start poll().
*/
const afterPollFetch = function f(err){
if(true===f.isFirstCall){
f.isFirstCall = false;
Chat.ajaxEnd();
Chat.e.viewMessages.classList.remove('loading');
setTimeout(function(){
Chat.scrollMessagesTo(1);
}, 250);
}
Chat.timer.cancelPendingPollTimer();
if(Chat._gotServerError){
Chat.reportErrorAsMessage(
"Shutting down chat poller due to server-side error. ",
"Reload this page to reactivate it."
);
} else {
if( err && Chat.beVerbose ){
console.error("afterPollFetch:",err.name,err.status,err.message);
}
if( !err || 'timeout'===err.name/*(probably) long-poll expired*/ ){
/* Restart the poller immediately. */
reportConnectionOkay('afterPollFetch '+err, false);
}else{
/* Delay a while before trying again, noting that other Chat
APIs may try and succeed at connections before this timer
resolves, in which case they'll clear this timeout and the
UI message about the outage. */
let delay;
D.addClass(Chat.e.pollErrorMarker, 'connection-error');
if( ++Chat.timer.errCount < Chat.timer.minErrForNotify ){
delay = Chat.timer.resetDelay(
(Chat.timer.minDelay * Chat.timer.errCount)
+ Chat.timer.randomInterval(Chat.timer.minDelay)
);
if(Chat.beVerbose){
console.warn("Ignoring polling error #",Chat.timer.errCount,
"for another",delay,"ms" );
}
} else {
delay = Chat.timer.incrDelay();
//console.warn("afterPollFetch Chat.e.eMsgPollError",Chat.e.eMsgPollError);
const msg = "Poller connection error. Retrying in "+delay+ " ms.";
/* Replace the current/newest connection error widget. We could also
just update its body with the new message, but then its timestamp
never updates. OTOH, if we replace the message, we lose the
start time of the outage in the log. It seems more useful to
update the timestamp so that it doesn't look like it's hung. */
if( Chat.e.eMsgPollError ){
Chat.deleteMessageElem(Chat.e.eMsgPollError, false);
}
const theMsg = Chat.e.eMsgPollError = Chat.reportErrorAsMessage(msg);
D.addClass(Chat.e.eMsgPollError.e.body,'poller-connection');
/* Add a "retry now" button */
const btnDel = D.addClass(D.button("Retry now"), 'retry-now');
const eParent = Chat.e.eMsgPollError.e.content;
D.append(eParent, " ", btnDel);
btnDel.addEventListener('click', function(){
D.remove(btnDel);
D.append(eParent, D.text("retrying..."));
Chat.timer.cancelPendingPollTimer().currentDelay =
Chat.timer.resetDelay() +
1 /*workaround for showing the "connection restored"
message, as the +1 will cause
Chat.timer.isDelayed() to be true.*/;
poll();
});
//Chat.playNewMessageSound();// browser complains b/c this wasn't via human interaction
}
Chat.timer.startPendingPollTimer(delay);
}
}
};
afterPollFetch.isFirstCall = true;
/**
Initiates, if it's not already running, a single long-poll
request to the /chat-poll endpoint. In the handling of that
response, it end up will psuedo-recursively calling itself via
the response-handling process. Despite being async, the implied
returned Promise is meaningless.
*/
const poll = Chat.poll = async function f(){
if(f.running) return;
f.running = true;
Chat._isBatchLoading = f.isFirstCall;
if(true===f.isFirstCall){
f.isFirstCall = false;
f.pendingOnError = undefined;
Chat.ajaxStart();
Chat.e.viewMessages.classList.add('loading');
/*
We manager onerror() results in poll() in a roundabout
manner: when an onerror() arrives, we stash it aside
for a moment before processing it.
This level of indirection is necessary to be able to
unambiguously identify client-timeout-specific polling errors
from other errors. Timeouts are always announced in pairs of
an HTTP 0 and something we can unambiguously identify as a
timeout (in that order). When we receive an HTTP error we put
it into this queue. If an ontimeout() call arrives before
this error is handled, this error is ignored. If, however, an
HTTP error is seen without an accompanying timeout, we handle
it from here.
It's kinda like in the curses C API, where you to match
ALT-X by first getting an ESC event, then an X event, but
this one is a lot less explicable. (It's almost certainly a
mis-handling bug in F.fetch(), but it has so far eluded my
eyes.)
*/
f.delayPendingOnError = function(err){
if( f.pendingOnError ){
const x = f.pendingOnError;
f.pendingOnError = undefined;
afterPollFetch(x);
}
};
}
F.fetch("chat-poll",{
timeout: Chat.timer.pollTimeout,
urlParams:{
name: Chat.mxMsg
},
responseType: "json",
// Disable the ajax start/end handling for this long-polling op:
beforesend: chatPollBeforeSend,
aftersend: function(){
poll.running = false;
},
ontimeout: function(err){
f.pendingOnError = undefined /*strip preceeding non-timeout error, if any*/;
afterPollFetch(err);
},
onerror:function(err){
Chat._isBatchLoading = false;
if(Chat.beVerbose){
console.error("poll.onerror:",err.name,err.status,JSON.stringify(err));
}
f.pendingOnError = err;
setTimeout(f.delayPendingOnError, 100);
},
onload:function(y){
reportConnectionOkay('poll.onload', true);
newcontent(y);
if(Chat._isBatchLoading){
Chat._isBatchLoading = false;
Chat.updateActiveUserList();
}
afterPollFetch();
}
});
}/*poll()*/;
poll.isFirstCall = true;
Chat._gotServerError = poll.running = false;
if( window.fossil.config.chat.fromcli ){
Chat.chatOnlyMode(true);
}
Chat.timer.startPendingPollTimer();
delete ForceResizeKludge.$disabled;
ForceResizeKludge();
Chat.animate.$disabled = false;
setTimeout( ()=>Chat.inputFocus(), 0 );
F.page.chat = Chat/* enables testing the APIs via the dev tools */;
});
|
Changes to src/fossil.popupwidget.js.
| ︙ | ︙ | |||
284 285 286 287 288 289 290 |
);
return F.toast;
};
F.toast = {
config: {
position: { x: 5, y: 5 /*viewport-relative, pixels*/ },
| | | 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
);
return F.toast;
};
F.toast = {
config: {
position: { x: 5, y: 5 /*viewport-relative, pixels*/ },
displayTimeMs: 5000
},
/**
Convenience wrapper around a PopupWidget which pops up a shared
PopupWidget instance to show toast-style messages (commonly
seen on Android). Its arguments may be anything suitable for
passing to fossil.dom.append(), and each argument is first
append()ed to the toast widget, then the widget is shown for
|
| ︙ | ︙ |
Changes to src/graph.c.
| ︙ | ︙ | |||
119 120 121 122 123 124 125 |
int nRow; /* Number of rows */
int nHash; /* Number of slots in apHash[] */
u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
** rail that the node */
u8 bOverfull; /* Unable to allocate sufficient rails */
u64 mergeRail; /* Rails used for merge lines */
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
| | | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
int nRow; /* Number of rows */
int nHash; /* Number of slots in apHash[] */
u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
** rail that the node */
u8 bOverfull; /* Unable to allocate sufficient rails */
u64 mergeRail; /* Rails used for merge lines */
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */
};
#endif
/* The N-th bit */
#define BIT(N) (((u64)1)<<(N))
|
| ︙ | ︙ |
Changes to src/graph.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* This module contains javascript needed to render timeline graphs in Fossil.
**
** There can be multiple graphs on a single webpage, but this script is only
** loaded once.
**
** Prior to sourcing this script, there should be a separate
** <script type='application/json' id='timeline-data-NN'> for each graph,
** each containing JSON like this:
**
** { "iTableId": INTEGER, // Table sequence number (NN)
** "circleNodes": BOOLEAN, // True for circle nodes. False for squares
** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit
** "iRailPitch": INTEGER, // Spacing between vertical lines (px)
| < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* This module contains javascript needed to render timeline graphs in Fossil.
**
** There can be multiple graphs on a single webpage, but this script is only
** loaded once.
**
** Prior to sourcing this script, there should be a separate
** <script type='application/json' id='timeline-data-NN'> for each graph,
** each containing JSON like this:
**
** { "iTableId": INTEGER, // Table sequence number (NN)
** "circleNodes": BOOLEAN, // True for circle nodes. False for squares
** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit
** "iRailPitch": INTEGER, // Spacing between vertical lines (px)
** "nomo": BOOLEAN, // True to join merge lines with rails
** "iTopRow": INTEGER, // Index of top-most row in the graph
** "omitDescenders": BOOLEAN, // Omit ancestor lines off bottom of screen
** "fileDiff": BOOLEAN, // True for file diff. False for check-in
** "scrollToSelect": BOOLEAN, // Scroll to selection on first render
** "nrail": INTEGER, // Number of vertical "rails"
** "baseUrl": TEXT, // Top-level URL
|
| ︙ | ︙ |
Changes to src/http_ssl.c.
| ︙ | ︙ | |||
317 318 319 320 321 322 323 | ** for future versions of OpenSSL, and explicit initialization may be redundant. ** NOTE TO HACKERS TWEAKING THEIR OPENSSL CONFIGURATION: ** The following OpenSSL configuration options must not be used for this feature ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not ** currently set these options when building OpenSSL for Windows. */ #if defined(_WIN32) #if OPENSSL_VERSION_NUMBER >= 0x030200000 | > | > | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
** for future versions of OpenSSL, and explicit initialization may be redundant.
** NOTE TO HACKERS TWEAKING THEIR OPENSSL CONFIGURATION:
** The following OpenSSL configuration options must not be used for this feature
** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not
** currently set these options when building OpenSSL for Windows. */
#if defined(_WIN32)
#if OPENSSL_VERSION_NUMBER >= 0x030200000
if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */
&& SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0
){
fossil_print("NOTICE: Failed to load the Windows root certificates.\n");
}
#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
#endif /* _WIN32 */
/* Load client SSL identity, preferring the filename specified on the
** command line */
|
| ︙ | ︙ | |||
997 998 999 1000 1001 1002 1003 |
fossil_print("OpenSSL-version: (none)\n");
if( verbose ){
fossil_print("\n"
" The OpenSSL library is not used by this build of Fossil\n\n"
);
}
#else
| | | | 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
fossil_print("OpenSSL-version: (none)\n");
if( verbose ){
fossil_print("\n"
" The OpenSSL library is not used by this build of Fossil\n\n"
);
}
#else
fossil_print("OpenSSL-version: %s (0x%09llx)\n",
SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay());
if( verbose ){
fossil_print("\n"
" The version of the OpenSSL library being used\n"
" by this instance of Fossil. Version 3.0.0 or\n"
" later is recommended.\n\n"
);
}
|
| ︙ | ︙ | |||
1059 1060 1061 1062 1063 1064 1065 |
" the identity of servers for \"https:\" URLs. These values\n"
" come into play when Fossil is used as a TLS client. These\n"
" values are built into your OpenSSL library.\n\n"
);
}
#if defined(_WIN32)
| < | | < < > | | | | 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 |
" the identity of servers for \"https:\" URLs. These values\n"
" come into play when Fossil is used as a TLS client. These\n"
" values are built into your OpenSSL library.\n\n"
);
}
#if defined(_WIN32)
fossil_print(" OpenSSL-winstore: %s\n",
(SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No");
if( verbose ){
fossil_print("\n"
" OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n"
" are able to use the root certificates managed by the Windows\n"
" operating system. The installed root certificates are listed\n"
" by the command:\n\n"
" certutil -store \"ROOT\"\n\n"
);
}
#endif /* _WIN32 */
if( zUsed==0 ) zUsed = "";
fossil_print(" Trust store used: %s\n", zUsed);
|
| ︙ | ︙ | |||
1230 1231 1232 1233 1234 1235 1236 |
** Return the OpenSSL version number being used. Space to hold
** this name is obtained from fossil_malloc() and should be
** freed by the caller.
*/
char *fossil_openssl_version(void){
#if defined(FOSSIL_ENABLE_SSL)
return mprintf("%s (0x%09x)\n",
| | | 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 |
** Return the OpenSSL version number being used. Space to hold
** this name is obtained from fossil_malloc() and should be
** freed by the caller.
*/
char *fossil_openssl_version(void){
#if defined(FOSSIL_ENABLE_SSL)
return mprintf("%s (0x%09x)\n",
SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay());
#else
return mprintf("none");
#endif
}
|
Changes to src/info.c.
| ︙ | ︙ | |||
949 950 951 952 953 954 955 |
const char *zComment;
const char *zDate;
const char *zOrigDate;
int okWiki = 0;
Blob wiki_read_links = BLOB_INITIALIZER;
Blob wiki_add_links = BLOB_INITIALIZER;
| | | 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
const char *zComment;
const char *zDate;
const char *zOrigDate;
int okWiki = 0;
Blob wiki_read_links = BLOB_INITIALIZER;
Blob wiki_add_links = BLOB_INITIALIZER;
Th_StoreUnsafe("current_checkin", zName);
style_header("Check-in [%S]", zUuid);
login_anonymous_available();
zEUser = db_text(0,
"SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=%d AND tagtype>0",
TAG_USER, rid);
zEComment = db_text(0,
|
| ︙ | ︙ | |||
1180 1181 1182 1183 1184 1185 1186 |
}
if( diffType!=2 ){
@ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
@ Side-by-Side Diff</a>
}
if( diffType!=0 ){
if( *zW ){
| | | | 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 |
}
if( diffType!=2 ){
@ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
@ Side-by-Side Diff</a>
}
if( diffType!=0 ){
if( *zW ){
@ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
@ Show Whitespace Changes</a>
}else{
@ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
@ Ignore Whitespace</a>
}
}
if( zParent ){
@ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
@ Patch</a>
}
|
| ︙ | ︙ | |||
1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 |
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
diff_config_init(&DCfg, 0);
diffType = preferred_diff_type();
if( P("from") && P("to") ){
v1 = artifact_from_ci_and_filename("from");
v2 = artifact_from_ci_and_filename("to");
}else{
Stmt q;
v1 = name_to_rid_www("v1");
v2 = name_to_rid_www("v2");
/* If the two file versions being compared both have the same
** filename, then offer an "Annotate" link that constructs an
** annotation between those version. */
db_prepare(&q,
"SELECT (SELECT substr(uuid,1,20) FROM blob WHERE rid=a.mid),"
" (SELECT substr(uuid,1,20) FROM blob WHERE rid=b.mid),"
| > > | 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 |
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
diff_config_init(&DCfg, 0);
diffType = preferred_diff_type();
if( P("from") && P("to") ){
v1 = artifact_from_ci_and_filename("from");
v2 = artifact_from_ci_and_filename("to");
if( v1==0 || v2==0 ) fossil_redirect_home();
}else{
Stmt q;
v1 = name_to_rid_www("v1");
v2 = name_to_rid_www("v2");
if( v1==0 || v2==0 ) fossil_redirect_home();
/* If the two file versions being compared both have the same
** filename, then offer an "Annotate" link that constructs an
** annotation between those version. */
db_prepare(&q,
"SELECT (SELECT substr(uuid,1,20) FROM blob WHERE rid=a.mid),"
" (SELECT substr(uuid,1,20) FROM blob WHERE rid=b.mid),"
|
| ︙ | ︙ | |||
2001 2002 2003 2004 2005 2006 2007 |
const char *zFN = db_column_text(&q, 2);
style_submenu_element("Annotate",
"%R/annotate?origin=%s&checkin=%s&filename=%T",
zOrig, zCkin, zFN);
}
db_finalize(&q);
}
| < | 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 |
const char *zFN = db_column_text(&q, 2);
style_submenu_element("Annotate",
"%R/annotate?origin=%s&checkin=%s&filename=%T",
zOrig, zCkin, zFN);
}
db_finalize(&q);
}
zRe = P("regex");
cgi_check_for_malice();
if( zRe ) re_compile(&pRe, zRe, 0);
if( verbose ) objdescFlags |= OBJDESC_DETAIL;
if( isPatch ){
Blob c1, c2, *pOut;
DiffConfig DCfg;
|
| ︙ | ︙ | |||
2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 |
fossil_print("%b\n", cgi_output_blob());
}
/*
** WEBPAGE: artifact
** WEBPAGE: file
** WEBPAGE: whatis
**
** Typical usage:
**
** /artifact/HASH
** /whatis/HASH
** /file/NAME
**
** Additional query parameters:
**
** ln - show line numbers
** ln=N - highlight line number N
** ln=M-N - highlight lines M through N inclusive
** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
** verbose - show more detail in the description
** download - redirect to the download (artifact page only)
** name=NAME - filename or hash as a query parameter
** filename=NAME - alternative spelling for "name="
** fn=NAME - alternative spelling for "name="
** ci=VERSION - The specific check-in to use with "name=" to
** identify the file.
** txt - Force display of unformatted source text
| > > > > | 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 |
fossil_print("%b\n", cgi_output_blob());
}
/*
** WEBPAGE: artifact
** WEBPAGE: file
** WEBPAGE: whatis
** WEBPAGE: docfile
**
** Typical usage:
**
** /artifact/HASH
** /whatis/HASH
** /file/NAME
** /docfile/NAME
**
** Additional query parameters:
**
** ln - show line numbers
** ln=N - highlight line number N
** ln=M-N - highlight lines M through N inclusive
** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
** verbose - show more detail in the description
** brief - show just the document, not the metadata. The
** /docfile page is an alias for /file?brief
** download - redirect to the download (artifact page only)
** name=NAME - filename or hash as a query parameter
** filename=NAME - alternative spelling for "name="
** fn=NAME - alternative spelling for "name="
** ci=VERSION - The specific check-in to use with "name=" to
** identify the file.
** txt - Force display of unformatted source text
|
| ︙ | ︙ | |||
2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 |
int renderAsSvg = 0;
int objType;
int asText;
const char *zUuid = 0;
u32 objdescFlags = OBJDESC_BASE;
int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
int hashOnly = P("hash")!=0;
int isFile = fossil_strcmp(g.zPath,"file")==0;
const char *zLn = P("ln");
const char *zName = P("name");
const char *zCI = P("ci");
HQuery url;
char *zCIUuid = 0;
int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
int isBranchCI = 0; /* ci= refers to a branch name */
char *zHeader = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cgi_check_for_malice();
style_set_current_feature("artifact");
/* Capture and normalize the name= and ci= query parameters */
if( zName==0 ){
zName = P("filename");
if( zName==0 ){
zName = P("fn");
}
| > > > > > | 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 |
int renderAsSvg = 0;
int objType;
int asText;
const char *zUuid = 0;
u32 objdescFlags = OBJDESC_BASE;
int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
int hashOnly = P("hash")!=0;
int docOnly = P("brief")!=0;
int isFile = fossil_strcmp(g.zPath,"file")==0;
const char *zLn = P("ln");
const char *zName = P("name");
const char *zCI = P("ci");
HQuery url;
char *zCIUuid = 0;
int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
int isBranchCI = 0; /* ci= refers to a branch name */
char *zHeader = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cgi_check_for_malice();
style_set_current_feature("artifact");
if( fossil_strcmp(g.zPath, "docfile")==0 ){
isFile = 1;
docOnly = 1;
}
/* Capture and normalize the name= and ci= query parameters */
if( zName==0 ){
zName = P("filename");
if( zName==0 ){
zName = P("fn");
}
|
| ︙ | ︙ | |||
2800 2801 2802 2803 2804 2805 2806 |
cgi_set_content_type("text/plain");
cgi_set_content(&uuid);
return;
}
asText = P("txt")!=0;
if( isFile ){
| > > | | 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 |
cgi_set_content_type("text/plain");
cgi_set_content(&uuid);
return;
}
asText = P("txt")!=0;
if( isFile ){
if( docOnly ){
/* No header */
}else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
zCI = "tip";
@ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a>
@ from the %z(href("%R/info/tip"))latest check-in</a></h2>
}else{
const char *zPath;
Blob path;
blob_zero(&path);
|
| ︙ | ︙ | |||
2822 2823 2824 2825 2826 2827 2828 |
}else if( isSymbolicCI ){
@ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
}else{
@ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
}
blob_reset(&path);
}
| < > > | | | | | > | 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 |
}else if( isSymbolicCI ){
@ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
}else{
@ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
}
blob_reset(&path);
}
zMime = mimetype_from_name(zName);
if( !docOnly ){
style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
zName, zCI);
style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
zName, zCI);
style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
}
blob_init(&downloadName, zName, -1);
objType = OBJTYPE_CONTENT;
}else{
@ <h2>Artifact
style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
if( g.perm.Setup ){
@ (%d(rid)):</h2>
|
| ︙ | ︙ | |||
2851 2852 2853 2854 2855 2856 2857 |
}
if( !descOnly && P("download")!=0 ){
cgi_redirectf("%R/raw/%s?at=%T",
db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
file_tail(blob_str(&downloadName)));
/*NOTREACHED*/
}
| | | 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 |
}
if( !descOnly && P("download")!=0 ){
cgi_redirectf("%R/raw/%s?at=%T",
db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
file_tail(blob_str(&downloadName)));
/*NOTREACHED*/
}
if( g.perm.Admin && !docOnly ){
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid);
}else{
style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid);
}
}
|
| ︙ | ︙ | |||
2896 2897 2898 2899 2900 2901 2902 |
const char *zUser = db_column_text(&q,0);
const char *zDate = db_column_text(&q,1);
const char *zIp = db_column_text(&q,2);
@ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
}
db_finalize(&q);
}
| > | | | > > | > > | > > | | | > | > | > | 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 |
const char *zUser = db_column_text(&q,0);
const char *zDate = db_column_text(&q,1);
const char *zIp = db_column_text(&q,2);
@ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
}
db_finalize(&q);
}
if( !docOnly ){
style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName));
if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
}
}
if( zMime ){
if( fossil_strcmp(zMime, "text/html")==0 ){
if( asText ){
style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0));
}else{
renderAsHtml = 1;
if( !docOnly ){
style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
}
}
}else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
|| fossil_strcmp(zMime, "text/x-markdown")==0
|| fossil_strcmp(zMime, "text/x-pikchr")==0 ){
if( asText ){
style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki",
"%s", url_render(&url, "txt", 0, 0, 0));
}else{
renderAsWiki = 1;
if( !docOnly ){
style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
}
}
}else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){
if( asText ){
style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0));
}else{
renderAsSvg = 1;
if( !docOnly ){
style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
}
}
}
if( !docOnly && fileedit_is_editable(zName) ){
style_submenu_element("Edit",
"%R/fileedit?filename=%T&checkin=%!S",
zName, zCI);
}
}
if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
style_submenu_element("Parsed", "%R/info/%s", zUuid);
}
if( descOnly ){
style_submenu_element("Content", "%R/artifact/%s", zUuid);
}else{
if( !docOnly || !isFile ){
@ <hr>
}
content_get(rid, &content);
if( renderAsWiki ){
safe_html_context(DOCSRC_FILE);
wiki_render_by_mimetype(&content, zMime);
document_emit_js();
}else if( renderAsHtml ){
@ <iframe src="%R/raw/%s(zUuid)"
|
| ︙ | ︙ | |||
3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 |
rid, TAG_BGCOLOR)==2;
fNewPropagateColor = P("clr")!=0 ? P("pclr")!=0 : fPropagateColor;
zNewColorFlag = P("newclr") ? " checked" : "";
zNewTagFlag = P("newtag") ? " checked" : "";
zNewTag = PDT("tagname","");
zNewBrFlag = P("newbr") ? " checked" : "";
zNewBranch = PDT("brname","");
zCloseFlag = P("close") ? " checked" : "";
zHideFlag = P("hide") ? " checked" : "";
if( P("apply") && cgi_csrf_safe(2) ){
Blob ctrl;
char *zNow;
blob_zero(&ctrl);
| > | 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 |
rid, TAG_BGCOLOR)==2;
fNewPropagateColor = P("clr")!=0 ? P("pclr")!=0 : fPropagateColor;
zNewColorFlag = P("newclr") ? " checked" : "";
zNewTagFlag = P("newtag") ? " checked" : "";
zNewTag = PDT("tagname","");
zNewBrFlag = P("newbr") ? " checked" : "";
zNewBranch = PDT("brname","");
zBranchName = branch_of_rid(rid);
zCloseFlag = P("close") ? " checked" : "";
zHideFlag = P("hide") ? " checked" : "";
if( P("apply") && cgi_csrf_safe(2) ){
Blob ctrl;
char *zNow;
blob_zero(&ctrl);
|
| ︙ | ︙ | |||
3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 |
blob_zero(&comment);
blob_append(&comment, zNewComment, -1);
zUuid[10] = 0;
style_header("Edit Check-in [%s]", zUuid);
if( P("preview") ){
Blob suffix;
int nTag = 0;
@ <b>Preview:</b>
@ <blockquote>
@ <table border=0>
if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
| > > > > > > | > > | | 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 |
blob_zero(&comment);
blob_append(&comment, zNewComment, -1);
zUuid[10] = 0;
style_header("Edit Check-in [%s]", zUuid);
if( P("preview") ){
Blob suffix;
int nTag = 0;
const char *zDplyBr; /* Branch name used to determine BG color */
if( zNewBrFlag[0] && zNewBranch[0] ){
zDplyBr = zNewBranch;
}else{
zDplyBr = zBranchName;
}
@ <b>Preview:</b>
@ <blockquote>
@ <table border=0>
if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
@ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));">
}else if( zColor[0] ){
@ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));">
}else if( zDplyBr && fossil_strcmp(zDplyBr,"trunk")!=0 ){
@ <tr><td style="background-color:%h(hash_color(zDplyBr));">
}else{
@ <tr><td>
}
@ %!W(blob_str(&comment))
blob_zero(&suffix);
blob_appendf(&suffix, "(user: %h", zNewUser);
db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag"
|
| ︙ | ︙ | |||
3746 3747 3748 3749 3750 3751 3752 | @ </td></tr> @ <tr><th align="right" valign="top">Tags:</th> @ <td valign="top"> @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)> @ Add the following new tag name to this check-in:</label> @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)"> | < < < | 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 |
@ </td></tr>
@ <tr><th align="right" valign="top">Tags:</th>
@ <td valign="top">
@ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)>
@ Add the following new tag name to this check-in:</label>
@ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)">
db_prepare(&q,
"SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
" ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)"
" ELSE tagname END /*sort*/",
rid
);
|
| ︙ | ︙ |
Changes to src/loadctrl.c.
| ︙ | ︙ | |||
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
** %fossil test-loadavg
**
** Print the load average on the host machine.
*/
void loadavg_test_cmd(void){
fossil_print("load-average: %f\n", load_average());
}
/*
** Abort the current page request if the load average of the host
** computer is too high. Admin and Setup users are exempt from this
** restriction.
*/
void load_control(void){
double mxLoad = atof(db_get("max-loadavg", "0.0"));
#if 1
/* Disable this block only to test load restrictions */
if( mxLoad<=0.0 || mxLoad>=load_average() ) return;
login_check_credentials();
if(g.perm.Admin || g.perm.Setup){
return;
}
#endif
| > > > > > > > > > > > > > > > > > > > > < < < < < < < | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
** %fossil test-loadavg
**
** Print the load average on the host machine.
*/
void loadavg_test_cmd(void){
fossil_print("load-average: %f\n", load_average());
}
/*
** WEBPAGE: test-overload
**
** Generate the response that would normally be shown only when
** service is denied due to an overload condition. This is for
** testing of the overload warning page.
*/
void overload_page(void){
double mxLoad = atof(db_get("max-loadavg", "0.0"));
style_set_current_feature("test");
style_header("Server Overload");
@ <h2>The server load is currently too high.
@ Please try again later.</h2>
@ <p>Current load average: %f(load_average())<br>
@ Load average limit: %f(mxLoad)<br>
@ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br>
@ Timestamp: %h(db_text("","SELECT datetime()"))Z</p>
style_finish_page();
}
/*
** Abort the current page request if the load average of the host
** computer is too high. Admin and Setup users are exempt from this
** restriction.
*/
void load_control(void){
double mxLoad = atof(db_get("max-loadavg", "0.0"));
#if 1
/* Disable this block only to test load restrictions */
if( mxLoad<=0.0 || mxLoad>=load_average() ) return;
login_check_credentials();
if(g.perm.Admin || g.perm.Setup){
return;
}
#endif
overload_page();
cgi_set_status(503,"Server Overload");
cgi_reply();
exit(0);
}
|
Changes to src/login.c.
| ︙ | ︙ | |||
1297 1298 1299 1300 1301 1302 1303 |
**
** robot-restrict The value is a list of GLOB patterns for pages
** that should restrict robot access. No restrictions
** are applied if this setting is undefined or is
** an empty string.
*/
void login_restrict_robot_access(void){
| < > > > > > > > > > > > > > > > > > | 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 |
**
** robot-restrict The value is a list of GLOB patterns for pages
** that should restrict robot access. No restrictions
** are applied if this setting is undefined or is
** an empty string.
*/
void login_restrict_robot_access(void){
const char *zGlob;
int isMatch = 1;
int nQP; /* Number of query parameters other than name= */
if( g.zLogin!=0 ) return;
zGlob = db_get("robot-restrict",0);
if( zGlob==0 || zGlob[0]==0 ) return;
if( g.isHuman ){
const char *zReferer;
const char *zAccept;
const char *zBr;
zReferer = P("HTTP_REFERER");
if( zReferer && zReferer[0]!=0 ) return;
/* Robots typically do not accept the brotli encoding, at least not
** at the time of this writing (2025-04-01), but standard web-browser
** all generally do accept brotli. So if brotli is accepted,
** assume we are not talking to a robot. We might want to revisit this
** heuristic in the future...
*/
if( (zAccept = P("HTTP_ACCEPT_ENCODING"))!=0
&& (zBr = strstr(zAccept,"br"))!=0
&& !fossil_isalnum(zBr[2])
&& (zBr==zAccept || !fossil_isalnum(zBr[-1]))
){
return;
}
}
nQP = cgi_qp_count();
if( nQP<1 ) return;
isMatch = glob_multi_match(zGlob, g.zPath);
if( !isMatch ) return;
/* Check for exceptions to the restriction on the number of query
|
| ︙ | ︙ | |||
1398 1399 1400 1401 1402 1403 1404 |
** This feature allows the "fossil ui" command to give the user
** full access rights without having to log in.
*/
zIpAddr = PD("REMOTE_ADDR","nil");
if( ( cgi_is_loopback(zIpAddr)
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
&& g.useLocalauth
| | | 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 |
** This feature allows the "fossil ui" command to give the user
** full access rights without having to log in.
*/
zIpAddr = PD("REMOTE_ADDR","nil");
if( ( cgi_is_loopback(zIpAddr)
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
&& g.useLocalauth
&& db_get_boolean("localauth",0)==0
&& P("HTTPS")==0
){
char *zSeed;
if( g.localOpen ) zLogin = db_lget("default-user",0);
if( zLogin!=0 ){
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
}else{
|
| ︙ | ︙ |
Changes to src/lookslike.c.
| ︙ | ︙ | |||
476 477 478 479 480 481 482 |
if( strchr("(_", z[i+n])!=0 ) return 0;
return 1;
}
/*
** Returns true if the given text contains certain keywords or
** punctuation which indicate that it might be an SQL injection attempt
| | | | | > | > > | | | | | | | | > > > > > > > > > | | | | | | | | | 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 |
if( strchr("(_", z[i+n])!=0 ) return 0;
return 1;
}
/*
** Returns true if the given text contains certain keywords or
** punctuation which indicate that it might be an SQL injection attempt
** or Cross-site scripting attempt or some other kind of mischief.
**
** This is not a primary defense against vulnerabilities in the Fossil
** code. Rather, this is part of an effort to do early detection of malicious
** spiders to avoid them using up too many CPU cycles. Or, this routine
** can also be thought of as a secondary layer of defense against attacks.
*/
int looks_like_attack(const char *zTxt){
unsigned int i;
int rc = 0;
if( zTxt==0 ) return 0;
for(i=0; zTxt[i]; i++){
switch( zTxt[i] ){
case '<':
case ';':
case '\'':
return 1;
case '/': /* 0123456789 123456789 */
if( strncmp(zTxt+i+1, "/wp-content/plugins/", 20)==0 ) rc = 1;
if( strncmp(zTxt+i+1, "/wp-admin/admin-ajax", 20)==0 ) rc = 1;
break;
case 'a':
case 'A':
if( isWholeWord(zTxt, i, "and", 3) ) rc = 1;
break;
case 'n':
case 'N':
if( isWholeWord(zTxt, i, "null", 4) ) rc = 1;
break;
case 'o':
case 'O':
if( isWholeWord(zTxt, i, "order", 5) && fossil_isspace(zTxt[i+5]) ){
rc = 1;
}
if( isWholeWord(zTxt, i, "or", 2) ) rc = 1;
break;
case 's':
case 'S':
if( isWholeWord(zTxt, i, "select", 6) ) rc = 1;
break;
case 'w':
case 'W':
if( isWholeWord(zTxt, i, "waitfor", 7) ) rc = 1;
break;
}
}
if( rc ){
/* The test/markdown-test3.md document which is part of the Fossil source
** tree intentionally tries to fake an attack. Do not report such
** errors. */
const char *zPathInfo = P("PATH_INFO");
if( sqlite3_strglob("/doc/*/test/markdown-test3.md", zPathInfo)==0 ){
rc = 0;
}
}
return rc;
}
/*
** This is a utility routine associated with the test-looks-like-sql-injection
** command.
**
** Read input from zInFile and print only those lines that look like they
** might be SQL injection.
**
** Or if bInvert is true, then show the opposite - those lines that do NOT
** look like SQL injection.
*/
static void show_attack_lines(
const char *zInFile, /* Name of input file */
int bInvert, /* Invert the sense of the output (-v) */
int bDeHttpize /* De-httpize the inputs. (-d) */
){
FILE *in;
char zLine[10000];
if( zInFile==0 || strcmp(zInFile,"-")==0 ){
in = stdin;
}else{
in = fopen(zInFile, "rb");
if( in==0 ){
fossil_fatal("cannot open \"%s\" for reading\n", zInFile);
}
}
while( fgets(zLine, sizeof(zLine), in) ){
dehttpize(zLine);
if( (looks_like_attack(zLine)!=0) ^ bInvert ){
fossil_print("%s", zLine);
}
}
if( in!=stdin ) fclose(in);
}
/*
** COMMAND: test-looks-like-attack
**
** Read lines of input from files named as arguments (or from standard
** input if no arguments are provided) and print those that look like they
** might be part of an SQL injection attack.
**
** Used to test the looks_lile_attack() utility subroutine, possibly
** by piping in actual server log data.
*/
void test_looks_like_attack(void){
int i;
int bInvert = find_option("invert","v",0)!=0;
int bDeHttpize = find_option("dehttpize","d",0)!=0;
verify_all_options();
if( g.argc==2 ){
show_attack_lines(0, bInvert, bDeHttpize);
}
for(i=2; i<g.argc; i++){
show_attack_lines(g.argv[i], bInvert, bDeHttpize);
}
}
|
Changes to src/main.c.
| ︙ | ︙ | |||
2051 2052 2053 2054 2055 2056 2057 |
/* Use the first element of PATH_INFO as the page name
** and deliver the appropriate page back to the user.
*/
set_base_url(0);
if( fossil_redirect_to_https_if_needed(2) ) return;
if( zPathInfo==0 || zPathInfo[0]==0
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
| | > | 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 |
/* Use the first element of PATH_INFO as the page name
** and deliver the appropriate page back to the user.
*/
set_base_url(0);
if( fossil_redirect_to_https_if_needed(2) ) return;
if( zPathInfo==0 || zPathInfo[0]==0
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
/* Second special case: If the PATH_INFO is blank, issue a
** temporary 302 redirect:
** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set.
** (2) to the home page identified by the "index-page" setting
** in the repository CONFIG table
** (3) to "/index" if there no "index-page" setting in CONFIG
*/
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
|
| ︙ | ︙ | |||
2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 |
** website we have an CGI at http://fossil.com/index.html (note
** ".com" instead of ".org") that looks like this:
**
** #!/usr/bin/fossil
** redirect: * https://fossil-scm.org/home
**
** Thus requests to the .com website redirect to the .org website.
*/
static void redirect_web_page(int nRedirect, char **azRedirect){
int i; /* Loop counter */
const char *zNotFound = 0; /* Not found URL */
const char *zName = P("name");
set_base_url(0);
if( zName==0 ){
| > > > > > > > > > > > | 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 |
** website we have an CGI at http://fossil.com/index.html (note
** ".com" instead of ".org") that looks like this:
**
** #!/usr/bin/fossil
** redirect: * https://fossil-scm.org/home
**
** Thus requests to the .com website redirect to the .org website.
** This form uses a 301 Permanent redirect.
**
** On a "*" redirect, the PATH_INFO and QUERY_STRING of the query
** that provoked the redirect are appended to the target. So, for
** example, if the input URL for the redirect above were
** "http://www.fossil.com/index.html/timeline?c=20250404", then
** the redirect would be to:
**
** https://fossil-scm.org/home/timeline?c=20250404
** ^^^^^^^^^^^^^^^^^^^^
** Copied from input URL
*/
static void redirect_web_page(int nRedirect, char **azRedirect){
int i; /* Loop counter */
const char *zNotFound = 0; /* Not found URL */
const char *zName = P("name");
set_base_url(0);
if( zName==0 ){
|
| ︙ | ︙ | |||
2295 2296 2297 2298 2299 2300 2301 |
}
}
}
if( zNotFound ){
Blob to;
const char *z;
if( strstr(zNotFound, "%s") ){
| | > | | | 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 |
}
}
}
if( zNotFound ){
Blob to;
const char *z;
if( strstr(zNotFound, "%s") ){
char *zTarget = mprintf(zNotFound /*works-like:"%s"*/, zName);
cgi_redirect_perm(zTarget);
}
if( strchr(zNotFound, '?') ){
cgi_redirect_perm(zNotFound);
}
blob_init(&to, zNotFound, -1);
z = P("PATH_INFO");
if( z && z[0]=='/' ) blob_append(&to, z, -1);
z = P("QUERY_STRING");
if( z && z[0]!=0 ) blob_appendf(&to, "?%s", z);
cgi_redirect_perm(blob_str(&to));
}else{
@ <html>
@ <head><title>No Such Object</title></head>
@ <body>
@ <p>No such object: <b>%h(zName)</b></p>
@ </body>
cgi_reply();
|
| ︙ | ︙ | |||
2350 2351 2352 2353 2354 2355 2356 | ** or "directory:" ** ** notfound: URL When in "directory:" mode, redirect to ** URL if no suitable repository is found. ** ** repolist When in "directory:" mode, display a page ** showing a list of available repositories if | | > > > > > > > | 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 | ** or "directory:" ** ** notfound: URL When in "directory:" mode, redirect to ** URL if no suitable repository is found. ** ** repolist When in "directory:" mode, display a page ** showing a list of available repositories if ** the URL is "/". Some control over the display ** is accomplished using environment variables. ** FOSSIL_REPOLIST_TITLE is the tital of the page. ** FOSSIL_REPOLIST_SHOW cause the "Description" ** column to display if it contains "description" as ** as a substring, and causes the Login-Group column ** to display if it contains the "login-group" ** substring. ** ** localauth Grant administrator privileges to connections ** from 127.0.0.1 or ::1. ** ** nossl Signal that no SSL connections are available. ** ** nocompress Do not compress HTTP replies. |
| ︙ | ︙ | |||
2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 | ** ** redirect: REPO URL Extract the "name" query parameter and search ** REPO for a check-in or ticket that matches the ** value of "name", then redirect to URL. There ** can be multiple "redirect:" lines that are ** processed in order. If the REPO is "*", then ** an unconditional redirect to URL is taken. ** ** jsmode: VALUE Specifies the delivery mode for JavaScript ** files. See the help text for the --jsmode ** flag of the http command. ** ** mainmenu: FILE Override the mainmenu config setting with the ** contents of the given file. | > > > | 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 | ** ** redirect: REPO URL Extract the "name" query parameter and search ** REPO for a check-in or ticket that matches the ** value of "name", then redirect to URL. There ** can be multiple "redirect:" lines that are ** processed in order. If the REPO is "*", then ** an unconditional redirect to URL is taken. ** When "*" is used a 301 permanent redirect is ** issued and the tail and query string from the ** original query are appeneded onto URL. ** ** jsmode: VALUE Specifies the delivery mode for JavaScript ** files. See the help text for the --jsmode ** flag of the http command. ** ** mainmenu: FILE Override the mainmenu config setting with the ** contents of the given file. |
| ︙ | ︙ | |||
3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 | ** and the SSH_CONNECTION environment variable is set. Use the --test ** option on interactive sessions to avoid that special processing when ** using this command interactively over SSH. A better solution would be ** to use a different command for "ssh" sync, but we cannot do that without ** breaking legacy. ** ** Options: ** --nobody Pretend to be user "nobody" ** --test Do not do special "sync" processing when operating ** over an SSH link ** --th-trace Trace TH1 execution (for debugging purposes) ** --usercap CAP User capability string (Default: "sxy") | > < > > | 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 |
** and the SSH_CONNECTION environment variable is set. Use the --test
** option on interactive sessions to avoid that special processing when
** using this command interactively over SSH. A better solution would be
** to use a different command for "ssh" sync, but we cannot do that without
** breaking legacy.
**
** Options:
** --csrf-safe N Set cgi_csrf_safe() to to return N
** --nobody Pretend to be user "nobody"
** --test Do not do special "sync" processing when operating
** over an SSH link
** --th-trace Trace TH1 execution (for debugging purposes)
** --usercap CAP User capability string (Default: "sxy")
*/
void cmd_test_http(void){
const char *zIpAddr; /* IP address of remote client */
const char *zUserCap;
int bTest = 0;
const char *zCsrfSafe = find_option("csrf-safe",0,1);
Th_InitTraceLog();
if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe);
zUserCap = find_option("usercap",0,1);
if( !find_option("nobody",0,0) ){
if( zUserCap==0 ){
g.useLocalauth = 1;
zUserCap = "sxy";
}
login_set_capabilities(zUserCap, 0);
|
| ︙ | ︙ | |||
3503 3504 3505 3506 3507 3508 3509 |
if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
ssh_add_path_argument(&ssh);
}
blob_append_escaped_arg(&ssh, "fossil", 1);
}else{
blob_appendf(&ssh, " %$", zFossilCmd);
}
| | > | 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 |
if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
ssh_add_path_argument(&ssh);
}
blob_append_escaped_arg(&ssh, "fossil", 1);
}else{
blob_appendf(&ssh, " %$", zFossilCmd);
}
blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d",
iPort);
if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
if( zExtPage ){
if( !file_is_absolute_path(zExtPage) ){
zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
}
|
| ︙ | ︙ | |||
3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 |
** case=1 Issue a fossil_warning() while generating the page.
** case=2 Extra db_begin_transaction()
** case=3 Extra db_end_transaction()
** case=4 Error during SQL processing
** case=5 Call the segfault handler
** case=6 Call webpage_assert()
** case=7 Call webpage_error()
*/
void test_warning_page(void){
int iCase = atoi(PD("case","0"));
int i;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_set_current_feature("test");
style_header("Warning Test Page");
style_submenu_element("Error Log","%R/errorlog");
| > > > | > | | < < < | | 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 |
** case=1 Issue a fossil_warning() while generating the page.
** case=2 Extra db_begin_transaction()
** case=3 Extra db_end_transaction()
** case=4 Error during SQL processing
** case=5 Call the segfault handler
** case=6 Call webpage_assert()
** case=7 Call webpage_error()
** case=8 Simulate a timeout
** case=9 Simulate a TH1 XSS vulnerability
** case=10 Simulate a TH1 SQL-injection vulnerability
*/
void test_warning_page(void){
int iCase = atoi(PD("case","0"));
int i;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_set_current_feature("test");
style_header("Warning Test Page");
style_submenu_element("Error Log","%R/errorlog");
@ <p>This page will generate various kinds of errors to test Fossil's
@ reaction. Depending on settings, a message might be written
@ into the <a href="%R/errorlog">error log</a>. Click on
@ one of the following hyperlinks to generate a simulated error:
for(i=1; i<=10; i++){
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
}
@ </p>
@ <p><ol>
@ <li value='1'> Call fossil_warning()
if( iCase==1 ){
fossil_warning("Test warning message from /test-warning");
|
| ︙ | ︙ | |||
3741 3742 3743 3744 3745 3746 3747 |
if( iCase==5 ){
sigsegv_handler(0);
}
@ <li value='6'> call webpage_assert(0)
if( iCase==6 ){
webpage_assert( 5==7 );
}
| | | > > > > > > > > > > > > > > > > > > > | 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 |
if( iCase==5 ){
sigsegv_handler(0);
}
@ <li value='6'> call webpage_assert(0)
if( iCase==6 ){
webpage_assert( 5==7 );
}
@ <li value='7'> call webpage_error()
if( iCase==7 ){
cgi_reset_content();
webpage_error("Case 7 from /test-warning");
}
@ <li value='8'> simulated timeout
if( iCase==8 ){
fossil_set_timeout(1);
cgi_reset_content();
sqlite3_sleep(1100);
}
@ <li value='9'> simulated TH1 XSS vulnerability
@ <li value='10'> simulated TH1 SQL-injection vulnerability
if( iCase==9 || iCase==10 ){
const char *zR;
int n, rc;
static const char *zTH1[] = {
/* case 9 */ "html [taint {<b>XSS</b>}]",
/* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
" html \"<b>[htmlize $msg]</b>\"\n"
"}"
};
rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
zR = Th_GetResult(g.interp, &n);
if( rc==TH_OK ){
@ <pre class="th1result">%h(zR)</pre>
}else{
@ <pre class="th1error">%h(zR)</pre>
}
}
@ </ol>
@ <p>End of test</p>
style_finish_page();
}
|
Changes to src/manifest.c.
| ︙ | ︙ | |||
2925 2926 2927 2928 2929 2930 2931 |
switch(typeId){
case CFTYPE_MANIFEST: return "checkin";
case CFTYPE_CLUSTER: return "cluster";
case CFTYPE_CONTROL: return "tag";
case CFTYPE_WIKI: return "wiki";
case CFTYPE_TICKET: return "ticket";
case CFTYPE_ATTACHMENT: return "attachment";
| | | | | | | | > > | | > > | > | < | | | < | | > > | > > | 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 |
switch(typeId){
case CFTYPE_MANIFEST: return "checkin";
case CFTYPE_CLUSTER: return "cluster";
case CFTYPE_CONTROL: return "tag";
case CFTYPE_WIKI: return "wiki";
case CFTYPE_TICKET: return "ticket";
case CFTYPE_ATTACHMENT: return "attachment";
case CFTYPE_EVENT: return "technote";
case CFTYPE_FORUM: return "forumpost";
}
return NULL;
}
/*
** Creates a JSON representation of p, appending it to b.
**
** b is not cleared before rendering, so the caller needs to do that
** if it's important for their use case.
**
** Pedantic note: this routine traverses p->aFile directly, rather
** than using manifest_file_next(), so that delta manifests are
** rendered as-is instead of containing their derived F-cards. If that
** policy is ever changed, p will need to be non-const.
*/
void artifact_to_json(Manifest const *p, Blob *b){
int i;
blob_append_literal(b, "{");
blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid));
/*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type));
#define ISA(TYPE) if( p->type==TYPE )
#define CARD_LETTER(LETTER) \
blob_append_literal(b, ",\"" #LETTER "\":")
#define CARD_STR(LETTER, VAL) \
assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
#define CARD_STR2(LETTER, VAL) \
if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
#define STR_OR_NULL(VAL) \
if( VAL ) blob_appendf(b, "%!j", VAL); \
else blob_append(b, "null", 4)
#define KVP_STR(ADDCOMMA, KEY,VAL) \
if(ADDCOMMA) blob_append_char(b, ','); \
blob_appendf(b, "%!j:", #KEY); \
STR_OR_NULL(VAL)
ISA( CFTYPE_ATTACHMENT ){
CARD_LETTER(A);
blob_append_char(b, '{');
KVP_STR(0, filename, p->zAttachName);
KVP_STR(1, target, p->zAttachTarget);
KVP_STR(1, source, p->zAttachSrc);
blob_append_char(b, '}');
}
CARD_STR2(B, p->zBaseline);
CARD_STR2(C, p->zComment);
CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
ISA( CFTYPE_EVENT ){
blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}",
p->rEventDate, p->zEventId);
}
ISA( CFTYPE_MANIFEST ){
CARD_LETTER(F);
blob_append_char(b, '[');
for( i = 0; i < p->nFile; ++i ){
ManifestFile const * const pF = &p->aFile[i];
if( i>0 ) blob_append_char(b, ',');
blob_append_char(b, '{');
KVP_STR(0, name, pF->zName);
KVP_STR(1, uuid, pF->zUuid);
KVP_STR(1, perm, pF->zPerm);
KVP_STR(1, rename, pF->zPrior);
blob_append_char(b, '}');
}
/* Special case: model checkins with no F-card as having an empty
** array, rather than no F-cards, to hypothetically simplify
** handling in JSON queries. */
blob_append_char(b, ']');
}
CARD_STR2(G, p->zThreadRoot);
ISA( CFTYPE_FORUM ){
CARD_LETTER(H);
STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL);
CARD_STR2(I, p->zInReplyTo);
}
if( p->nField ){
CARD_LETTER(J);
blob_append_char(b, '[');
for( i = 0; i < p->nField; ++i ){
const char * zName = p->aField[i].zName;
if( i>0 ) blob_append_char(b, ',');
blob_append_char(b, '{');
KVP_STR(0, name, '+'==*zName ? &zName[1] : zName);
KVP_STR(1, value, p->aField[i].zValue);
blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false");
blob_append_char(b, '}');
}
blob_append_char(b, ']');
}
CARD_STR2(K, p->zTicketUuid);
CARD_STR2(L, p->zWikiTitle);
ISA( CFTYPE_CLUSTER ){
CARD_LETTER(M);
blob_append_char(b, '[');
for( int i = 0; i < p->nCChild; ++i ){
if( i>0 ) blob_append_char(b, ',');
blob_appendf(b, "%!j", p->azCChild[i]);
}
blob_append_char(b, ']');
}
CARD_STR2(N, p->zMimetype);
ISA( CFTYPE_MANIFEST || p->nParent>0 ){
CARD_LETTER(P);
blob_append_char(b, '[');
for( i = 0; i < p->nParent; ++i ){
if( i>0 ) blob_append_char(b, ',');
blob_appendf(b, "%!j", p->azParent[i]);
}
/* Special case: model checkins with no P-card as having an empty
** array, as per F-cards. */
blob_append_char(b, ']');
}
if( p->nCherrypick ){
CARD_LETTER(Q);
blob_append_char(b, '[');
for( i = 0; i < p->nCherrypick; ++i ){
if( i>0 ) blob_append_char(b, ',');
blob_append_char(b, '{');
blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]);
KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
KVP_STR(1, base, p->aCherrypick[i].zCPBase);
blob_append_char(b, '}');
}
blob_append_char(b, ']');
}
CARD_STR2(R, p->zRepoCksum);
if( p->nTag ){
CARD_LETTER(T);
blob_append_char(b, '[');
for( int i = 0; i < p->nTag; ++i ){
const char *zName = p->aTag[i].zName;
if( i>0 ) blob_append_char(b, ',');
blob_append_char(b, '{');
blob_appendf(b, "\"type\":\"%c\"", *zName);
KVP_STR(1, name, &zName[1]);
KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
/* We could arguably resolve the "*" as null or p's uuid. */;
KVP_STR(1, value, p->aTag[i].zValue);
blob_append_char(b, '}');
}
blob_append_char(b, ']');
}
CARD_STR2(U, p->zUser);
if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type
|| CFTYPE_EVENT==p->type ){
CARD_LETTER(W);
STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL);
}
blob_append_literal(b, "}");
#undef CARD_FMT
#undef CARD_LETTER
#undef CARD_STR
#undef CARD_STR2
#undef ISA
#undef KVP_STR
|
| ︙ | ︙ | |||
3162 3163 3164 3165 3166 3167 3168 | } /* ** COMMAND: test-artifact-to-json ** | | | | 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 |
}
/*
** COMMAND: test-artifact-to-json
**
** Usage: %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names]
**
** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
*/
void test_manifest_to_json(void){
int i;
Blob b = empty_blob;
Stmt q;
const int bPretty = find_option("pretty","p",0)!=0;
int nErr = 0;
db_find_and_open_repository(0,0);
db_prepare(&q, "select json_pretty(:json)");
for( i=2; i<g.argc; ++i ){
char const *zName = g.argv[i];
const int rc = artifact_to_json_by_name(zName, &b);
|
| ︙ | ︙ |
Changes to src/name.c.
| ︙ | ︙ | |||
1814 1815 1816 1817 1818 1819 1820 |
if( bRecent==0 || n!=250 ){
style_submenu_element("Recent","bloblist?n=250&recent");
}
if( bUnclst==0 ){
style_submenu_element("Unclustered","bloblist?unclustered");
}
if( g.perm.Admin ){
| | | 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 |
if( bRecent==0 || n!=250 ){
style_submenu_element("Recent","bloblist?n=250&recent");
}
if( bUnclst==0 ){
style_submenu_element("Unclustered","bloblist?unclustered");
}
if( g.perm.Admin ){
style_submenu_element("Xfer Log", "rcvfromlist");
}
if( !phantomOnly ){
style_submenu_element("Phantoms", "bloblist?phan");
}
style_submenu_element("Clusters","clusterlist");
if( g.perm.Private || g.perm.Admin ){
if( !privOnly ){
|
| ︙ | ︙ | |||
2003 2004 2005 2006 2007 2008 2009 |
** generate extra network traffic on every sync request.
*/
void phantom_list_page(void){
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("Public Phantom Artifacts");
if( g.perm.Admin ){
| | | 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 |
** generate extra network traffic on every sync request.
*/
void phantom_list_page(void){
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("Public Phantom Artifacts");
if( g.perm.Admin ){
style_submenu_element("Xfer Log", "rcvfromlist");
style_submenu_element("Artifact List", "bloblist");
}
if( g.perm.Write ){
style_submenu_element("Artifact Stats", "artifact_stats");
}
table_of_public_phantoms();
style_finish_page();
|
| ︙ | ︙ | |||
2028 2029 2030 2031 2032 2033 2034 |
void bigbloblist_page(void){
Stmt q;
int n = atoi(PD("n","250"));
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
if( g.perm.Admin ){
| | | 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 |
void bigbloblist_page(void){
Stmt q;
int n = atoi(PD("n","250"));
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
if( g.perm.Admin ){
style_submenu_element("Xfer Log", "rcvfromlist");
}
if( g.perm.Write ){
style_submenu_element("Artifact Stats", "artifact_stats");
}
style_submenu_element("All Artifacts", "bloblist");
style_header("%d Largest Artifacts", n);
db_multi_exec(
|
| ︙ | ︙ | |||
2201 2202 2203 2204 2205 2206 2207 | } /* ** COMMAND: test-phantoms ** ** Usage: %fossil test-phantoms ** | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 |
}
/*
** COMMAND: test-phantoms
**
** Usage: %fossil test-phantoms
**
** Show all phantom artifacts. A phantom artifact is one for which there
** is no content. Options:
**
** --count Show only a count of the number of phantoms.
** --delta Show all delta-phantoms. A delta-phantom is a
** artifact for which there is a delta but the delta
** source is a phantom.
** --list Just list the phantoms. Do not try to describe them.
*/
void test_phatoms_cmd(void){
int bDelta;
int bList;
int bCount;
unsigned nPhantom = 0;
unsigned nDeltaPhantom = 0;
db_find_and_open_repository(0,0);
bDelta = find_option("delta", 0, 0)!=0;
bList = find_option("list", 0, 0)!=0;
bCount = find_option("count", 0, 0)!=0;
verify_all_options();
if( bList || bCount ){
Stmt q1, q2;
db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0");
while( db_step(&q1)==SQLITE_ROW ){
int rid = db_column_int(&q1, 0);
nPhantom++;
if( !bCount ){
fossil_print("%S (%d)\n", db_column_text(&q1,1), rid);
}
db_prepare(&q2,
"WITH RECURSIVE deltasof(rid) AS ("
" SELECT rid FROM delta WHERE srcid=%d"
" UNION"
" SELECT delta.rid FROM deltasof, delta"
" WHERE delta.srcid=deltasof.rid)"
"SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob"
" ON blob.rid=deltasof.rid", rid
);
while( db_step(&q2)==SQLITE_ROW ){
nDeltaPhantom++;
if( !bCount ){
fossil_print(" %S (%d)\n", db_column_text(&q2,1),
db_column_int(&q2,0));
}
}
db_finalize(&q2);
}
db_finalize(&q1);
if( nPhantom ){
fossil_print("Phantoms: %u Delta-phantoms: %u\n",
nPhantom, nDeltaPhantom);
}
}else if( bDelta ){
describe_artifacts_to_stdout(
"IN (WITH RECURSIVE delta_phantom(rid) AS (\n"
" SELECT delta.rid FROM blob, delta\n"
" WHERE blob.size<0 AND delta.srcid=blob.rid\n"
" UNION\n"
" SELECT delta.rid FROM delta_phantom, delta\n"
" WHERE delta.srcid=delta_phantom.rid)\n"
" SELECT rid FROM delta_phantom)", 0);
}else{
describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
}
}
/* Maximum number of collision examples to remember */
#define MAX_COLLIDE 25
/*
** Generate a report on the number of collisions in artifact hashes
|
| ︙ | ︙ | |||
2309 2310 2311 2312 2313 2314 2315 |
sqlite3_int64 szTotal = 0;
sqlite3_int64 szCTotal = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("All Cluster Artifacts");
style_submenu_element("All Artifactst", "bloblist");
if( g.perm.Admin ){
| | | 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 |
sqlite3_int64 szTotal = 0;
sqlite3_int64 szCTotal = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("All Cluster Artifacts");
style_submenu_element("All Artifactst", "bloblist");
if( g.perm.Admin ){
style_submenu_element("Xfer Log", "rcvfromlist");
}
style_submenu_element("Phantoms", "bloblist?phan");
if( g.perm.Write ){
style_submenu_element("Artifact Stats", "artifact_stats");
}
db_prepare(&q,
|
| ︙ | ︙ |
Changes to src/printf.c.
| ︙ | ︙ | |||
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 |
void fossil_errorlog(const char *zFormat, ...){
struct tm *pNow;
time_t now;
FILE *out;
const char *z;
int i;
int bDetail = 0;
va_list ap;
static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
"HTTP_USER_AGENT",
"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
"REQUEST_URI", "SCRIPT_NAME" };
if( g.zErrlog==0 ) return;
if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){
| > | 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 |
void fossil_errorlog(const char *zFormat, ...){
struct tm *pNow;
time_t now;
FILE *out;
const char *z;
int i;
int bDetail = 0;
int bBrief = 0;
va_list ap;
static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
"HTTP_USER_AGENT",
"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
"REQUEST_URI", "SCRIPT_NAME" };
if( g.zErrlog==0 ) return;
if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){
|
| ︙ | ︙ | |||
1096 1097 1098 1099 1100 1101 1102 1103 1104 |
fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n",
pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday,
pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
va_start(ap, zFormat);
if( zFormat[0]=='X' ){
bDetail = 1;
zFormat++;
}
vfprintf(out, zFormat, ap);
| > > | > > | | | 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 |
fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n",
pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday,
pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
va_start(ap, zFormat);
if( zFormat[0]=='X' ){
bDetail = 1;
zFormat++;
}else if( strncmp(zFormat,"SMTP:",5)==0 ){
bBrief = 1;
}
vfprintf(out, zFormat, ap);
fprintf(out, " (pid %d)\n", (int)getpid());
va_end(ap);
if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase);
if( bBrief ){
/* Say nothing more */
}else if( bDetail ){
cgi_print_all(1,3,out);
}else{
for(i=0; i<count(azEnv); i++){
char *p;
if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){
fprintf(out, "%s=%s\n", azEnv[i], p);
fossil_path_free(p);
}else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
fprintf(out, "%s=%s\n", azEnv[i], z);
}
}
}
if( out!=stderr ) fclose(out);
}
/*
** The following variable becomes true while processing a fatal error
** or a panic. If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/
|
| ︙ | ︙ |
Changes to src/repolist.c.
| ︙ | ︙ | |||
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
struct RepoInfo {
char *zRepoName; /* Name of the repository file */
int isValid; /* True if zRepoName is a valid Fossil repository */
int isRepolistSkin; /* 1 or 2 if this repository wants to be the skin
** for the repository list. 2 means do use this
** repository but do not display it in the list. */
char *zProjName; /* Project Name. Memory from fossil_malloc() */
char *zLoginGroup; /* Name of login group, or NULL. Malloced() */
double rMTime; /* Last update. Julian day number */
};
#endif
/*
** Discover information about the repository given by
** pRepo->zRepoName. The discovered information is stored in other
** fields of the RepoInfo object.
*/
static void remote_repo_info(RepoInfo *pRepo){
sqlite3 *db;
sqlite3_stmt *pStmt;
int rc;
pRepo->isRepolistSkin = 0;
pRepo->isValid = 0;
pRepo->zProjName = 0;
pRepo->zLoginGroup = 0;
pRepo->rMTime = 0.0;
g.dbIgnoreErrors++;
rc = sqlite3_open_v2(pRepo->zRepoName, &db, SQLITE_OPEN_READWRITE, 0);
if( rc ) goto finish_repo_list;
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
| > > | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
struct RepoInfo {
char *zRepoName; /* Name of the repository file */
int isValid; /* True if zRepoName is a valid Fossil repository */
int isRepolistSkin; /* 1 or 2 if this repository wants to be the skin
** for the repository list. 2 means do use this
** repository but do not display it in the list. */
char *zProjName; /* Project Name. Memory from fossil_malloc() */
char *zProjDesc; /* Project Description. Memory from fossil_malloc() */
char *zLoginGroup; /* Name of login group, or NULL. Malloced() */
double rMTime; /* Last update. Julian day number */
};
#endif
/*
** Discover information about the repository given by
** pRepo->zRepoName. The discovered information is stored in other
** fields of the RepoInfo object.
*/
static void remote_repo_info(RepoInfo *pRepo){
sqlite3 *db;
sqlite3_stmt *pStmt;
int rc;
pRepo->isRepolistSkin = 0;
pRepo->isValid = 0;
pRepo->zProjName = 0;
pRepo->zProjDesc = 0;
pRepo->zLoginGroup = 0;
pRepo->rMTime = 0.0;
g.dbIgnoreErrors++;
rc = sqlite3_open_v2(pRepo->zRepoName, &db, SQLITE_OPEN_READWRITE, 0);
if( rc ) goto finish_repo_list;
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
|
| ︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
" WHERE name='project-name'",
-1, &pStmt, 0);
if( rc ) goto finish_repo_list;
if( sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
" WHERE name='login-group-name'",
-1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->zLoginGroup = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->rMTime = sqlite3_column_double(pStmt,0);
}
pRepo->isValid = 1;
sqlite3_finalize(pStmt);
finish_repo_list:
g.dbIgnoreErrors--;
sqlite3_close(db);
}
/*
** Generate a web-page that lists all repositories located under the
** g.zRepositoryName directory and return non-zero.
**
** For the special case when g.zRepositoryName is a non-chroot-jail "/",
** compose the list using the "repo:" entries in the global_config
| > > > > > > > > > > > > > > > > > > > > > > > > | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
" WHERE name='project-name'",
-1, &pStmt, 0);
if( rc ) goto finish_repo_list;
if( sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
if( rc ) goto finish_repo_list;
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
" WHERE name='project-description'",
-1, &pStmt, 0);
if( rc ) goto finish_repo_list;
if( sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->zProjDesc = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
" WHERE name='login-group-name'",
-1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->zLoginGroup = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
pRepo->rMTime = sqlite3_column_double(pStmt,0);
}
pRepo->isValid = 1;
sqlite3_finalize(pStmt);
finish_repo_list:
g.dbIgnoreErrors--;
sqlite3_close(db);
}
/*
** SETTING: show-repolist-desc boolean default=off
**
** If the value of this setting is "1" globally, then the repository-list
** page will show the description of each repository. This setting only
** has effect when it is in the global setting database.
*/
/*
** SETTING: show-repolist-lg boolean default=off
**
** If the value of this setting is "1" globally, then the repository-list
** page will show the login-group for each repository. This setting only
** has effect when it is in the global setting database.
*/
/*
** Generate a web-page that lists all repositories located under the
** g.zRepositoryName directory and return non-zero.
**
** For the special case when g.zRepositoryName is a non-chroot-jail "/",
** compose the list using the "repo:" entries in the global_config
|
| ︙ | ︙ | |||
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
int n = 0; /* Number of repositories found */
int allRepo; /* True if running "fossil ui all".
** False if a directory scan of base for repos */
Blob html; /* Html for the body of the repository list */
char *zSkinRepo = 0; /* Name of the repository database used for skins */
char *zSkinUrl = 0; /* URL for the skin database */
int quickfilter = 0; /* Is quickfilter is enabled? */
assert( g.db==0 );
blob_init(&html, 0, 0);
if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
/* For the special case of the "repository directory" being "/",
** show all of the repositories named in the ~/.fossil database.
**
** On unix systems, then entries are of the form "repo:/home/..."
** and on Windows systems they are like on unix, starting with a "/"
** or they can begin with a drive letter: "repo:C:/Users/...". In either
** case, we want returned path to omit any initial "/".
*/
| > > > > > > > > > > > > > > > < > > | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
int n = 0; /* Number of repositories found */
int allRepo; /* True if running "fossil ui all".
** False if a directory scan of base for repos */
Blob html; /* Html for the body of the repository list */
char *zSkinRepo = 0; /* Name of the repository database used for skins */
char *zSkinUrl = 0; /* URL for the skin database */
int quickfilter = 0; /* Is quickfilter is enabled? */
const char *zShow; /* Value of FOSSIL_REPOLIST_SHOW environment variable */
int bShowDesc = 0; /* True to show the description column */
int bShowLg = 0; /* True to show the login-group column */
assert( g.db==0 );
zShow = P("FOSSIL_REPOLIST_SHOW");
if( zShow ){
bShowDesc = strstr(zShow,"description")!=0;
bShowLg = strstr(zShow,"login-group")!=0;
}else if( db_open_config(1, 1)
&& db_table_exists("configdb", "global_config")
){
bShowDesc = db_int(bShowDesc, "SELECT value FROM global_config"
" WHERE name='show-repolist-desc'");
bShowLg = db_int(bShowLg, "SELECT value FROM global_config"
" WHERE name='show-repolist-lg'");
}
blob_init(&html, 0, 0);
if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
/* For the special case of the "repository directory" being "/",
** show all of the repositories named in the ~/.fossil database.
**
** On unix systems, then entries are of the form "repo:/home/..."
** and on Windows systems they are like on unix, starting with a "/"
** or they can begin with a drive letter: "repo:C:/Users/...". In either
** case, we want returned path to omit any initial "/".
*/
db_multi_exec(
"CREATE TEMP VIEW sfile AS"
" SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
" WHERE name GLOB 'repo:*'"
);
allRepo = 1;
}else{
/* The default case: All repositories under the g.zRepositoryName
** directory.
*/
blob_init(&base, g.zRepositoryName, -1);
db_close(0);
assert( g.db==0 );
sqlite3_open(":memory:", &g.db);
db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
db_multi_exec("CREATE TABLE vfile(pathname);");
vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"
#if USE_SEE
" AND pathname NOT GLOB '*[^/].efossil'"
|
| ︙ | ︙ | |||
159 160 161 162 163 164 165 |
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
return 0;
}else{
Stmt q;
double rNow;
| > > > > > > > > > > > > > > > > > | | | | > > > > > > > > > | > > > > | > > | | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
return 0;
}else{
Stmt q;
double rNow;
char zType[16]; /* Column type letters for class "sortable" */
int nType;
zType[0] = 't'; /* Repo name */
zType[1] = 'x'; /* Space between repo-name and project-name */
zType[2] = 't'; /* Project name */
nType = 3;
if( bShowDesc ){
zType[nType++] = 'x'; /* Space between name and description */
zType[nType++] = 't'; /* Project description */
}
zType[nType++] = 'x'; /* space before age */
zType[nType++] = 'k'; /* Project age */
if( bShowLg ){
zType[nType++] = 'x'; /* space before login-group */
zType[nType++] = 't'; /* Login Group */
}
zType[nType] = 0;
blob_appendf(&html,
"<table border='0' class='sortable filterlist' data-init-sort='1'"
" data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n"
"<tr><th>Filename</th><th> </th>\n"
"<th%s><nobr>Project Name</nobr></th>\n",
zType, (bShowDesc ? " width='25%'" : ""));
if( bShowDesc ){
blob_appendf(&html,
"<th> </th>\n"
"<th width='25%%'><nobr>Project Description</nobr></th>\n"
);
}
blob_appendf(&html,
"<th> </th>"
"<th><nobr>Last Modified</nobr></th>\n"
);
if( bShowLg ){
blob_appendf(&html,
"<th> </th>"
"<th><nobr>Login Group</nobr></th></tr>\n"
);
}
blob_appendf(&html,"</thead><tbody>\n");
db_prepare(&q, "SELECT pathname"
" FROM sfile ORDER BY pathname COLLATE nocase;");
rNow = db_double(0, "SELECT julianday('now')");
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
int nName = (int)strlen(zName);
int nSuffix = 7; /* ".fossil" */
|
| ︙ | ︙ | |||
229 230 231 232 233 234 235 |
iAge = (sqlite3_int64)((rNow - x.rMTime)*86400);
zAge = human_readable_age(rNow - x.rMTime);
if( x.rMTime==0.0 ){
/* This repository has no entry in the "event" table.
** Its age will still be maximum, so data-sortkey will work. */
zAge = mprintf("unknown");
}
| | | | | | | | > > | | > > > > > > > > > | | > > > | > > | | | | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
iAge = (sqlite3_int64)((rNow - x.rMTime)*86400);
zAge = human_readable_age(rNow - x.rMTime);
if( x.rMTime==0.0 ){
/* This repository has no entry in the "event" table.
** Its age will still be maximum, so data-sortkey will work. */
zAge = mprintf("unknown");
}
blob_appendf(&html, "<tr><td valign='top'><nobr>");
if( !file_ends_with_repository_extension(zName,0) ){
/* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
** do not work for repositories whose names do not end in ".fossil".
** So do not hyperlink those cases. */
blob_appendf(&html,"%h",zName);
} else if( sqlite3_strglob("*/.*", zName)==0 ){
/* Do not show hyperlinks for hidden repos */
blob_appendf(&html, "%h (hidden)", zName);
} else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
blob_appendf(&html,
"<a href='%R/%T/home' target='_blank'>/%h</a>\n",
zUrl, zName);
}else if( file_ends_with_repository_extension(zName,1) ){
/* As described in
** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
** foo.fossil and foo/bar.fossil both exist and we create a
** link to foo/bar/... then the URI dispatcher will instead
** see that as a link to foo.fossil. In such cases, do not
** emit a link to foo/bar.fossil. */
char * zDirPart = file_dirname(zName);
if( db_exists("SELECT 1 FROM sfile "
"WHERE pathname=(%Q || '.fossil') COLLATE nocase"
#if USE_SEE
" OR pathname=(%Q || '.efossil') COLLATE nocase"
#endif
, zDirPart
#if USE_SEE
, zDirPart
#endif
) ){
blob_appendf(&html,
"<s>%h</s> (directory/repo name collision)\n",
zName);
}else{
blob_appendf(&html,
"<a href='%R/%T/home' target='_blank'>%h</a>\n",
zUrl, zName);
}
fossil_free(zDirPart);
}else{
blob_appendf(&html,
"<a href='%R/%T/home' target='_blank'>%h</a>\n",
zUrl, zName);
}
blob_appendf(&html,"</nobr></td>\n");
if( x.zProjName ){
blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n",
x.zProjName);
fossil_free(x.zProjName);
}else{
blob_appendf(&html, "<td> </td><td></td>\n");
}
if( !bShowDesc ){
/* Do nothing */
}else if( x.zProjDesc ){
blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n",
x.zProjDesc);
fossil_free(x.zProjDesc);
}else{
blob_appendf(&html, "<td> </td><td></td>\n");
}
blob_appendf(&html,
"<td> </td><td data-sortkey='%08x' align='center' valign='top'>"
"<nobr>%h</nobr></td>\n",
(int)iAge, zAge);
fossil_free(zAge);
if( !bShowLg ){
blob_appendf(&html, "</tr>\n");
}else if( x.zLoginGroup ){
blob_appendf(&html, "<td> </td><td valign='top'>"
"<nobr>%h</nobr></td></tr>\n",
x.zLoginGroup);
fossil_free(x.zLoginGroup);
}else{
blob_appendf(&html, "<td> </td><td></td></tr>\n");
}
sqlite3_free(zUrl);
}
db_finalize(&q);
blob_appendf(&html,"</tbody></table>\n");
}
if( zSkinRepo ){
char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl);
g.zBaseURL = 0;
set_base_url(zNewBase);
db_open_repository(zSkinRepo);
fossil_free(zSkinRepo);
|
| ︙ | ︙ |
Changes to src/report.c.
| ︙ | ︙ | |||
586 587 588 589 590 591 592 |
rn = 0;
zTitle = mprintf("Copy Of %s", zTitle);
zOwner = g.zLogin;
}
}
if( zOwner==0 ) zOwner = g.zLogin;
style_submenu_element("Cancel", "%R/reportlist");
| < < < | 586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
rn = 0;
zTitle = mprintf("Copy Of %s", zTitle);
zOwner = g.zLogin;
}
}
if( zOwner==0 ) zOwner = g.zLogin;
style_submenu_element("Cancel", "%R/reportlist");
style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format");
if( zErr ){
@ <blockquote class="reportError">%h(zErr)</blockquote>
}
@ <form action="rptedit" method="post"><div>
@ <input type="hidden" name="rn" value="%d(rn)">
@ <p>Report Title:<br>
|
| ︙ | ︙ | |||
904 905 906 907 908 909 910 |
}
++pState->nCount;
/* Output the separator above each entry in a table which has multiple lines
** per database entry.
*/
if( pState->iNewRow>=0 ){
| | > | 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 |
}
++pState->nCount;
/* Output the separator above each entry in a table which has multiple lines
** per database entry.
*/
if( pState->iNewRow>=0 ){
@ <tr><td colspan="%d(pState->nCol)" style="padding:0px">
@ <hr style="margin:0px"></td></tr>
}
/* Output the data for this entry from the database
*/
zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
if( zBg==0 ) zBg = "white";
@ <tr style="background-color:%h(zBg)">
|
| ︙ | ︙ |
Changes to src/search.c.
| ︙ | ︙ | |||
616 617 618 619 620 621 622 623 624 625 626 627 628 629 |
const char *zScope = 0;
const char *zWidth = find_option("width","W",1);
int bDebug = find_option("debug",0,0)!=0; /* Undocumented */
int nLimit = zLimit ? atoi(zLimit) : -1000;
int width;
int nTty = 0; /* VT100 highlight color for matching text */
const char *zHighlight = 0;
nTty = terminal_is_vt100();
/* Undocumented option to change highlight color */
zHighlight = find_option("highlight",0,1);
if( zHighlight ) nTty = atoi(zHighlight);
| > | 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 |
const char *zScope = 0;
const char *zWidth = find_option("width","W",1);
int bDebug = find_option("debug",0,0)!=0; /* Undocumented */
int nLimit = zLimit ? atoi(zLimit) : -1000;
int width;
int nTty = 0; /* VT100 highlight color for matching text */
const char *zHighlight = 0;
int bFlags = 0; /* DB open flags */
nTty = terminal_is_vt100();
/* Undocumented option to change highlight color */
zHighlight = find_option("highlight",0,1);
if( zHighlight ) nTty = atoi(zHighlight);
|
| ︙ | ︙ | |||
664 665 666 667 668 669 670 |
if( find_option("technotes",0,0) ){ srchFlags |= SRCH_TECHNOTE; bFts = 1; }
if( find_option("tickets",0,0) ){ srchFlags |= SRCH_TKT; bFts = 1; }
if( find_option("wiki",0,0) ){ srchFlags |= SRCH_WIKI; bFts = 1; }
/* If no search objects are specified, default to "check-in comments" */
if( srchFlags==0 ) srchFlags = SRCH_CKIN;
| | | | 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 |
if( find_option("technotes",0,0) ){ srchFlags |= SRCH_TECHNOTE; bFts = 1; }
if( find_option("tickets",0,0) ){ srchFlags |= SRCH_TKT; bFts = 1; }
if( find_option("wiki",0,0) ){ srchFlags |= SRCH_WIKI; bFts = 1; }
/* If no search objects are specified, default to "check-in comments" */
if( srchFlags==0 ) srchFlags = SRCH_CKIN;
if( srchFlags==SRCH_HELP ) bFlags = OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE;
db_find_and_open_repository(bFlags, 0);
verify_all_options();
if( g.argc<3 ) return;
login_set_capabilities("s", 0);
if( search_restrict(srchFlags)==0 && (srchFlags & SRCH_HELP)==0 ){
const char *zC1 = 0, *zPlural = "s";
if( srchFlags & SRCH_TECHNOTE ){ zC1 = "technote"; }
if( srchFlags & SRCH_TKT ){ zC1 = "ticket"; }
|
| ︙ | ︙ |
Changes to src/security_audit.c.
| ︙ | ︙ | |||
98 99 100 101 102 103 104 105 106 107 108 109 110 111 | const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */ const char *zDevCap; /* Capabilities of user group "developer" */ const char *zReadCap; /* Capabilities of user group "reader" */ const char *zPubPages; /* GLOB pattern for public pages */ const char *zSelfCap; /* Capabilities of self-registered users */ int hasSelfReg = 0; /* True if able to self-register */ const char *zPublicUrl; /* Canonical access URL */ Blob cmd; char *z; int n, i; CapabilityString *pCap; char **azCSP; /* Parsed content security policy */ login_check_credentials(); | > | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */ const char *zDevCap; /* Capabilities of user group "developer" */ const char *zReadCap; /* Capabilities of user group "reader" */ const char *zPubPages; /* GLOB pattern for public pages */ const char *zSelfCap; /* Capabilities of self-registered users */ int hasSelfReg = 0; /* True if able to self-register */ const char *zPublicUrl; /* Canonical access URL */ const char *zVulnReport; /* The vuln-report setting */ Blob cmd; char *z; int n, i; CapabilityString *pCap; char **azCSP; /* Parsed content security policy */ login_check_credentials(); |
| ︙ | ︙ | |||
360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
/* The strict-manifest-syntax setting should be on. */
if( db_get_boolean("strict-manifest-syntax",1)==0 ){
@ <li><p><b>WARNING:</b>
@ The "strict-manifest-syntax" flag is off. This is a security
@ risk. Turn this setting on (its default) to protect the users
@ of this repository.
}
/* Obsolete: */
if( hasAnyCap(zAnonCap, "d") ||
hasAnyCap(zDevCap, "d") ||
hasAnyCap(zReadCap, "d") ){
@ <li><p><b>WARNING:</b>
@ One or more users has the <a
| > > > > > > > > > > > > | 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
/* The strict-manifest-syntax setting should be on. */
if( db_get_boolean("strict-manifest-syntax",1)==0 ){
@ <li><p><b>WARNING:</b>
@ The "strict-manifest-syntax" flag is off. This is a security
@ risk. Turn this setting on (its default) to protect the users
@ of this repository.
}
zVulnReport = db_get("vuln-report","log");
if( fossil_strcmp(zVulnReport,"block")!=0
&& fossil_strcmp(zVulnReport,"fatal")!=0
){
@ <li><p><b>WARNING:</b>
@ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
@ has a value of "%h(zVulnReport)". This disables defenses against
@ XSS or SQL-injection vulnerabilities caused by coding errors in
@ custom TH1 scripts. For the best security, change
@ the value of the vuln-report setting to "block" or "fatal".
}
/* Obsolete: */
if( hasAnyCap(zAnonCap, "d") ||
hasAnyCap(zDevCap, "d") ||
hasAnyCap(zReadCap, "d") ){
@ <li><p><b>WARNING:</b>
@ One or more users has the <a
|
| ︙ | ︙ | |||
808 809 810 811 812 813 814 | /* ** WEBPAGE: errorlog ** ** Show the content of the error log. Only the administrator can view ** this page. ** | | | | | > > > | | > > > | 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 |
/*
** WEBPAGE: errorlog
**
** Show the content of the error log. Only the administrator can view
** this page.
**
** y=0x001 Show only hack attempts
** y=0x002 Show only panics and assertion faults
** y=0x004 Show hung backoffice processes
** y=0x008 Show POST requests from a different origin
** y=0x010 Show SQLITE_AUTH and similar
** y=0x020 Show SMTP error reports
** y=0x040 Show TH1 vulnerability reports
** y=0x800 Show other uncategorized messages
**
** If y is omitted or is zero, a count of the various message types is
** shown.
*/
void errorlog_page(void){
i64 szFile;
FILE *in;
char *zLog;
const char *zType = P("y");
static const int eAllTypes = 0x87f;
long eType = 0;
int bOutput = 0;
int prevWasTime = 0;
int nHack = 0;
int nPanic = 0;
int nOther = 0;
int nHang = 0;
int nXPost = 0;
int nAuth = 0;
int nSmtp = 0;
int nVuln = 0;
char z[10000];
char zTime[10000];
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
|
| ︙ | ︙ | |||
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 |
}
if( eType & 0x04 ){
@ <li>Hung backoffice processes
}
if( eType & 0x08 ){
@ <li>POST requests from different origin
}
if( eType & 0x40 ){
@ <li>Other uncategorized messages
}
@ </ul>
}
@ <hr>
if( eType ){
@ <pre>
}
while( fgets(z, sizeof(z), in) ){
if( prevWasTime ){
if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){
bOutput = (eType & 0x01)!=0;
nHack++;
}else
if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){
bOutput = (eType & 0x02)!=0;
nPanic++;
}else
if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
bOutput = (eType & 0x04)!=0;
nHang++;
}else
if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
bOutput = (eType & 0x08)!=0;
nXPost++;
}else
{
| > > > > > > > > > > > > > > > > > > > > > > > | | 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 |
}
if( eType & 0x04 ){
@ <li>Hung backoffice processes
}
if( eType & 0x08 ){
@ <li>POST requests from different origin
}
if( eType & 0x10 ){
@ <li>SQLITE_AUTH and similar errors
}
if( eType & 0x20 ){
@ <li>SMTP malfunctions
}
if( eType & 0x40 ){
@ <li>TH1 vulnerabilities
}
if( eType & 0x800 ){
@ <li>Other uncategorized messages
}
@ </ul>
}
@ <hr>
if( eType ){
@ <pre>
}
while( fgets(z, sizeof(z), in) ){
if( prevWasTime ){
if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){
bOutput = (eType & 0x01)!=0;
nHack++;
}else
if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){
bOutput = (eType & 0x02)!=0;
nPanic++;
}else
if( strncmp(z,"SMTP:", 5)==0 ){
bOutput = (eType & 0x20)!=0;
nSmtp++;
}else
if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
bOutput = (eType & 0x04)!=0;
nHang++;
}else
if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
bOutput = (eType & 0x08)!=0;
nXPost++;
}else
if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
|| sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
){
bOutput = (eType & 0x10)!=0;
nAuth++;
}else
if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
bOutput = (eType & 0x40)!=0;
nVuln++;
}else
{
bOutput = (eType & 0x800)!=0;
nOther++;
}
if( bOutput ){
@ %h(zTime)\
}
}
if( strncmp(z, "--------", 8)==0 ){
|
| ︙ | ︙ | |||
956 957 958 959 960 961 962 |
}
}
fclose(in);
if( eType ){
@ </pre>
}
if( eType==0 ){
| | > > > > | > > > > > > > > | | < < < < | | 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 |
}
}
fclose(in);
if( eType ){
@ </pre>
}
if( eType==0 ){
int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
int nTotal = nNonHack + nHack + nXPost;
@ <p><table border="a" cellspacing="0" cellpadding="5">
if( nPanic>0 ){
@ <tr><td align="right">%d(nPanic)</td>
@ <td><a href="./errorlog?y=2">Panics</a></td>
}
if( nVuln>0 ){
@ <tr><td align="right">%d(nVuln)</td>
@ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
}
if( nHack>0 ){
@ <tr><td align="right">%d(nHack)</td>
@ <td><a href="./errorlog?y=1">Hack Attempts</a></td>
}
if( nHang>0 ){
@ <tr><td align="right">%d(nHang)</td>
@ <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
}
if( nXPost>0 ){
@ <tr><td align="right">%d(nXPost)</td>
@ <td><a href="./errorlog?y=8">POSTs from different origin</a></td>
}
if( nAuth>0 ){
@ <tr><td align="right">%d(nAuth)</td>
@ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
}
if( nSmtp>0 ){
@ <tr><td align="right">%d(nSmtp)</td>
@ <td><a href="./errorlog?y=32">SMTP faults</a></td>
}
if( nOther>0 ){
@ <tr><td align="right">%d(nOther)</td>
@ <td><a href="./errorlog?y=2048">Other</a></td>
}
@ <tr><td align="right">%d(nTotal)</td>
if( nTotal>0 ){
@ <td><a href="./errorlog?y=4095">All Messages</a></td>
}else{
@ <td>All Messages</td>
}
@ </table>
}
style_finish_page();
}
|
Changes to src/setup.c.
| ︙ | ︙ | |||
199 200 201 202 203 204 205 |
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Log Menu");
@ <table border="0" cellspacing="3">
| | | | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Log Menu");
@ <table border="0" cellspacing="3">
if( db_get_boolean("admin-log",1)==0 ){
blob_appendf(&desc,
"The admin log records configuration changes to the repository.\n"
"<b>Disabled</b>: Turn on the "
" <a href='%R/setup_settings'>admin-log setting</a> to enable."
);
setup_menu_entry("Admin Log", 0, blob_str(&desc));
blob_reset(&desc);
}else{
setup_menu_entry("Admin Log", "admin_log",
"The admin log records configuration changes to the repository\n"
"in the \"admin_log\" table.\n"
);
}
setup_menu_entry("Xfer Log", "rcvfromlist",
"The artifact log records when new content is added in the\n"
"\"rcvfrom\" table.\n"
);
if( db_get_boolean("access-log",1) ){
setup_menu_entry("User Log", "user_log",
"Login attempts recorded in the \"accesslog\" table."
);
|
| ︙ | ︙ | |||
460 461 462 463 464 465 466 |
@ </blockquote>
@ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds
@ and "require a mouse event" should be turned on. These values only come
@ into play when the main auto-hyperlink settings is 2 ("UserAgent and
@ Javascript").</p>
@
@ <p>To see if Javascript-base hyperlink enabling mechanism is working,
| | | 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
@ </blockquote>
@ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds
@ and "require a mouse event" should be turned on. These values only come
@ into play when the main auto-hyperlink settings is 2 ("UserAgent and
@ Javascript").</p>
@
@ <p>To see if Javascript-base hyperlink enabling mechanism is working,
@ visit the <a href="%R/test-env">/test-env</a> page (from a separate
@ web browser that is not logged in, even as "anonymous") and verify
@ that the "g.jsHref" value is "1".</p>
@ <p>(Properties: "auto-hyperlink", "auto-hyperlink-delay", and
@ "auto-hyperlink-mouseover"")</p>
}
/*
|
| ︙ | ︙ | |||
602 603 604 605 606 607 608 | @ without the "--localauth" option. @ <li> The server is started from CGI without the "localauth" keyword @ in the CGI script. @ </ol> @ (Property: "localauth") @ @ <hr> | | | 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 |
@ without the "--localauth" option.
@ <li> The server is started from CGI without the "localauth" keyword
@ in the CGI script.
@ </ol>
@ (Property: "localauth")
@
@ <hr>
onoff_attribute("Enable /test-env",
"test_env_enable", "test_env_enable", 0, 0);
@ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all
@ users. When disabled (the default) only users Admin and Setup can visit
@ the /test_env page.
@ (Property: "test_env_enable")
@ </p>
@
|
| ︙ | ︙ | |||
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
@ <hr>
onoff_attribute("Break comments at newline characters",
"timeline-hard-newlines", "thnl", 0, 0);
@ <p>In timeline displays, newline characters in check-in comments force
@ a line break on the display.
@ (Property: "timeline-hard-newlines")</p>
@ <hr>
onoff_attribute("Use Universal Coordinated Time (UTC)",
"timeline-utc", "utc", 1, 0);
@ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
@ Zulu) instead of in local time. On this server, local time is currently
tmDiff = db_double(0.0, "SELECT julianday('now')");
tmDiff = db_double(0.0,
| > > > > > > > > > > | 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 |
@ <hr>
onoff_attribute("Break comments at newline characters",
"timeline-hard-newlines", "thnl", 0, 0);
@ <p>In timeline displays, newline characters in check-in comments force
@ a line break on the display.
@ (Property: "timeline-hard-newlines")</p>
@ <hr>
onoff_attribute("Do not adjust user-selected background colors",
"raw-bgcolor", "rbgc", 0, 0);
@ <p>Fossil normally attempts to adjust the saturation and intensity of
@ user-specified background colors on check-ins and branches so that the
@ foreground text is easily readable on all skins. Enable this setting
@ to omit that adjustment and use exactly the background color specified
@ by users.
@ (Property: "raw-bgcolor")</p>
@ <hr>
onoff_attribute("Use Universal Coordinated Time (UTC)",
"timeline-utc", "utc", 1, 0);
@ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
@ Zulu) instead of in local time. On this server, local time is currently
tmDiff = db_double(0.0, "SELECT julianday('now')");
tmDiff = db_double(0.0,
|
| ︙ | ︙ | |||
1284 1285 1286 1287 1288 1289 1290 |
@ The project name will also be used as the RSS feed title.
@ (Property: "project-name")
@ </p>
@ <hr>
textarea_attribute("Project Description", 3, 80,
"project-description", "pd", "", 0);
@ <p>Describe your project. This will be used in page headers for search
| | | 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 |
@ The project name will also be used as the RSS feed title.
@ (Property: "project-name")
@ </p>
@ <hr>
textarea_attribute("Project Description", 3, 80,
"project-description", "pd", "", 0);
@ <p>Describe your project. This will be used in page headers for search
@ engines, the repository listing and a short RSS description.
@ (Property: "project-description")</p>
@ <hr>
entry_attribute("Canonical Server URL", 40, "email-url",
"eurl", "", 0);
@ <p>This is the URL used to access this repository as a server.
@ Other repositories use this URL to clone or sync against this repository.
@ This is also the basename for hyperlinks included in email alert text.
|
| ︙ | ︙ |
Changes to src/setupuser.c.
| ︙ | ︙ | |||
39 40 41 42 43 44 45 46 47 48 49 50 51 |
*/
void setup_ulist(void){
Stmt s;
double rNow;
const char *zWith = P("with");
int bUnusedOnly = P("unused")!=0;
int bUbg = P("ubg")!=0;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
| > | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
*/
void setup_ulist(void){
Stmt s;
double rNow;
const char *zWith = P("with");
int bUnusedOnly = P("unused")!=0;
int bUbg = P("ubg")!=0;
int bHaveAlerts;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
bHaveAlerts = alert_tables_exist();
style_submenu_element("Add", "setup_uedit");
style_submenu_element("Log", "access_log");
style_submenu_element("Help", "setup_ulist_notes");
if( bHaveAlerts ){
style_submenu_element("Subscribers", "subscribers");
}
style_set_current_feature("setup");
style_header("User List");
if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
@ <thead><tr>
|
| ︙ | ︙ | |||
145 146 147 148 149 150 151 |
}
if( bUnusedOnly ){
zWith = mprintf(
" AND login NOT IN ("
"SELECT user FROM event WHERE user NOT NULL "
"UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
" AND uid NOT IN (SELECT uid FROM rcvfrom)",
| | | | | | < | > | | | | > > > > | | | | > > | | 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
}
if( bUnusedOnly ){
zWith = mprintf(
" AND login NOT IN ("
"SELECT user FROM event WHERE user NOT NULL "
"UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
" AND uid NOT IN (SELECT uid FROM rcvfrom)",
bHaveAlerts ?
" UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
}else if( zWith && zWith[0] ){
zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
}else{
zWith = "";
}
db_prepare(&s,
/*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch'),"
/* 5 */"lower(login) AS sortkey, "
/* 6 */"CASE WHEN info LIKE '%%expires 20%%'"
" THEN substr(info,instr(lower(info),'expires')+8,10)"
" END AS exp,"
/* 7 */"atime,"
/* 8 */"user.mtime AS sorttime,"
/*9-11*/"%s"
" FROM user LEFT JOIN lastAccess ON login=uname"
" LEFT JOIN subscriber ON login=suname"
" WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
" ORDER BY sorttime DESC",
bHaveAlerts
? "subscriber.ssub, subscriber.subscriberId, subscriber.semail"
: "null, null, null",
zWith/*safe-for-%s*/
);
rNow = db_double(0.0, "SELECT julianday('now');");
while( db_step(&s)==SQLITE_ROW ){
int uid = db_column_int(&s, 0);
const char *zLogin = db_column_text(&s, 1);
const char *zCap = db_column_text(&s, 2);
const char *zInfo = db_column_text(&s, 3);
const char *zDate = db_column_text(&s, 4);
const char *zSortKey = db_column_text(&s,5);
const char *zExp = db_column_text(&s,6);
double rATime = db_column_double(&s,7);
char *zAge = 0;
const char *zSub;
int sid = db_column_int(&s,10);
sqlite3_int64 sorttime = db_column_int64(&s, 8);
if( rATime>0.0 ){
zAge = human_readable_age(rNow - rATime);
}
if( bUbg ){
@ <tr style='background-color: %h(user_color(zLogin));'>
}else{
@ <tr>
}
@ <td data-sortkey='%h(zSortKey)'>\
@ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
@ <td>%h(zCap)
@ <td>%h(zInfo)
@ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
@ <td>%h(zExp?zExp:"")
@ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
if( db_column_type(&s,9)==SQLITE_NULL ){
@ <td>
}else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){
@ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a>
}else{
const char *zEmail = db_column_text(&s, 11);
char * zAt = zEmail ? mprintf(" → %h", zEmail) : mprintf("");
@ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt)
}
@ </tr>
fossil_free(zAge);
}
@ </tbody></table>
db_finalize(&s);
|
| ︙ | ︙ | |||
302 303 304 305 306 307 308 |
if( zPw==0 ) return 0;
if( zPw[0]==0 ) return 1;
while( zPw[0]=='*' ){ zPw++; }
return zPw[0]!=0;
}
/*
| | | | | > > | < | > > > > | > > > > > > > | > > > > | > > > | > > > > > > > > > > > > > > > > > > | | | | | 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 |
if( zPw==0 ) return 0;
if( zPw[0]==0 ) return 1;
while( zPw[0]=='*' ){ zPw++; }
return zPw[0]!=0;
}
/*
** Return true if user capability strings zOrig and zNew materially
** differ, taking into account that they may be sorted in an arbitary
** order. This does not take inherited permissions into
** account. Either argument may be NULL. A NULL and an empty string
** are considered equivalent here. e.g. "abc" and "cab" are equivalent
** for this purpose, but "aCb" and "acb" are not.
*/
static int userCapsChanged(const char *zOrig, const char *zNew){
if( !zOrig ){
return zNew ? (0!=*zNew) : 0;
}else if( !zNew ){
return 0!=*zOrig;
}else if( 0==fossil_strcmp(zOrig, zNew) ){
return 0;
}else{
/* We don't know that zOrig and zNew are sorted equivalently. The
** following steps will compare strings which contain all the same
** capabilities letters as equivalent, regardless of the letters'
** order in their strings. */
char aOrig[128]; /* table of zOrig bytes */
int nOrig = 0, nNew = 0;
memset( &aOrig[0], 0, sizeof(aOrig) );
for( ; *zOrig; ++zOrig, ++nOrig ){
if( 0==(*zOrig & 0x80) ){
aOrig[(int)*zOrig] = 1;
}
}
for( ; *zNew; ++zNew, ++nNew ){
if( 0==(*zNew & 0x80) && !aOrig[(int)*zNew] ){
return 1;
}
}
return nOrig!=nNew;
}
}
/*
** COMMAND: test-user-caps-changed
**
** Usage: %fossil test-user-caps-changed caps1 caps2
**
*/
void test_user_caps_changed(void){
char const * zOld = g.argc>2 ? g.argv[2] : NULL;
char const * zNew = g.argc>3 ? g.argv[3] : NULL;
fossil_print("Has changes? = %d\n",
userCapsChanged( zOld, zNew ));
}
/*
** Sends notification of user permission elevation changes to all
** subscribers with a "u" subscription. This is a no-op if alerts are
** not enabled.
**
** These subscriptions differ from most, in that:
**
** - They currently lack an "unsubscribe" link.
**
** - Only an admin can assign this subscription, but if a non-admin
** edits their subscriptions after an admin assigns them this one,
** this particular one will be lost. "Feature or bug?" is unclear,
** but it would be odd for a non-admin to be assigned this
** capability.
*/
static void alert_user_cap_change(const char *zLogin, /*Affected user*/
int uid, /*[user].uid*/
int bIsNew, /*true if new user*/
const char *zOrigCaps,/*Old caps*/
const char *zNewCaps /*New caps*/){
Blob hdr, body;
Stmt q;
int nBody;
AlertSender *pSender;
char *zSubname;
char *zURL;
char * zSubject;
if( !alert_enabled() ) return;
zSubject = bIsNew
? mprintf("New user created: [%q]", zLogin)
: mprintf("User [%q] capabilities changed", zLogin);
zURL = db_get("email-url",0);
zSubname = db_get("email-subname", "[Fossil Repo]");
blob_init(&body, 0, 0);
blob_init(&hdr, 0, 0);
if( bIsNew ){
blob_appendf(&body, "User [%q] was created with "
"permissions [%q] by user [%q].\n",
zLogin, zNewCaps, g.zLogin);
} else {
blob_appendf(&body, "Permissions for user [%q] where changed "
"from [%q] to [%q] by user [%q].\n",
zLogin, zOrigCaps, zNewCaps, g.zLogin);
}
if( zURL ){
blob_appendf(&body, "\nUser editor: %s/setup_uedit?uid=%d\n", zURL, uid);
}
nBody = blob_size(&body);
|
| ︙ | ︙ | |||
484 485 486 487 488 489 490 |
}else if( zDeleteVerify!=0 ){
/* Need to verify a delete request */
}else if( !cgi_csrf_safe(2) ){
/* This might be a cross-site request forgery, so ignore it */
}else{
/* We have all the information we need to make the change to the user */
char c;
| | | 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
}else if( zDeleteVerify!=0 ){
/* Need to verify a delete request */
}else if( !cgi_csrf_safe(2) ){
/* This might be a cross-site request forgery, so ignore it */
}else{
/* We have all the information we need to make the change to the user */
char c;
int bCapsChanged = 0 /* 1 if user's permissions changed */;
const int bIsNew = uid<=0;
char aCap[70], zNm[4];
zNm[0] = 'a';
zNm[2] = 0;
for(i=0, c='a'; c<='z'; c++){
zNm[1] = c;
a[c&0x7f] = ((c!='s' && c!='y') || g.perm.Setup) && P(zNm)!=0;
|
| ︙ | ︙ | |||
506 507 508 509 510 511 512 |
for(c='A'; c<='Z'; c++){
zNm[1] = c;
a[c&0x7f] = P(zNm)!=0;
if( a[c&0x7f] ) aCap[i++] = c;
}
aCap[i] = 0;
| | | 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 |
for(c='A'; c<='Z'; c++){
zNm[1] = c;
a[c&0x7f] = P(zNm)!=0;
if( a[c&0x7f] ) aCap[i++] = c;
}
aCap[i] = 0;
bCapsChanged = bIsNew || userCapsChanged(zOldCaps, &aCap[0]);
zPw = P("pw");
zLogin = P("login");
if( strlen(zLogin)==0 ){
const char *zRef = cgi_referer("setup_ulist");
style_header("User Creation Error");
@ <span class="loginError">Empty login not allowed.</span>
@
|
| ︙ | ︙ | |||
611 612 613 614 615 616 617 |
style_header("User Change Error");
admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
@ <span class="loginError">%h(zErr)</span>
@
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
@ [Bummer]</a></p>
style_finish_page();
| | > > | | | | 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
style_header("User Change Error");
admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
@ <span class="loginError">%h(zErr)</span>
@
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
@ [Bummer]</a></p>
style_finish_page();
if( bCapsChanged ){
/* It's possible that caps were updated locally even if
** login group updates failed. */
alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
}
return;
}
}
if( bCapsChanged ){
alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
}
cgi_redirect(cgi_referer("setup_ulist"));
return;
}
/* Load the existing information about the user, if any
*/
|
| ︙ | ︙ |
Changes to src/shun.c.
| ︙ | ︙ | |||
371 372 373 374 375 376 377 |
const int perScreen = 500; /* RCVIDs per page */
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
| | | 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
const int perScreen = 500; /* RCVIDs per page */
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Xfer Log");
style_submenu_element("Log-Menu", "setup-logmenu");
if( showAll ){
ofst = 0;
}else{
style_submenu_element("All", "rcvfromlist?all=1");
}
if( ofst>0 ){
|
| ︙ | ︙ | |||
413 414 415 416 417 418 419 |
" EXISTS(SELECT 1 FROM rcvidSha1 WHERE x=rcvfrom.rcvid),"
" EXISTS(SELECT 1 FROM rcvidSha3 WHERE x=rcvfrom.rcvid)"
" FROM rcvfrom LEFT JOIN user USING(uid)"
" ORDER BY rcvid DESC LIMIT %d OFFSET %d",
showAll ? -1 : perScreen+1, ofst
);
@ <p>Whenever new artifacts are added to the repository, either by
| > | | | 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
" EXISTS(SELECT 1 FROM rcvidSha1 WHERE x=rcvfrom.rcvid),"
" EXISTS(SELECT 1 FROM rcvidSha3 WHERE x=rcvfrom.rcvid)"
" FROM rcvfrom LEFT JOIN user USING(uid)"
" ORDER BY rcvid DESC LIMIT %d OFFSET %d",
showAll ? -1 : perScreen+1, ofst
);
@ <p>Whenever new artifacts are added to the repository, either by
@ push or using the web interface or by "fossil commit" or similar,
@ an entry is made in the RCVFROM table
@ to record the source of those artifacts. This log facilitates
@ finding and fixing attempts to inject illicit content into the
@ repository.</p>
@
@ <p>Click on the "rcvid" to show a list of specific artifacts received
@ by a transaction. After identifying illicit artifacts, remove them
@ using the "Shun" button. If an "rcvid" is not hyperlinked, that means
@ all artifacts associated with that rcvid have already been shunned
|
| ︙ | ︙ |
Changes to src/sitemap.c.
| ︙ | ︙ | |||
283 284 285 286 287 288 289 |
}
if( !isPopup ){
style_header("Test Page Map");
style_adunit_config(ADUNIT_RIGHT_OK);
}
@ <ul id="sitemap" class="columns" style="column-width:20em">
if( g.perm.Admin || db_get_boolean("test_env_enable",0) ){
| | > | 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
}
if( !isPopup ){
style_header("Test Page Map");
style_adunit_config(ADUNIT_RIGHT_OK);
}
@ <ul id="sitemap" class="columns" style="column-width:20em">
if( g.perm.Admin || db_get_boolean("test_env_enable",0) ){
@ <li>%z(href("%R/test-env"))CGI Environment Test</a></li>
}
if( g.perm.Read ){
@ <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
}
@ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li>
@ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li>
@ <li>%z(href("%R/hash-color-test"))Hash color test</a>
@ <li>%z(href("%R/test-bgcolor"))Background color test</a>
if( g.perm.Admin ){
@ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li>
@ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li>
@ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li>
@ <li>%z(href("%R/test-warning"))Error Log test page</a></li>
@ <li>%z(href("%R/repo_stat1"))Repository <tt>sqlite_stat1</tt> table</a>
@ <li>%z(href("%R/repo_schema"))Repository schema</a></li>
|
| ︙ | ︙ |
Changes to src/smtp.c.
| ︙ | ︙ | |||
154 155 156 157 158 159 160 |
struct SmtpSession {
const char *zFrom; /* Domain from which we are sending */
const char *zDest; /* Domain that will receive the email */
char *zHostname; /* Hostname of SMTP server for zDest */
u32 smtpFlags; /* Flags changing the operation */
FILE *logFile; /* Write session transcript to this log file */
Blob *pTranscript; /* Record session transcript here */
| | > > | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
struct SmtpSession {
const char *zFrom; /* Domain from which we are sending */
const char *zDest; /* Domain that will receive the email */
char *zHostname; /* Hostname of SMTP server for zDest */
u32 smtpFlags; /* Flags changing the operation */
FILE *logFile; /* Write session transcript to this log file */
Blob *pTranscript; /* Record session transcript here */
int bOpen; /* True if connection is Open */
int bFatal; /* Error is fatal. Do not retry */
char *zErr; /* Error message */
Blob inbuf; /* Input buffer */
UrlData url; /* Address of the server */
};
/* Allowed values for SmtpSession.smtpFlags */
#define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
#define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
#define SMTP_TRACE_BLOB 0x00004 /* Record transcript */
#define SMTP_DIRECT 0x00008 /* Skip the MX lookup */
|
| ︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 |
void smtp_session_free(SmtpSession *pSession){
socket_close();
blob_reset(&pSession->inbuf);
fossil_free(pSession->zHostname);
fossil_free(pSession->zErr);
fossil_free(pSession);
}
/*
** Allocate a new SmtpSession object.
**
| > > > > > > > > > > > > > > > > > > > > > > | | | | < < | < < < | < | < < < < < < < | < | | | > > | > > > > > > > > > | > | > > < | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
void smtp_session_free(SmtpSession *pSession){
socket_close();
blob_reset(&pSession->inbuf);
fossil_free(pSession->zHostname);
fossil_free(pSession->zErr);
fossil_free(pSession);
}
/*
** Set an error message on the SmtpSession
*/
static void smtp_set_error(
SmtpSession *p, /* The SMTP context */
int bFatal, /* Fatal error. Reset and retry is pointless */
const char *zFormat, /* Error message. */
...
){
if( bFatal ) p->bFatal = 1;
if( p->zErr==0 ){
va_list ap;
va_start(ap, zFormat);
p->zErr = vmprintf(zFormat, ap);
va_end(ap);
}
if( p->bOpen ){
socket_close();
p->bOpen = 0;
}
}
/*
** Allocate a new SmtpSession object.
**
** Both zFrom and zDest must be specified. smtpFlags may not contain
** either SMTP_TRACE_FILE or SMTP_TRACE_BLOB as those settings must be
** added by a subsequent call to smtp_session_config().
**
** The iPort option is ignored unless SMTP_PORT is set in smtpFlags
*/
SmtpSession *smtp_session_new(
const char *zFrom, /* Domain for the client */
const char *zDest, /* Domain of the server */
u32 smtpFlags, /* Flags */
int iPort /* TCP port if the SMTP_PORT flags is present */
){
SmtpSession *p;
p = fossil_malloc( sizeof(*p) );
memset(p, 0, sizeof(*p));
p->zFrom = zFrom;
p->zDest = zDest;
p->smtpFlags = smtpFlags;
p->url.port = 25;
blob_init(&p->inbuf, 0, 0);
if( smtpFlags & SMTP_PORT ){
p->url.port = iPort;
}
if( (smtpFlags & SMTP_DIRECT)!=0 ){
int i;
p->zHostname = fossil_strdup(zDest);
for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
if( p->zHostname[i]==':' ){
p->zHostname[i] = 0;
p->url.port = atoi(&p->zHostname[i+1]);
}
}else{
p->zHostname = smtp_mx_host(zDest);
}
if( p->zHostname==0 ){
smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest);
return p;
}
p->url.name = p->zHostname;
socket_global_init();
p->bOpen = 0;
return p;
}
/*
** Configure debugging options on SmtpSession. Add all bits in
** smtpFlags to the settings. The following bits can be added:
**
** SMTP_FLAG_FILE: In which case pArg is the FILE* pointer to use
**
** SMTP_FLAG_BLOB: In which case pArg is the Blob* poitner to use.
*/
void smtp_session_config(SmtpSession *p, u32 smtpFlags, void *pArg){
p->smtpFlags = smtpFlags;
if( smtpFlags & SMTP_TRACE_FILE ){
p->logFile = (FILE*)pArg;
}else if( smtpFlags & SMTP_TRACE_BLOB ){
p->pTranscript = (Blob*)pArg;
}
}
/*
** Send a single line of output the SMTP client to the server.
*/
static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
Blob b = empty_blob;
va_list ap;
char *z;
int n;
if( !p->bOpen ) return;
va_start(ap, zFormat);
blob_vappendf(&b, zFormat, ap);
va_end(ap);
z = blob_buffer(&b);
n = blob_size(&b);
assert( n>=2 );
assert( z[n-1]=='\n' );
|
| ︙ | ︙ | |||
289 290 291 292 293 294 295 |
static void smtp_recv_line(SmtpSession *p, Blob *in){
int n = blob_size(&p->inbuf);
char *z = blob_buffer(&p->inbuf);
int i = blob_tell(&p->inbuf);
int nDelay = 0;
if( i<n && z[n-1]=='\n' ){
blob_line(&p->inbuf, in);
| | | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
static void smtp_recv_line(SmtpSession *p, Blob *in){
int n = blob_size(&p->inbuf);
char *z = blob_buffer(&p->inbuf);
int i = blob_tell(&p->inbuf);
int nDelay = 0;
if( i<n && z[n-1]=='\n' ){
blob_line(&p->inbuf, in);
}else if( !p->bOpen ){
blob_init(in, 0, 0);
}else{
if( n>0 && i>=n ){
blob_truncate(&p->inbuf, 0);
blob_rewind(&p->inbuf);
n = 0;
}
|
| ︙ | ︙ | |||
312 313 314 315 316 317 318 |
z[n] = 0;
if( n>0 && z[n-1]=='\n' ) break;
if( got==1000 ) continue;
}
nDelay++;
if( nDelay>100 ){
blob_init(in, 0, 0);
| | < < | 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
z[n] = 0;
if( n>0 && z[n-1]=='\n' ) break;
if( got==1000 ) continue;
}
nDelay++;
if( nDelay>100 ){
blob_init(in, 0, 0);
smtp_set_error(p, 1, "client times out waiting on server response");
return;
}else{
sqlite3_sleep(100);
}
}while( n<1 || z[n-1]!='\n' );
blob_truncate(&p->inbuf, n);
blob_line(&p->inbuf, in);
|
| ︙ | ︙ | |||
352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
int *pbMore, /* True if the reply is not complete */
char **pzArg /* Argument */
){
int n;
char *z;
blob_truncate(in, 0);
smtp_recv_line(p, in);
z = blob_str(in);
n = blob_size(in);
if( z[0]=='#' ){
*piCode = 0;
*pbMore = 1;
*pzArg = z;
}else{
| > | 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
int *pbMore, /* True if the reply is not complete */
char **pzArg /* Argument */
){
int n;
char *z;
blob_truncate(in, 0);
smtp_recv_line(p, in);
blob_trim(in);
z = blob_str(in);
n = blob_size(in);
if( z[0]=='#' ){
*piCode = 0;
*pbMore = 1;
*pzArg = z;
}else{
|
| ︙ | ︙ | |||
373 374 375 376 377 378 379 |
** Have the client send a QUIT message.
*/
int smtp_client_quit(SmtpSession *p){
Blob in = BLOB_INITIALIZER;
int iCode = 0;
int bMore = 0;
char *zArg = 0;
| > | | | | | | > | > > > > > > > > > > | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
** Have the client send a QUIT message.
*/
int smtp_client_quit(SmtpSession *p){
Blob in = BLOB_INITIALIZER;
int iCode = 0;
int bMore = 0;
char *zArg = 0;
if( p->bOpen ){
smtp_send_line(p, "QUIT\r\n");
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
p->bOpen = 0;
socket_close();
}
return 0;
}
/*
** Begin a client SMTP session. Wait for the initial 220 then send
** the EHLO and wait for a 250.
**
** Return 0 on success and non-zero for a failure.
*/
static int smtp_client_startup(SmtpSession *p){
Blob in = BLOB_INITIALIZER;
int iCode = 0;
int bMore = 0;
char *zArg = 0;
if( p==0 || p->bFatal ) return 1;
if( socket_open(&p->url) ){
smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg());
return 1;
}
p->bOpen = 1;
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=220 ){
smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg);
smtp_client_quit(p);
return 1;
}
smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=250 ){
smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg);
smtp_client_quit(p);
return 1;
}
fossil_free(p->zErr);
p->zErr = 0;
return 0;
}
/*
** COMMAND: test-smtp-probe
**
** Usage: %fossil test-smtp-probe DOMAIN [ME]
|
| ︙ | ︙ | |||
539 540 541 542 543 544 545 546 547 548 549 |
){
int i;
int iCode = 0;
int bMore = 0;
char *zArg = 0;
Blob in;
blob_init(&in, 0, 0);
smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
| > > > > | > > > | > > > | > > > | > > > > | 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
){
int i;
int iCode = 0;
int bMore = 0;
char *zArg = 0;
Blob in;
blob_init(&in, 0, 0);
if( !p->bOpen ){
if( !p->bFatal ) smtp_client_startup(p);
if( !p->bOpen ) return 1;
}
smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=250 ){
smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg);
return 1;
}
for(i=0; i<nTo; i++){
smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=250 ){
smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg);
return 1;
}
}
smtp_send_line(p, "DATA\r\n");
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=354 ){
smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg);
return 1;
}
smtp_send_email_body(zMsg, socket_send, 0);
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
fossil_print("C: # message content\nC: .\n");
}
if( p->smtpFlags & SMTP_TRACE_FILE ){
fprintf(p->logFile, "C: # message content\nC: .\n");
}
if( p->smtpFlags & SMTP_TRACE_BLOB ){
blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
}
do{
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
}while( bMore );
if( iCode!=250 ){
smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"",
iCode, zArg);
return 1;
}
return 0;
}
/*
** The input is a base email address of the form "local@domain".
** Return a pointer to just the "domain" part, or 0 if the string
** contains no "@".
|
| ︙ | ︙ | |||
634 635 636 637 638 639 640 |
zToDomain = domain_of_addr(azTo[0]);
}
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
if( p->zErr ){
fossil_fatal("%s", p->zErr);
}
fossil_print("Connection to \"%s\"\n", p->zHostname);
| < | 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 |
zToDomain = domain_of_addr(azTo[0]);
}
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
if( p->zErr ){
fossil_fatal("%s", p->zErr);
}
fossil_print("Connection to \"%s\"\n", p->zHostname);
smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
smtp_client_quit(p);
if( p->zErr ){
fossil_fatal("ERROR: %s\n", p->zErr);
}
smtp_session_free(p);
blob_reset(&body);
}
|
Changes to src/sorttable.js.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* Javascript code that will enables sorting of the table. This code is ** derived from ** ** http://www.webtoolkit.info/sortable-html-table.html ** ** but with extensive modifications. ** ** All tables with class "sortable" are registered with the SortableTable() ** function. Example: ** ** <table class='sortable' data-column-types='tnkx' data-init-sort='2'> ** ** Column data types are determined by the data-column-types attribute of | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* Javascript code that will enables sorting of the table. This code is ** derived from ** ** http://www.webtoolkit.info/sortable-html-table.html ** ** but with extensive modifications. ** ** All tables with class "sortable" are registered with the SortableTable() ** function. Example: ** ** <table class='sortable' data-column-types='tnkx' data-init-sort='2'> ** ** Column data types are determined by the data-column-types attribute of ** the table. The value of data-column-types is a string where each ** character of the string represents a datatype for one column in the ** table. ** ** t Sort by text ** n Sort numerically ** k Sort by the data-sortkey property ** x This column is not sortable |
| ︙ | ︙ | |||
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
var clsName = hdrCell.className.replace(/\s*\bsort\s*\w+/, '');
clsName += ' sort ' + sortType;
hdrCell.className = clsName;
}
}
this.sortText = function(a,b) {
var i = thisObject.sortIndex;
aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
if(aa<bb) return -1;
if(aa==bb) return a.rowIndex-b.rowIndex;
return 1;
}
this.sortReverseText = function(a,b) {
var i = thisObject.sortIndex;
aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
if(aa<bb) return +1;
if(aa==bb) return a.rowIndex-b.rowIndex;
return -1;
}
this.sortNumeric = function(a,b) {
| > > > > | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
var clsName = hdrCell.className.replace(/\s*\bsort\s*\w+/, '');
clsName += ' sort ' + sortType;
hdrCell.className = clsName;
}
}
this.sortText = function(a,b) {
var i = thisObject.sortIndex;
if (a.cells.length<=i) return -1; /* see ticket 59d699710b1ab5d4 */
if (b.cells.length<=i) return 1;
aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
if(aa<bb) return -1;
if(aa==bb) return a.rowIndex-b.rowIndex;
return 1;
}
this.sortReverseText = function(a,b) {
var i = thisObject.sortIndex;
if (a.cells.length<=i) return 1; /* see ticket 59d699710b1ab5d4 */
if (b.cells.length<=i) return -1;
aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
if(aa<bb) return +1;
if(aa==bb) return a.rowIndex-b.rowIndex;
return -1;
}
this.sortNumeric = function(a,b) {
|
| ︙ | ︙ |
Changes to src/stash.c.
| ︙ | ︙ | |||
257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
*/
static int stash_create(void){
const char *zComment; /* Comment to add to the stash */
int stashid; /* ID of the new stash */
int vid; /* Current check-out */
zComment = find_option("comment", "m", 1);
verify_all_options();
if( zComment==0 ){
Blob prompt; /* Prompt for stash comment */
Blob comment; /* User comment reply */
#if defined(_WIN32) || defined(__CYGWIN__)
int bomSize;
const unsigned char *bom = get_utf8_bom(&bomSize);
| > | 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
*/
static int stash_create(void){
const char *zComment; /* Comment to add to the stash */
int stashid; /* ID of the new stash */
int vid; /* Current check-out */
zComment = find_option("comment", "m", 1);
(void)fossil_text_editor();
verify_all_options();
if( zComment==0 ){
Blob prompt; /* Prompt for stash comment */
Blob comment; /* User comment reply */
#if defined(_WIN32) || defined(__CYGWIN__)
int bomSize;
const unsigned char *bom = get_utf8_bom(&bomSize);
|
| ︙ | ︙ | |||
506 507 508 509 510 511 512 | /* ** COMMAND: stash ** ** Usage: %fossil stash SUBCOMMAND ARGS... ** ** > fossil stash | | | > > > > > | 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | /* ** COMMAND: stash ** ** Usage: %fossil stash SUBCOMMAND ARGS... ** ** > fossil stash ** > fossil stash save ?FILES...? ** > fossil stash snapshot ?FILES...? ** ** Save the current changes in the working tree as a new stash. ** Then revert the changes back to the last check-in. If FILES ** are listed, then only stash and revert the named files. The ** "save" verb can be omitted if and only if there are no other ** arguments. The "snapshot" verb works the same as "save" but ** omits the revert, keeping the check-out unchanged. ** ** Options: ** --editor NAME Use the NAME editor to enter comment ** -m|--comment COMMENT Comment text for the new stash ** ** ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM? ** ** List all changes sets currently stashed. Show information about ** individual files in each changeset if -v or --verbose is used. ** ** > fossil stash show|cat ?STASHID? ?DIFF-OPTIONS? |
| ︙ | ︙ |
Changes to src/stat.c.
| ︙ | ︙ | |||
164 165 166 167 168 169 170 |
style_submenu_element("Activity Reports", "reports");
style_submenu_element("Hash Collisions", "hash-collisions");
style_submenu_element("Artifacts", "bloblist");
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
style_submenu_element("Table Sizes", "repo-tabsize");
}
if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
| | | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
style_submenu_element("Activity Reports", "reports");
style_submenu_element("Hash Collisions", "hash-collisions");
style_submenu_element("Artifacts", "bloblist");
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
style_submenu_element("Table Sizes", "repo-tabsize");
}
if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
style_submenu_element("Environment", "test-env");
}
@ <table class="label-value">
fsize = file_size(g.zRepositoryName, ExtFILE);
@ <tr><th>Repository Size:</th><td>%,lld(fsize) bytes</td>
@ </td></tr>
if( !brief ){
@ <tr><th>Number Of Artifacts:</th><td>
|
| ︙ | ︙ |
Changes to src/statrep.c.
| ︙ | ︙ | |||
857 858 859 860 861 862 863 864 865 866 867 868 869 870 | ** * m (merge check-in), ** * n (non-merge check-in) ** * f (forum post) ** * w (wiki page change) ** * t (ticket change) ** * g (tag added or removed) ** Defaulting to all event types. ** ** The view-specific query parameters include: ** ** view=byweek: ** ** y=YYYY The year to report (default is the server's ** current year). | > > > | 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 | ** * m (merge check-in), ** * n (non-merge check-in) ** * f (forum post) ** * w (wiki page change) ** * t (ticket change) ** * g (tag added or removed) ** Defaulting to all event types. ** from=DATETIME Consider only events after this timestamp (requires to) ** to=DATETIME Consider only events before this timestamp (requires from) ** ** ** The view-specific query parameters include: ** ** view=byweek: ** ** y=YYYY The year to report (default is the server's ** current year). |
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
742 743 744 745 746 747 748 |
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
** allows it to be properly overridden via the TH1 setup script (i.e. it
** is evaluated before the header is rendered).
*/
Th_MaybeStore("default_csp", zDfltCsp);
fossil_free(zDfltCsp);
Th_Store("nonce", zNonce);
| > | | | | 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 |
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
** allows it to be properly overridden via the TH1 setup script (i.e. it
** is evaluated before the header is rendered).
*/
Th_MaybeStore("default_csp", zDfltCsp);
fossil_free(zDfltCsp);
Th_Store("nonce", zNonce);
Th_StoreUnsafe("project_name",
db_get("project-name","Unnamed Fossil Project"));
Th_StoreUnsafe("project_description", db_get("project-description",""));
if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
Th_Store("baseurl", g.zBaseURL);
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
Th_Store("home", g.zTop);
Th_Store("index_page", db_get("index-page","/home"));
if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
Th_Store("current_page", local_zCurrentPage);
if( g.zPath ){ /* store the first segment of a path; */
|
| ︙ | ︙ | |||
770 771 772 773 774 775 776 |
Th_Store("manifest_date", MANIFEST_DATE);
Th_Store("compiler_name", COMPILER_NAME);
Th_Store("mainmenu", style_get_mainmenu());
stylesheet_url_var();
image_url_var("logo");
image_url_var("background");
if( !login_is_nobody() ){
| | | 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 |
Th_Store("manifest_date", MANIFEST_DATE);
Th_Store("compiler_name", COMPILER_NAME);
Th_Store("mainmenu", style_get_mainmenu());
stylesheet_url_var();
image_url_var("logo");
image_url_var("background");
if( !login_is_nobody() ){
Th_Store("login", html_lookalike(g.zLogin,-1));
}
Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
g.ftntsIssues[2] || g.ftntsIssues[3] ){
char buf[80];
sqlite3_snprintf(sizeof(buf), buf, "%i %i %i %i", g.ftntsIssues[0],
g.ftntsIssues[1], g.ftntsIssues[2], g.ftntsIssues[3]);
|
| ︙ | ︙ | |||
1380 1381 1382 1383 1384 1385 1386 | @ Title: <input type="text" size="50" name="title" value="%h(zTitle)"> @ <input type="submit" value="Submit"> @ </form> style_finish_page(); } /* | > | | 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 |
@ Title: <input type="text" size="50" name="title" value="%h(zTitle)">
@ <input type="submit" value="Submit">
@ </form>
style_finish_page();
}
/*
** WEBPAGE: test-env
** WEBPAGE: test_env alias
**
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){
webpage_error("");
}
|
| ︙ | ︙ | |||
1443 1444 1445 1446 1447 1448 1449 | ** query parameters can jump to this routine to render an error ** message screen. ** ** For administators, or if the test_env_enable setting is true, then ** details of the request environment are displayed. Otherwise, just ** the error message is shown. ** | | | 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 |
** query parameters can jump to this routine to render an error
** message screen.
**
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed. Otherwise, just
** the error message is shown.
**
** If zFormat is an empty string, then this is the /test-env page.
*/
void webpage_error(const char *zFormat, ...){
int showAll = 0;
char *zErr = 0;
int isAuth = 0;
char zCap[100];
|
| ︙ | ︙ | |||
1543 1544 1545 1546 1547 1548 1549 |
blob_zero(&t);
}
}
@ <hr>
P("HTTP_USER_AGENT");
P("SERVER_SOFTWARE");
cgi_print_all(showAll, 0, 0);
| | | 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 |
blob_zero(&t);
}
}
@ <hr>
P("HTTP_USER_AGENT");
P("SERVER_SOFTWARE");
cgi_print_all(showAll, 0, 0);
@ <p><form method="POST" action="%R/test-env">
@ <input type="hidden" name="showall" value="%d(showAll)">
@ <input type="submit" name="post-test-button" value="POST Test">
@ </form>
if( showAll && blob_size(&g.httpHeader)>0 ){
@ <hr>
@ <pre>
@ %h(blob_str(&g.httpHeader))
|
| ︙ | ︙ |
Changes to src/style.chat.css.
| ︙ | ︙ | |||
211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
overflow: auto;
padding: 0 0.25em;
}
body.chat #chat-messages-wrapper.loading > * {
/* An attempt at reducing flicker when loading lots of messages. */
visibility: hidden;
}
body.chat div.content {
margin: 0;
padding: 0;
display: flex;
flex-direction: column-reverse;
/* ^^^^ In order to get good automatic scrolling of new messages on
the BOTTOM in bottom-up chat mode, such that they scroll up
| > > > > > > > > > | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
overflow: auto;
padding: 0 0.25em;
}
body.chat #chat-messages-wrapper.loading > * {
/* An attempt at reducing flicker when loading lots of messages. */
visibility: hidden;
}
/* Provide a visual cue when polling is offline. */
body.chat.connection-error #chat-input-line-wrapper {
border-top: medium dotted red;
}
body.chat.fossil-dark-style.connection-error #chat-input-line-wrapper {
border-color: yellow;
}
body.chat div.content {
margin: 0;
padding: 0;
display: flex;
flex-direction: column-reverse;
/* ^^^^ In order to get good automatic scrolling of new messages on
the BOTTOM in bottom-up chat mode, such that they scroll up
|
| ︙ | ︙ | |||
239 240 241 242 243 244 245 |
}
body.chat:not(.chat-only-mode) #chat-input-area{
/* Safari user reports that 2em is necessary to keep the file selection
widget from overlapping the page footer, whereas a margin of 0 is fine
for FF/Chrome (and 2em is a *huge* waste of space for those). */
margin-bottom: 0;
}
| > | | | | | | | | 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
}
body.chat:not(.chat-only-mode) #chat-input-area{
/* Safari user reports that 2em is necessary to keep the file selection
widget from overlapping the page footer, whereas a margin of 0 is fine
for FF/Chrome (and 2em is a *huge* waste of space for those). */
margin-bottom: 0;
}
body.chat .chat-input-field {
flex: 10 1 auto;
margin: 0;
}
body.chat #chat-input-field-x,
body.chat #chat-input-field-multi {
overflow: auto;
resize: vertical;
}
body.chat #chat-input-field-x {
display: inline-block/*supposed workaround for Chrome weirdness*/;
padding: 0.2em;
background-color: rgba(156,156,156,0.3);
white-space: pre-wrap;
/* ^^^ Firefox, when pasting plain text into a contenteditable field,
loses all newlines unless we explicitly set this. Chrome does not. */
cursor: text;
/* ^^^ In some browsers the cursor may not change for a contenteditable
element until it has focus, causing potential confusion. */
}
body.chat #chat-input-field-x:empty::before {
content: attr(data-placeholder);
opacity: 0.6;
}
body.chat .chat-input-field:not(:focus){
border-width: 1px;
border-style: solid;
border-radius: 0.25em;
}
body.chat .chat-input-field:focus{
/* This transparent border helps avoid the text shifting around
when the contenteditable attribute causes a border (which we
apparently cannot style) to be added. */
border-width: 1px;
border-style: solid;
border-color: transparent;
border-radius: 0.25em;
|
| ︙ | ︙ |
Changes to src/th.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* ** The implementation of the TH core. This file contains the parser, and ** the implementation of the interface in th.h. */ #include "config.h" #include "th.h" #include <string.h> #include <assert.h> /* ** Values used for element values in the tcl_platform array. */ #if !defined(TH_ENGINE) # define TH_ENGINE "TH1" #endif | > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* ** The implementation of the TH core. This file contains the parser, and ** the implementation of the interface in th.h. */ #include "config.h" #include "th.h" #include <string.h> #include <assert.h> /* ** External routines */ void fossil_panic(const char*,...); void fossil_errorlog(const char*,...); /* ** Values used for element values in the tcl_platform array. */ #if !defined(TH_ENGINE) # define TH_ENGINE "TH1" #endif |
| ︙ | ︙ | |||
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
** The Buffer structure and the thBufferXXX() functions are used to make
** memory allocation easier when building up a result.
*/
struct Buffer {
char *zBuf;
int nBuf;
int nBufAlloc;
};
typedef struct Buffer Buffer;
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);
/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void th_memcpy(void *dest, const void *src, size_t n){
if( n>0 ) memcpy(dest,src,n);
}
/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static void thBufferWriteResize(
Th_Interp *interp,
Buffer *pBuffer,
const char *zAdd,
| > > > > > > > > > | > > > > | > | > | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
** The Buffer structure and the thBufferXXX() functions are used to make
** memory allocation easier when building up a result.
*/
struct Buffer {
char *zBuf;
int nBuf;
int nBufAlloc;
int bTaint;
};
typedef struct Buffer Buffer;
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);
/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void th_memcpy(void *dest, const void *src, size_t n){
if( n>0 ) memcpy(dest,src,n);
}
/*
** An oversized string has been encountered. Do not try to recover.
** Panic the process.
*/
void Th_OversizeString(void){
fossil_panic("string too large. maximum size 286MB.");
}
/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static void thBufferWriteResize(
Th_Interp *interp,
Buffer *pBuffer,
const char *zAdd,
int nAddX
){
int nAdd = TH1_LEN(nAddX);
int nNew = (pBuffer->nBuf+nAdd)*2+32;
#if defined(TH_MEMDEBUG)
char *zNew = (char *)Th_Malloc(interp, nNew);
TH1_SIZECHECK(nNew);
th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
Th_Free(interp, pBuffer->zBuf);
pBuffer->zBuf = zNew;
#else
int nOld = pBuffer->nBufAlloc;
TH1_SIZECHECK(nNew);
pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
#endif
pBuffer->nBufAlloc = nNew;
th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
pBuffer->nBuf += nAdd;
TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
}
static void thBufferWriteFast(
Th_Interp *interp,
Buffer *pBuffer,
const char *zAdd,
int nAddX
){
int nAdd = TH1_LEN(nAddX);
if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
}else{
if( pBuffer->zBuf ){
memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
}
pBuffer->nBuf += nAdd;
TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
}
}
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)
/*
** Add a single character to a buffer
*/
|
| ︙ | ︙ | |||
702 703 704 705 706 707 708 709 710 711 |
Th_Interp *interp,
const char *zWord,
int nWord
){
int rc = TH_OK;
Buffer output;
int i;
thBufferInit(&output);
| > | | | | | | 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
Th_Interp *interp,
const char *zWord,
int nWord
){
int rc = TH_OK;
Buffer output;
int i;
int nn = TH1_LEN(nWord);
thBufferInit(&output);
if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
thBufferWrite(interp, &output, &zWord[1], nn-2);
}else{
/* If the word is surrounded by double-quotes strip these away. */
if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
zWord++;
nn -= 2;
}
for(i=0; rc==TH_OK && i<nn; i++){
int nGet;
int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
int (*xSubst)(Th_Interp *, const char*, int) = 0;
switch( zWord[i] ){
case '\\':
|
| ︙ | ︙ | |||
741 742 743 744 745 746 747 |
}
default: {
thBufferAddChar(interp, &output, zWord[i]);
continue; /* Go to the next iteration of the for(...) loop */
}
}
| | | | 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
}
default: {
thBufferAddChar(interp, &output, zWord[i]);
continue; /* Go to the next iteration of the for(...) loop */
}
}
rc = xGet(interp, &zWord[i], nn-i, &nGet);
if( rc==TH_OK ){
rc = xSubst(interp, &zWord[i], nGet);
}
if( rc==TH_OK ){
const char *zRes;
int nRes;
zRes = Th_GetResult(interp, &nRes);
thBufferWrite(interp, &output, zRes, nRes);
i += (nGet-1);
}
}
}
if( rc==TH_OK ){
Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
}
thBufferFree(interp, &output);
return rc;
}
/*
** Return true if one of the following is true of the buffer pointed
|
| ︙ | ︙ | |||
824 825 826 827 828 829 830 | int rc = TH_OK; Buffer strbuf; Buffer lenbuf; int nCount = 0; const char *zInput = zList; | | | | | | 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 |
int rc = TH_OK;
Buffer strbuf;
Buffer lenbuf;
int nCount = 0;
const char *zInput = zList;
int nInput = TH1_LEN(nList);
thBufferInit(&strbuf);
thBufferInit(&lenbuf);
while( nInput>0 ){
const char *zWord;
int nWord;
thNextSpace(interp, zInput, nInput, &nWord);
zInput += nWord;
nInput = TH1_LEN(nList)-(zInput-zList);
if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
|| TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
){
goto finish;
}
zInput = &zInput[TH1_LEN(nWord)];
nInput = TH1_LEN(nList)-(zInput-zList);
if( nWord>0 ){
zWord = Th_GetResult(interp, &nWord);
thBufferWrite(interp, &strbuf, zWord, nWord);
thBufferAddChar(interp, &strbuf, 0);
thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
nCount++;
}
|
| ︙ | ︙ | |||
870 871 872 873 874 875 876 |
);
anElem = (int *)&azElem[nCount];
zElem = (char *)&anElem[nCount];
th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
for(i=0; i<nCount;i++){
azElem[i] = zElem;
| | | 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 |
);
anElem = (int *)&azElem[nCount];
zElem = (char *)&anElem[nCount];
th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
for(i=0; i<nCount;i++){
azElem[i] = zElem;
zElem += (TH1_LEN(anElem[i]) + 1);
}
*pazElem = azElem;
*panElem = anElem;
}
if( pnCount ){
*pnCount = nCount;
}
|
| ︙ | ︙ | |||
892 893 894 895 896 897 898 |
/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
int rc = TH_OK;
const char *zInput = zProgram;
| | > > > > > | 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 |
/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
int rc = TH_OK;
const char *zInput = zProgram;
int nInput = TH1_LEN(nProgram);
if( TH1_TAINTED(nProgram)
&& Th_ReportTaint(interp, "script", zProgram, nProgram)
){
return TH_ERROR;
}
while( rc==TH_OK && nInput ){
Th_HashEntry *pEntry;
int nSpace;
const char *zFirst;
char **argv;
int *argl;
|
| ︙ | ︙ | |||
947 948 949 950 951 952 953 |
*/
rc = thSplitList(interp, zFirst, zInput-zFirst, &argv, &argl, &argc);
if( rc!=TH_OK ) continue;
if( argc>0 ){
/* Look up the command name in the command hash-table. */
| | | | 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 |
*/
rc = thSplitList(interp, zFirst, zInput-zFirst, &argv, &argl, &argc);
if( rc!=TH_OK ) continue;
if( argc>0 ){
/* Look up the command name in the command hash-table. */
pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
if( !pEntry ){
Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
rc = TH_ERROR;
}
/* Call the command procedure. */
if( rc==TH_OK ){
Th_Command *p = (Th_Command *)(pEntry->pData);
const char **azArg = (const char **)argv;
|
| ︙ | ︙ | |||
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 |
if( !interp->pFrame ){
rc = TH_ERROR;
}else{
int nInput = nProgram;
if( nInput<0 ){
nInput = th_strlen(zProgram);
}
rc = thEvalLocal(interp, zProgram, nInput);
}
interp->pFrame = pSavedFrame;
return rc;
}
| > > | 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 |
if( !interp->pFrame ){
rc = TH_ERROR;
}else{
int nInput = nProgram;
if( nInput<0 ){
nInput = th_strlen(zProgram);
}else{
nInput = TH1_LEN(nInput);
}
rc = thEvalLocal(interp, zProgram, nInput);
}
interp->pFrame = pSavedFrame;
return rc;
}
|
| ︙ | ︙ | |||
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 |
const char *zInner = 0;
int nInner = 0;
int isGlobal = 0;
int i;
if( nVarname<0 ){
nVarname = th_strlen(zVarname);
}
nOuter = nVarname;
/* If the variable name starts with "::", then do the lookup is in the
** uppermost (global) frame.
*/
if( nVarname>2 && zVarname[0]==':' && zVarname[1]==':' ){
| > > | 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 |
const char *zInner = 0;
int nInner = 0;
int isGlobal = 0;
int i;
if( nVarname<0 ){
nVarname = th_strlen(zVarname);
}else{
nVarname = TH1_LEN(nVarname);
}
nOuter = nVarname;
/* If the variable name starts with "::", then do the lookup is in the
** uppermost (global) frame.
*/
if( nVarname>2 && zVarname[0]==':' && zVarname[1]==':' ){
|
| ︙ | ︙ | |||
1269 1270 1271 1272 1273 1274 1275 |
Th_ErrorMessage(interp, "no such variable:", zVar, nVar);
return TH_ERROR;
}
return Th_SetResult(interp, pValue->zData, pValue->nData);
}
| < < < < < < < < < < < < < < < < < < < < < | 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 |
Th_ErrorMessage(interp, "no such variable:", zVar, nVar);
return TH_ERROR;
}
return Th_SetResult(interp, pValue->zData, pValue->nData);
}
/*
** Return true if variable (zVar, nVar) exists.
*/
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
return pValue && (pValue->zData || pValue->pHash);
}
|
| ︙ | ︙ | |||
1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 |
Th_Interp *interp,
const char *zVar,
int nVar,
const char *zValue,
int nValue
){
Th_Variable *pValue;
pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
if( !pValue ){
return TH_ERROR;
}
if( nValue<0 ){
| > > | > > | | | | | 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 |
Th_Interp *interp,
const char *zVar,
int nVar,
const char *zValue,
int nValue
){
Th_Variable *pValue;
int nn;
nVar = TH1_LEN(nVar);
pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
if( !pValue ){
return TH_ERROR;
}
if( nValue<0 ){
nn = th_strlen(zValue);
}else{
nn = TH1_LEN(nValue);
}
if( pValue->zData ){
Th_Free(interp, pValue->zData);
pValue->zData = 0;
}
assert(zValue || nn==0);
pValue->zData = Th_Malloc(interp, nn+1);
pValue->zData[nn] = '\0';
th_memcpy(pValue->zData, zValue, nn);
pValue->nData = nValue;
return TH_OK;
}
/*
** Create a variable link so that accessing variable (zLocal, nLocal) is
|
| ︙ | ︙ | |||
1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 |
** caller is responsible for eventually calling Th_Free() to free
** the returned buffer.
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
char *zRes;
if( n<0 ){
n = th_strlen(z);
}
zRes = Th_Malloc(interp, n+1);
th_memcpy(zRes, z, n);
zRes[n] = '\0';
return zRes;
}
| > > | 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 |
** caller is responsible for eventually calling Th_Free() to free
** the returned buffer.
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
char *zRes;
if( n<0 ){
n = th_strlen(z);
}else{
n = TH1_LEN(n);
}
zRes = Th_Malloc(interp, n+1);
th_memcpy(zRes, z, n);
zRes[n] = '\0';
return zRes;
}
|
| ︙ | ︙ | |||
1517 1518 1519 1520 1521 1522 1523 |
if( n<0 ){
n = th_strlen(z);
}
if( z && n>0 ){
char *zResult;
| > | | | | 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 |
if( n<0 ){
n = th_strlen(z);
}
if( z && n>0 ){
char *zResult;
int nn = TH1_LEN(n);
zResult = Th_Malloc(pInterp, nn+1);
th_memcpy(zResult, z, nn);
zResult[nn] = '\0';
pInterp->zResult = zResult;
pInterp->nResult = n;
}
return TH_OK;
}
|
| ︙ | ︙ | |||
1775 1776 1777 1778 1779 1780 1781 |
int i;
int hasSpecialChar = 0; /* Whitespace or {}[]'" */
int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
int nBrace = 0;
output.zBuf = *pzList;
| | > > > > | 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 |
int i;
int hasSpecialChar = 0; /* Whitespace or {}[]'" */
int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */
int nBrace = 0;
output.zBuf = *pzList;
output.nBuf = TH1_LEN(*pnList);
output.nBufAlloc = output.nBuf;
output.bTaint = 0;
TH1_XFER_TAINT(output.bTaint, *pnList);
if( nElem<0 ){
nElem = th_strlen(zElem);
}else{
nElem = TH1_LEN(nElem);
}
if( output.nBuf>0 ){
thBufferAddChar(interp, &output, ' ');
}
for(i=0; i<nElem; i++){
char c = zElem[i];
|
| ︙ | ︙ | |||
1832 1833 1834 1835 1836 1837 1838 |
Th_Interp *interp, /* Interpreter context */
char **pzStr, /* IN/OUT: Ptr to ptr to list */
int *pnStr, /* IN/OUT: Current length of *pzStr */
const char *zElem, /* Data to append */
int nElem /* Length of nElem */
){
char *zNew;
| | > | > > | > | | | 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 |
Th_Interp *interp, /* Interpreter context */
char **pzStr, /* IN/OUT: Ptr to ptr to list */
int *pnStr, /* IN/OUT: Current length of *pzStr */
const char *zElem, /* Data to append */
int nElem /* Length of nElem */
){
char *zNew;
long long int nNew;
int nn;
if( nElem<0 ){
nn = th_strlen(zElem);
}else{
nn = TH1_LEN(nElem);
}
nNew = TH1_LEN(*pnStr) + nn;
TH1_SIZECHECK(nNew);
zNew = Th_Malloc(interp, nNew);
th_memcpy(zNew, *pzStr, *pnStr);
th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);
Th_Free(interp, *pzStr);
*pzStr = zNew;
*pnStr = (int)nNew;
return TH_OK;
}
/*
** Initialize an interpreter.
*/
|
| ︙ | ︙ | |||
2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 |
char *zRight = 0; int nRight = 0;
/* Evaluate left and right arguments, if they exist. */
if( pExpr->pLeft ){
rc = exprEval(interp, pExpr->pLeft);
if( rc==TH_OK ){
zLeft = Th_TakeResult(interp, &nLeft);
}
}
if( rc==TH_OK && pExpr->pRight ){
rc = exprEval(interp, pExpr->pRight);
if( rc==TH_OK ){
zRight = Th_TakeResult(interp, &nRight);
}
}
/* Convert arguments to their required forms. */
if( rc==TH_OK ){
eArgType = pExpr->pOp->eArgType;
if( eArgType==ARG_NUMBER ){
| > > | 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 |
char *zRight = 0; int nRight = 0;
/* Evaluate left and right arguments, if they exist. */
if( pExpr->pLeft ){
rc = exprEval(interp, pExpr->pLeft);
if( rc==TH_OK ){
zLeft = Th_TakeResult(interp, &nLeft);
nLeft = TH1_LEN(nLeft);
}
}
if( rc==TH_OK && pExpr->pRight ){
rc = exprEval(interp, pExpr->pRight);
if( rc==TH_OK ){
zRight = Th_TakeResult(interp, &nRight);
nRight = TH1_LEN(nRight);
}
}
/* Convert arguments to their required forms. */
if( rc==TH_OK ){
eArgType = pExpr->pOp->eArgType;
if( eArgType==ARG_NUMBER ){
|
| ︙ | ︙ | |||
2158 2159 2160 2161 2162 2163 2164 |
rc = TH_ERROR;
goto finish;
}
iRes = iLeft%iRight;
break;
case OP_ADD: iRes = iLeft+iRight; break;
case OP_SUBTRACT: iRes = iLeft-iRight; break;
| | > > > | | 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 |
rc = TH_ERROR;
goto finish;
}
iRes = iLeft%iRight;
break;
case OP_ADD: iRes = iLeft+iRight; break;
case OP_SUBTRACT: iRes = iLeft-iRight; break;
case OP_LEFTSHIFT: {
iRes = (int)(((unsigned int)iLeft)<<(iRight&0x1f));
break;
}
case OP_RIGHTSHIFT: iRes = iLeft>>(iRight&0x1f); break;
case OP_LT: iRes = iLeft<iRight; break;
case OP_GT: iRes = iLeft>iRight; break;
case OP_LE: iRes = iLeft<=iRight; break;
case OP_GE: iRes = iLeft>=iRight; break;
case OP_EQ: iRes = iLeft==iRight; break;
case OP_NE: iRes = iLeft!=iRight; break;
case OP_BITWISE_AND: iRes = iLeft&iRight; break;
|
| ︙ | ︙ | |||
2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 |
int i; /* Loop counter */
int nToken = 0;
Expr **apToken = 0;
if( nExpr<0 ){
nExpr = th_strlen(zExpr);
}
/* Parse the expression to a list of tokens. */
rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
/* If the parsing was successful, create an expression tree from
** the parsed list of tokens. If successful, apToken[0] is set
| > > | 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 |
int i; /* Loop counter */
int nToken = 0;
Expr **apToken = 0;
if( nExpr<0 ){
nExpr = th_strlen(zExpr);
}else{
nExpr = TH1_LEN(nExpr);
}
/* Parse the expression to a list of tokens. */
rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);
/* If the parsing was successful, create an expression tree from
** the parsed list of tokens. If successful, apToken[0] is set
|
| ︙ | ︙ | |||
2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 |
unsigned int iKey = 0;
int i;
Th_HashEntry *pRet;
Th_HashEntry **ppRet;
if( nKey<0 ){
nKey = th_strlen(zKey);
}
for(i=0; i<nKey; i++){
iKey = (iKey<<3) ^ iKey ^ zKey[i];
}
iKey = iKey % TH_HASHSIZE;
| > > | 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 |
unsigned int iKey = 0;
int i;
Th_HashEntry *pRet;
Th_HashEntry **ppRet;
if( nKey<0 ){
nKey = th_strlen(zKey);
}else{
nKey = TH1_LEN(nKey);
}
for(i=0; i<nKey; i++){
iKey = (iKey<<3) ^ iKey ^ zKey[i];
}
iKey = iKey % TH_HASHSIZE;
|
| ︙ | ︙ | |||
2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 |
int i = 0;
int iOut = 0;
int base = 10;
int (*isdigit)(char) = th_isdigit;
if( n<0 ){
n = th_strlen(z);
}
if( n>1 && (z[0]=='-' || z[0]=='+') ){
i = 1;
}
if( (n-i)>2 && z[i]=='0' ){
if( z[i+1]=='x' || z[i+1]=='X' ){
| > > | 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 |
int i = 0;
int iOut = 0;
int base = 10;
int (*isdigit)(char) = th_isdigit;
if( n<0 ){
n = th_strlen(z);
}else{
n = TH1_LEN(n);
}
if( n>1 && (z[0]=='-' || z[0]=='+') ){
i = 1;
}
if( (n-i)>2 && z[i]=='0' ){
if( z[i+1]=='x' || z[i+1]=='X' ){
|
| ︙ | ︙ | |||
2854 2855 2856 2857 2858 2859 2860 |
int Th_ToDouble(
Th_Interp *interp,
const char *z,
int n,
double *pfOut
){
if( !sqlite3IsNumber((const char *)z, 0) ){
| | > > > | 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 |
int Th_ToDouble(
Th_Interp *interp,
const char *z,
int n,
double *pfOut
){
if( !sqlite3IsNumber((const char *)z, 0) ){
Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
return TH_ERROR;
}
sqlite3AtoF((const char *)z, pfOut);
return TH_OK;
}
/*
** Set the result of the interpreter to the th1 representation of
** the integer iVal and return TH_OK.
*/
int Th_SetResultInt(Th_Interp *interp, int iVal){
int isNegative = 0;
unsigned int uVal = iVal;
char zBuf[32];
char *z = &zBuf[32];
if( iVal<0 ){
if( iVal==0x80000000 ){
return Th_SetResult(interp, "-2147483648", -1);
}
isNegative = 1;
uVal = iVal * -1;
}
*(--z) = '\0';
*(--z) = (char)(48+(uVal%10));
while( (uVal = (uVal/10))>0 ){
*(--z) = (char)(48+(uVal%10));
|
| ︙ | ︙ |
Changes to src/th.h.
|
| < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/* This header file defines the external interface to the custom Scripting
** Language (TH) interpreter. TH is very similar to Tcl but is not an
** exact clone.
**
** TH1 was original developed to run SQLite tests on SymbianOS. This version
** of TH1 was repurposed as a scripted language for Fossil, and was heavily
** modified for that purpose, beginning in early 2008.
**
** More recently, TH1 has been enhanced to distinguish between regular text
** and "tainted" text. "Tainted" text is text that might have originated
** from an outside source and hence might not be trustworthy. To prevent
** cross-site scripting (XSS) and SQL-injections and similar attacks,
** tainted text should not be used for the following purposes:
**
** * executed as TH1 script or expression.
** * output as HTML or Javascript
** * used as part of an SQL query
**
** Tainted text can be converted into a safe form using commands like
** "htmlize". And some commands ("query" and "expr") know how to use
** potentially tainted variable values directly, and thus can bypass
** the restrictions above.
**
** Whether a string is clean or tainted is determined by its length integer.
** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
** (about 268MB - more than sufficient for the purposes of Fossil). The top
** bit of the length integer is the sign bit, of course. The next three bits
** are reserved. One of those, the 0x10000000 bit, marks tainted strings.
*/
#define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */
#define TH1_TAINT_BIT 0x10000000 /* The taint bit */
#define TH1_SIGN 0x80000000
/* Convert an integer into a string length. Negative values remain negative */
#define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X))
/* Return true if the string is tainted */
#define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0)
/* Remove taint from a string */
#define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT)
/* Add taint to a string */
#define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT)
/* If B is tainted, make A tainted too */
#define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B))
/* Check to see if a string is too big for TH1 */
#define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();}
void Th_OversizeString(void);
/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
typedef struct Th_Vtab Th_Vtab;
|
| ︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* ** Create and delete interpreters. */ Th_Interp * Th_CreateInterp(Th_Vtab *); void Th_DeleteInterp(Th_Interp *); /* ** Evaluate an TH program in the stack frame identified by parameter ** iFrame, according to the following rules: ** ** * If iFrame is 0, this means the current frame. ** ** * If iFrame is negative, then the nth frame up the stack, where n is | > > > > > > | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | /* ** Create and delete interpreters. */ Th_Interp * Th_CreateInterp(Th_Vtab *); void Th_DeleteInterp(Th_Interp *); /* ** Report taint in the string zStr,nStr. That string represents "zTitle" ** If non-zero is returned error out of the caller. */ int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr); /* ** Evaluate an TH program in the stack frame identified by parameter ** iFrame, according to the following rules: ** ** * If iFrame is 0, this means the current frame. ** ** * If iFrame is negative, then the nth frame up the stack, where n is |
| ︙ | ︙ | |||
54 55 56 57 58 59 60 | int Th_ExistsVar(Th_Interp *, const char *, int); int Th_ExistsArrayVar(Th_Interp *, const char *, int); int Th_GetVar(Th_Interp *, const char *, int); int Th_SetVar(Th_Interp *, const char *, int, const char *, int); int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); int Th_UnsetVar(Th_Interp *, const char *, int); | < < < < < < < < < < < < < | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | int Th_ExistsVar(Th_Interp *, const char *, int); int Th_ExistsArrayVar(Th_Interp *, const char *, int); int Th_GetVar(Th_Interp *, const char *, int); int Th_SetVar(Th_Interp *, const char *, int, const char *, int); int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); int Th_UnsetVar(Th_Interp *, const char *, int); typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); /* ** Register new commands. */ int Th_CreateCommand( Th_Interp *interp, |
| ︙ | ︙ |
Changes to src/th_lang.c.
| ︙ | ︙ | |||
37 38 39 40 41 42 43 |
return Th_WrongNumArgs(interp, "catch script ?varname?");
}
rc = Th_Eval(interp, 0, argv[1], -1);
if( argc==3 ){
int nResult;
const char *zResult = Th_GetResult(interp, &nResult);
| | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
return Th_WrongNumArgs(interp, "catch script ?varname?");
}
rc = Th_Eval(interp, 0, argv[1], -1);
if( argc==3 ){
int nResult;
const char *zResult = Th_GetResult(interp, &nResult);
Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
|
| ︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
char **azVar = 0;
int *anVar;
int nVar;
char **azValue = 0;
int *anValue;
int nValue;
int ii, jj;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "foreach varlist list script");
}
rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
if( rc ) return rc;
rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
for(jj=0; jj<nVar; jj++){
| > > > > | | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
char **azVar = 0;
int *anVar;
int nVar;
char **azValue = 0;
int *anValue;
int nValue;
int ii, jj;
int bTaint = 0;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "foreach varlist list script");
}
rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
if( rc ) return rc;
TH1_XFER_TAINT(bTaint, argl[2]);
rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
for(jj=0; jj<nVar; jj++){
int x = anValue[ii+jj];
TH1_XFER_TAINT(x, bTaint);
Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
}
rc = eval_loopbody(interp, argv[3], argl[3]);
}
if( rc==TH_BREAK ) rc = TH_OK;
Th_Free(interp, azVar);
Th_Free(interp, azValue);
return rc;
|
| ︙ | ︙ | |||
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
int argc,
const char **argv,
int *argl
){
char *zList = 0;
int nList = 0;
int i;
for(i=1; i<argc; i++){
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
}
Th_SetResult(interp, zList, nList);
Th_Free(interp, zList);
return TH_OK;
}
/*
| > > > | 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
int argc,
const char **argv,
int *argl
){
char *zList = 0;
int nList = 0;
int i;
int bTaint = 0;
for(i=1; i<argc; i++){
TH1_XFER_TAINT(bTaint,argl[i]);
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
}
TH1_XFER_TAINT(nList, bTaint);
Th_SetResult(interp, zList, nList);
Th_Free(interp, zList);
return TH_OK;
}
/*
|
| ︙ | ︙ | |||
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
int argc,
const char **argv,
int *argl
){
char *zList = 0;
int nList = 0;
int i, rc;
if( argc<2 ){
return Th_WrongNumArgs(interp, "lappend var ...");
}
rc = Th_GetVar(interp, argv[1], argl[1]);
if( rc==TH_OK ){
zList = Th_TakeResult(interp, &nList);
}
for(i=2; i<argc; i++){
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
}
Th_SetVar(interp, argv[1], argl[1], zList, nList);
Th_SetResult(interp, zList, nList);
Th_Free(interp, zList);
return TH_OK;
}
| > > > > | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
int argc,
const char **argv,
int *argl
){
char *zList = 0;
int nList = 0;
int i, rc;
int bTaint = 0;
if( argc<2 ){
return Th_WrongNumArgs(interp, "lappend var ...");
}
rc = Th_GetVar(interp, argv[1], argl[1]);
if( rc==TH_OK ){
zList = Th_TakeResult(interp, &nList);
}
TH1_XFER_TAINT(bTaint, nList);
for(i=2; i<argc; i++){
TH1_XFER_TAINT(bTaint, argl[i]);
Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
}
TH1_XFER_TAINT(nList, bTaint);
Th_SetVar(interp, argv[1], argl[1], zList, nList);
Th_SetResult(interp, zList, nList);
Th_Free(interp, zList);
return TH_OK;
}
|
| ︙ | ︙ | |||
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
){
int iElem;
int rc;
char **azElem;
int *anElem;
int nCount;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "lindex list index");
}
if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
return TH_ERROR;
}
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
if( rc==TH_OK ){
if( iElem<nCount && iElem>=0 ){
| > > > > | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
){
int iElem;
int rc;
char **azElem;
int *anElem;
int nCount;
int bTaint = 0;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "lindex list index");
}
if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
return TH_ERROR;
}
TH1_XFER_TAINT(bTaint, argl[1]);
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
if( rc==TH_OK ){
if( iElem<nCount && iElem>=0 ){
int sz = anElem[iElem];
TH1_XFER_TAINT(sz, bTaint);
Th_SetResult(interp, azElem[iElem], sz);
}else{
Th_SetResult(interp, 0, 0);
}
Th_Free(interp, azElem);
}
return rc;
|
| ︙ | ︙ | |||
354 355 356 357 358 359 360 361 362 |
if( argc!=3 ){
return Th_WrongNumArgs(interp, "lsearch list string");
}
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
if( rc==TH_OK ){
Th_SetResultInt(interp, -1);
for(i=0; i<nCount; i++){
| > | | 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
if( argc!=3 ){
return Th_WrongNumArgs(interp, "lsearch list string");
}
rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
if( rc==TH_OK ){
int nn = TH1_LEN(argl[2]);
Th_SetResultInt(interp, -1);
for(i=0; i<nCount; i++){
if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
Th_SetResultInt(interp, i);
break;
}
}
Th_Free(interp, azElem);
}
|
| ︙ | ︙ | |||
559 560 561 562 563 564 565 |
char *zUsage = 0; /* Build up a usage message here */
int nUsage = 0; /* Number of bytes at zUsage */
if( argc!=4 ){
return Th_WrongNumArgs(interp, "proc name arglist code");
}
| | > | | > | > | | | 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 |
char *zUsage = 0; /* Build up a usage message here */
int nUsage = 0; /* Number of bytes at zUsage */
if( argc!=4 ){
return Th_WrongNumArgs(interp, "proc name arglist code");
}
if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
&azParam, &anParam, &nParam) ){
return TH_ERROR;
}
/* Allocate the new ProcDefn structure. */
nByte = sizeof(ProcDefn) + /* ProcDefn structure */
(sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */
(sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */
TH1_LEN(argl[3]) + /* zProgram */
TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */
p = (ProcDefn *)Th_Malloc(interp, nByte);
/* If the last parameter in the parameter list is "args", then set the
** ProcDefn.hasArgs flag. The "args" parameter does not require an
** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
*/
if( nParam>0 ){
if( TH1_LEN(anParam[nParam-1])==4
&& 0==memcmp(azParam[nParam-1], "args", 4)
){
p->hasArgs = 1;
nParam--;
}
}
p->nParam = nParam;
p->azParam = (char **)&p[1];
p->anParam = (int *)&p->azParam[nParam];
p->azDefault = (char **)&p->anParam[nParam];
p->anDefault = (int *)&p->azDefault[nParam];
p->zProgram = (char *)&p->anDefault[nParam];
memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
p->nProgram = TH1_LEN(argl[3]);
zSpace = &p->zProgram[p->nProgram];
for(i=0; i<nParam; i++){
char **az;
int *an;
int n;
if( Th_SplitList(interp, azParam[i], anParam[i], &az, &an, &n) ){
|
| ︙ | ︙ | |||
670 671 672 673 674 675 676 |
int argc,
const char **argv,
int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
}
| | > | 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 |
int argc,
const char **argv,
int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
}
return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
argv[2], TH1_LEN(argl[2]));
}
/*
** TH Syntax:
**
** break ?value...?
** continue ?value...?
|
| ︙ | ︙ | |||
744 745 746 747 748 749 750 |
int iRes = 0;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string compare str1 str2");
}
zLeft = argv[2];
| | | | 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 |
int iRes = 0;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string compare str1 str2");
}
zLeft = argv[2];
nLeft = TH1_LEN(argl[2]);
zRight = argv[3];
nRight = TH1_LEN(argl[3]);
for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
iRes = zLeft[i]-zRight[i];
}
if( iRes==0 ){
iRes = nLeft-nRight;
}
|
| ︙ | ︙ | |||
777 778 779 780 781 782 783 |
int nHaystack;
int iRes = -1;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string first needle haystack");
}
| | | | 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 |
int nHaystack;
int iRes = -1;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string first needle haystack");
}
nNeedle = TH1_LEN(argl[2]);
nHaystack = TH1_LEN(argl[3]);
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
const char *zNeedle = argv[2];
const char *zHaystack = argv[3];
int i;
for(i=0; i<=(nHaystack-nNeedle); i++){
|
| ︙ | ︙ | |||
810 811 812 813 814 815 816 |
){
int iIndex;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string index string index");
}
| | | | > > | | | | | | > > | > | | | 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 |
){
int iIndex;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string index string index");
}
if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
iIndex = TH1_LEN(argl[2])-1;
}else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
Th_ErrorMessage(
interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
return TH_ERROR;
}
if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
int sz = 1;
TH1_XFER_TAINT(sz, argl[2]);
return Th_SetResult(interp, &argv[2][iIndex], sz);
}else{
return Th_SetResult(interp, 0, 0);
}
}
/*
** TH Syntax:
**
** string is CLASS STRING
*/
static int string_is_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string is class string");
}
if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
int i;
int iRes = 1;
for(i=0; i<TH1_LEN(argl[3]); i++){
if( !th_isalnum(argv[3][i]) ){
iRes = 0;
}
}
return Th_SetResultInt(interp, iRes);
}else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
double fVal;
if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
return Th_SetResultInt(interp, 1);
}
return Th_SetResultInt(interp, 0);
}else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
int iVal;
if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
return Th_SetResultInt(interp, 1);
}
return Th_SetResultInt(interp, 0);
}else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
return Th_SetResultInt(interp, 1);
}
return Th_SetResultInt(interp, 0);
}else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
}else{
Th_ErrorMessage(interp,
"Expected alnum, double, integer, list, or tainted, got:",
argv[2], TH1_LEN(argl[2]));
return TH_ERROR;
}
}
/*
** TH Syntax:
**
** string last NEEDLE HAYSTACK
*/
static int string_last_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int nNeedle;
int nHaystack;
int iRes = -1;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string last needle haystack");
}
nNeedle = TH1_LEN(argl[2]);
nHaystack = TH1_LEN(argl[3]);
if( nNeedle && nHaystack && nNeedle<=nHaystack ){
const char *zNeedle = argv[2];
const char *zHaystack = argv[3];
int i;
for(i=nHaystack-nNeedle; i>=0; i--){
|
| ︙ | ︙ | |||
917 918 919 920 921 922 923 |
*/
static int string_length_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "string length string");
}
| | | | > | | | | > > | > | | > > > | | > > | | 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 |
*/
static int string_length_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "string length string");
}
return Th_SetResultInt(interp, TH1_LEN(argl[2]));
}
/*
** TH Syntax:
**
** string match PATTERN STRING
**
*/
static int string_match_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
extern char *fossil_strndup(const char*,int);
extern void fossil_free(void*);
char *zPat, *zStr;
int rc;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string match pattern string");
}
zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
rc = sqlite3_strglob(zPat,zStr);
fossil_free(zPat);
fossil_free(zStr);
return Th_SetResultInt(interp, !rc);
}
/*
** TH Syntax:
**
** string range STRING FIRST LAST
*/
static int string_range_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int iStart;
int iEnd;
int sz;
if( argc!=5 ){
return Th_WrongNumArgs(interp, "string range string first last");
}
if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
iEnd = TH1_LEN(argl[2]);
}else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
Th_ErrorMessage(
interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
return TH_ERROR;
}
if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
return TH_ERROR;
}
if( iStart<0 ) iStart = 0;
if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
if( iStart>iEnd ) iEnd = iStart-1;
sz = iEnd - iStart + 1;
TH1_XFER_TAINT(sz, argl[2]);
return Th_SetResult(interp, &argv[2][iStart], sz);
}
/*
** TH Syntax:
**
** string repeat STRING COUNT
*/
static int string_repeat_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int n;
int i;
int sz;
long long int nByte;
char *zByte;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "string repeat string n");
}
if( Th_ToInt(interp, argv[3], argl[3], &n) ){
return TH_ERROR;
}
nByte = n;
sz = TH1_LEN(argl[2]);
nByte *= sz;
TH1_SIZECHECK(nByte+1);
zByte = Th_Malloc(interp, nByte+1);
for(i=0; i<nByte; i+=sz){
memcpy(&zByte[i], argv[2], sz);
}
n = nByte;
TH1_XFER_TAINT(n, argl[2]);
Th_SetResult(interp, zByte, n);
Th_Free(interp, zByte);
return TH_OK;
}
/*
** TH Syntax:
**
|
| ︙ | ︙ | |||
1025 1026 1027 1028 1029 1030 1031 |
int n;
const char *z;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "string trim string");
}
z = argv[2];
| | | | > | | 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 |
int n;
const char *z;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "string trim string");
}
z = argv[2];
n = TH1_LEN(argl[2]);
if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
while( n && th_isspace(z[0]) ){ z++; n--; }
}
if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
while( n && th_isspace(z[n-1]) ){ n--; }
}
TH1_XFER_TAINT(n, argl[2]);
Th_SetResult(interp, z, n);
return TH_OK;
}
/*
** TH Syntax:
**
** info exists VARNAME
*/
static int info_exists_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int rc;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "info exists var");
}
rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH Syntax:
**
|
| ︙ | ︙ | |||
1115 1116 1117 1118 1119 1120 1121 |
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int rc;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "array exists var");
}
| | | | 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 |
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int rc;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "array exists var");
}
rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH Syntax:
**
** array names VARNAME
*/
static int array_names_command(
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
int rc;
char *zElem = 0;
int nElem = 0;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "array names varname");
}
rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
if( rc!=TH_OK ){
return rc;
}
Th_SetResult(interp, zElem, nElem);
if( zElem ) Th_Free(interp, zElem);
return TH_OK;
}
|
| ︙ | ︙ | |||
1159 1160 1161 1162 1163 1164 1165 |
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "unset var");
}
| | | > | > | > | 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 |
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "unset var");
}
return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
}
int Th_CallSubCommand(
Th_Interp *interp,
void *ctx,
int argc,
const char **argv,
int *argl,
const Th_SubCommand *aSub
){
if( argc>1 ){
int i;
for(i=0; aSub[i].zName; i++){
const char *zName = aSub[i].zName;
if( th_strlen(zName)==TH1_LEN(argl[1])
&& 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
return aSub[i].xProc(interp, ctx, argc, argv, argl);
}
}
}
if(argc<2){
Th_ErrorMessage(interp, "Expected sub-command for",
argv[0], TH1_LEN(argl[0]));
}else{
Th_ErrorMessage(interp, "Expected sub-command, got:",
argv[1], TH1_LEN(argl[1]));
}
return TH_ERROR;
}
/*
** TH Syntax:
**
|
| ︙ | ︙ | |||
1317 1318 1319 1320 1321 1322 1323 |
int *argl
){
int iFrame = -1;
if( argc!=2 && argc!=3 ){
return Th_WrongNumArgs(interp, "uplevel ?level? script...");
}
| | | 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 |
int *argl
){
int iFrame = -1;
if( argc!=2 && argc!=3 ){
return Th_WrongNumArgs(interp, "uplevel ?level? script...");
}
if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
return TH_ERROR;
}
return Th_Eval(interp, iFrame, argv[argc-1], -1);
}
/*
** TH Syntax:
|
| ︙ | ︙ | |||
1340 1341 1342 1343 1344 1345 1346 |
int *argl
){
int iVar = 1;
int iFrame = -1;
int rc = TH_OK;
int i;
| | | > | 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 |
int *argl
){
int iVar = 1;
int iFrame = -1;
int rc = TH_OK;
int i;
if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
iVar++;
}
if( argc==iVar || (argc-iVar)%2 ){
return Th_WrongNumArgs(interp,
"upvar frame othervar myvar ?othervar myvar...?");
}
for(i=iVar; rc==TH_OK && i<argc; i=i+2){
rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
iFrame, argv[i], TH1_LEN(argl[i]));
}
return rc;
}
/*
** TH Syntax:
**
|
| ︙ | ︙ |
Changes to src/th_main.c.
| ︙ | ︙ | |||
260 261 262 263 264 265 266 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "httpize STRING");
}
| | | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "httpize STRING");
}
zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
Th_SetResult(interp, zOut, -1);
free(zOut);
return TH_OK;
}
/*
** True if output is enabled. False if disabled.
|
| ︙ | ︙ | |||
289 290 291 292 293 294 295 |
){
int rc;
if( argc<2 || argc>3 ){
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
}
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
if( g.thTrace ){
| | > | 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
){
int rc;
if( argc<2 || argc>3 ){
return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
}
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
if( g.thTrace ){
Th_Trace("enable_output {%.*s} -> %d<br>\n",
TH1_LEN(argl[1]),argv[1],enableOutput);
}
return rc;
}
/*
** TH1 command: enable_htmlify ?BOOLEAN?
**
|
| ︙ | ︙ | |||
320 321 322 323 324 325 326 |
"enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
}
buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
Th_SetResultInt(g.interp, buul);
if(argc>1){
if( g.thTrace ){
Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
| | | 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
"enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
}
buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
Th_SetResultInt(g.interp, buul);
if(argc>1){
if( g.thTrace ){
Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
TH1_LEN(argl[1]),argv[1],buul);
}
rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
if(!rc){
if(buul){
g.th1Flags &= ~TH_INIT_NO_ENCODE;
}else{
g.th1Flags |= TH_INIT_NO_ENCODE;
|
| ︙ | ︙ | |||
379 380 381 382 383 384 385 | ** Escape all characters with special meaning to HTML if the encode ** parameter is true, with the exception that that flag is ignored if ** g.th1Flags has the TH_INIT_NO_ENCODE flag. ** ** If pOut is NULL and the global pThOut is not then that blob ** is used for output. */ | | | > > > > | 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
** Escape all characters with special meaning to HTML if the encode
** parameter is true, with the exception that that flag is ignored if
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob *pOut, const char *z, int n, int encode){
if(0==pOut && pThOut!=0){
pOut = pThOut;
}
if(TH_INIT_NO_ENCODE & g.th1Flags){
encode = 0;
}
if( enableOutput && n ){
if( n<0 ){
n = strlen(z);
}else{
n = TH1_LEN(n);
}
if( encode ){
z = htmlize(z, n);
n = strlen(z);
}
if(pOut!=0){
blob_append(pOut, z, n);
}else if( g.cgiOutput ){
|
| ︙ | ︙ | |||
523 524 525 526 527 528 529 530 531 532 |
static int putsCmd(
Th_Interp *interp,
void *pConvert,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "puts STRING");
}
| > > > > > > > > | | 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
static int putsCmd(
Th_Interp *interp,
void *pConvert,
int argc,
const char **argv,
int *argl
){
int encode = *(unsigned int*)pConvert;
int n;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "puts STRING");
}
n = argl[1];
if( encode==0 && n>0 && TH1_TAINTED(n) ){
if( Th_ReportTaint(interp, "output string", argv[1], n) ){
return TH_ERROR;
}
}
sendText(0,(char*)argv[1], TH1_LEN(n), encode);
return TH_OK;
}
/*
** TH1 command: redirect URL ?withMethod?
**
** Issues an HTTP redirect to the specified URL and then exits the process.
|
| ︙ | ︙ | |||
555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
if( argc!=2 && argc!=3 ){
return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
}
if( argc==3 ){
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
return TH_ERROR;
}
}
if( withMethod ){
cgi_redirect_with_method(argv[1]);
}else{
cgi_redirect(argv[1]);
}
Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */
| > > > > > | 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 |
if( argc!=2 && argc!=3 ){
return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
}
if( argc==3 ){
if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
return TH_ERROR;
}
}
if( TH1_TAINTED(argl[1])
&& Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
){
return TH_ERROR;
}
if( withMethod ){
cgi_redirect_with_method(argv[1]);
}else{
cgi_redirect(argv[1]);
}
Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */
|
| ︙ | ︙ | |||
658 659 660 661 662 663 664 |
Blob src, title, body;
char *zValue = 0;
int nValue = 0;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "markdown STRING");
}
blob_zero(&src);
| | | 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 |
Blob src, title, body;
char *zValue = 0;
int nValue = 0;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "markdown STRING");
}
blob_zero(&src);
blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
blob_zero(&title); blob_zero(&body);
markdown_to_html(&src, &title, &body);
Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
Th_SetResult(interp, zValue, nValue);
Th_Free(interp, zValue);
return TH_OK;
|
| ︙ | ︙ | |||
688 689 690 691 692 693 694 |
){
int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "wiki STRING");
}
if( enableOutput ){
Blob src;
| | | 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 |
){
int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "wiki STRING");
}
if( enableOutput ){
Blob src;
blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
wiki_convert(&src, 0, flags);
blob_reset(&src);
}
return TH_OK;
}
/*
|
| ︙ | ︙ | |||
733 734 735 736 737 738 739 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "htmlize STRING");
}
| | | 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "htmlize STRING");
}
zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
Th_SetResult(interp, zOut, -1);
free(zOut);
return TH_OK;
}
/*
** TH1 command: encode64 STRING
|
| ︙ | ︙ | |||
755 756 757 758 759 760 761 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "encode64 STRING");
}
| | | | 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 |
const char **argv,
int *argl
){
char *zOut;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "encode64 STRING");
}
zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
Th_SetResult(interp, zOut, -1);
free(zOut);
return TH_OK;
}
/*
** TH1 command: date
**
** Return a string which is the current time and date. If the
** -local option is used, the date appears using localtime instead
** of UTC.
*/
static int dateCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
char *zOut;
if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
zOut = db_text("??", "SELECT datetime('now',toLocal())");
}else{
zOut = db_text("??", "SELECT datetime('now')");
}
Th_SetResult(interp, zOut, -1);
free(zOut);
return TH_OK;
|
| ︙ | ︙ | |||
808 809 810 811 812 813 814 |
char *zCapList = 0;
int nCapList = 0;
if( argc<2 ){
return Th_WrongNumArgs(interp, "hascap STRING ...");
}
for(i=1; rc==1 && i<argc; i++){
if( g.thTrace ){
| | | | 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 |
char *zCapList = 0;
int nCapList = 0;
if( argc<2 ){
return Th_WrongNumArgs(interp, "hascap STRING ...");
}
for(i=1; rc==1 && i<argc; i++){
if( g.thTrace ){
Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
}
rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
}
if( g.thTrace ){
Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
Th_Free(interp, zCapList);
}
Th_SetResultInt(interp, rc);
return TH_OK;
|
| ︙ | ︙ | |||
856 857 858 859 860 861 862 |
int nCap;
int rc;
int i;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "capexpr EXPR");
}
| | | 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 |
int nCap;
int rc;
int i;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "capexpr EXPR");
}
rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
if( rc ) return rc;
rc = 0;
for(i=0; i<nCap; i++){
if( azCap[i][0]=='!' ){
rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
}else if( azCap[i][0]=='@' ){
rc = login_has_capability(azCap[i]+1, anCap[i]-1, LOGIN_ANON);
|
| ︙ | ︙ | |||
919 920 921 922 923 924 925 |
int rc = 1, i, j;
unsigned int searchCap = search_restrict(SRCH_ALL);
if( argc<2 ){
return Th_WrongNumArgs(interp, "hascap STRING ...");
}
for(i=1; i<argc && rc; i++){
int match = 0;
| > | | | 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
int rc = 1, i, j;
unsigned int searchCap = search_restrict(SRCH_ALL);
if( argc<2 ){
return Th_WrongNumArgs(interp, "hascap STRING ...");
}
for(i=1; i<argc && rc; i++){
int match = 0;
int nn = TH1_LEN(argl[i]);
for(j=0; j<nn; j++){
switch( argv[i][j] ){
case 'c': match |= searchCap & SRCH_CKIN; break;
case 'd': match |= searchCap & SRCH_DOC; break;
case 't': match |= searchCap & SRCH_TKT; break;
case 'w': match |= searchCap & SRCH_WIKI; break;
}
}
if( !match ) rc = 0;
}
if( g.thTrace ){
Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH1 command: hasfeature STRING
|
| ︙ | ︙ | |||
1049 1050 1051 1052 1053 1054 1055 |
rc = 1;
}
#endif
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
rc = 1;
}
if( g.thTrace ){
| | | 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 |
rc = 1;
}
#endif
else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
rc = 1;
}
if( g.thTrace ){
Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
|
| ︙ | ︙ | |||
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 |
void *p,
int argc,
const char **argv,
int *argl
){
int rc = 0;
int i;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "anycap STRING");
}
| > > | | | 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 |
void *p,
int argc,
const char **argv,
int *argl
){
int rc = 0;
int i;
int nn;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "anycap STRING");
}
nn = TH1_LEN(argl[1]);
for(i=0; rc==0 && i<nn; i++){
rc = login_has_capability((char*)&argv[1][i],1,0);
}
if( g.thTrace ){
Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
}
Th_SetResultInt(interp, rc);
return TH_OK;
}
/*
** TH1 command: combobox NAME TEXT-LIST NUMLINES
|
| ︙ | ︙ | |||
1138 1139 1140 1141 1142 1143 1144 |
){
if( argc!=4 ){
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
}
if( enableOutput ){
int height;
Blob name;
| | | | > | 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 |
){
if( argc!=4 ){
return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
}
if( enableOutput ){
int height;
Blob name;
int nValue = 0;
const char *zValue;
char *z, *zH;
int nElem;
int *aszElem;
char **azElem;
int i;
if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
zValue = Th_Fetch(blob_str(&name), &nValue);
nValue = TH1_LEN(nValue);
zH = htmlize(blob_buffer(&name), blob_size(&name));
z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
free(zH);
sendText(0,z, -1, 0);
free(z);
blob_reset(&name);
for(i=0; i<nElem; i++){
|
| ︙ | ︙ | |||
1245 1246 1247 1248 1249 1250 1251 |
int iMin, iMax;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
}
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
z = argv[1];
| | | 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 |
int iMin, iMax;
if( argc!=4 ){
return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
}
if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
z = argv[1];
size = TH1_LEN(argl[1]);
for(n=1, i=0; i<size; i++){
if( z[i]=='\n' ){
n++;
if( n>=iMax ) break;
}
}
if( n<iMin ) n = iMin;
|
| ︙ | ︙ | |||
1405 1406 1407 1408 1409 1410 1411 |
}else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
return TH_OK;
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
return TH_OK;
}else{
| | > > > | > > | 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 |
}else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
return TH_OK;
}else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
return TH_OK;
}else{
Th_ErrorMessage(interp, "unsupported global state:",
argv[1], TH1_LEN(argl[1]));
return TH_ERROR;
}
}
/*
** TH1 command: getParameter NAME ?DEFAULT?
**
** Return the value of the specified query parameter or the specified default
** value when there is no matching query parameter.
*/
static int getParameterCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
const char *zDefault = 0;
const char *zVal;
int sz;
if( argc!=2 && argc!=3 ){
return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
}
if( argc==3 ){
zDefault = argv[2];
}
zVal = cgi_parameter(argv[1], zDefault);
sz = th_strlen(zVal);
Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
return TH_OK;
}
/*
** TH1 command: setParameter NAME VALUE
**
** Sets the value of the specified query parameter.
|
| ︙ | ︙ | |||
1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 | char zUTime[50]; fossil_cpu_times(0, &x); sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } /* ** TH1 command: randhex N ** ** Return N*2 random hexadecimal digits with N<50. If N is omitted, ** use a value of 10. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 |
char zUTime[50];
fossil_cpu_times(0, &x);
sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
Th_SetResult(interp, zUTime, -1);
return TH_OK;
}
/*
** TH1 command: taint STRING
**
** Return a copy of STRING that is marked as tainted.
*/
static int taintCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "STRING");
}
Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
return TH_OK;
}
/*
** TH1 command: untaint STRING
**
** Return a copy of STRING that is marked as untainted.
*/
static int untaintCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
if( argc!=2 ){
return Th_WrongNumArgs(interp, "STRING");
}
Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
return TH_OK;
}
/*
** TH1 command: randhex N
**
** Return N*2 random hexadecimal digits with N<50. If N is omitted,
** use a value of 10.
*/
|
| ︙ | ︙ | |||
1921 1922 1923 1924 1925 1926 1927 | const char *zTail; int n, i; int res = TH_OK; int nVar; char *zErr = 0; int noComplain = 0; | > | > > > > > > > > | | | | | | | 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 |
const char *zTail;
int n, i;
int res = TH_OK;
int nVar;
char *zErr = 0;
int noComplain = 0;
if( argc>3 && TH1_LEN(argl[1])==11
&& strncmp(argv[1], "-nocomplain", 11)==0
){
argc--;
argv++;
argl++;
noComplain = 1;
}
if( argc!=3 ){
return Th_WrongNumArgs(interp, "query SQL CODE");
}
if( g.db==0 ){
if( noComplain ) return TH_OK;
Th_ErrorMessage(interp, "database is not open", 0, 0);
return TH_ERROR;
}
zSql = argv[1];
nSql = argl[1];
if( TH1_TAINTED(nSql) ){
if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
return TH_ERROR;
}
nSql = TH1_LEN(nSql);
}
while( res==TH_OK && nSql>0 ){
zErr = 0;
report_restrict_sql(&zErr);
g.dbIgnoreErrors++;
rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
g.dbIgnoreErrors--;
report_unrestrict_sql();
if( rc!=0 || zErr!=0 ){
if( noComplain ) return TH_OK;
Th_ErrorMessage(interp, "SQL error: ",
zErr ? zErr : sqlite3_errmsg(g.db), -1);
return TH_ERROR;
}
n = (int)(zTail - zSql);
zSql += n;
nSql -= n;
if( pStmt==0 ) continue;
nVar = sqlite3_bind_parameter_count(pStmt);
for(i=1; i<=nVar; i++){
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
int szVar = zVar ? th_strlen(zVar) : 0;
if( szVar>1 && zVar[0]=='$'
&& Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
int nVal;
const char *zVal = Th_GetResult(interp, &nVal);
sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
}
}
while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
int nCol = sqlite3_column_count(pStmt);
for(i=0; i<nCol; i++){
const char *zCol = sqlite3_column_name(pStmt, i);
int szCol = th_strlen(zCol);
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
int szVal = sqlite3_column_bytes(pStmt, i);
Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
}
if( g.thTrace ){
Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
}
res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
if( g.thTrace ){
int nTrRes;
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
Th_Trace("[query_eval] => %h {%#h}<br>\n",
Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
}
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
}
rc = sqlite3_finalize(pStmt);
if( rc!=SQLITE_OK ){
if( noComplain ) return TH_OK;
Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
|
| ︙ | ︙ | |||
2036 2037 2038 2039 2040 2041 2042 |
rc = TH_ERROR;
}else{
Th_SetResult(interp, 0, 0);
rc = TH_OK;
}
if( g.thTrace ){
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
| | | 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 |
rc = TH_ERROR;
}else{
Th_SetResult(interp, 0, 0);
rc = TH_OK;
}
if( g.thTrace ){
Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
TH1_LEN(argl[nArg]), argv[nArg], rc);
}
return rc;
}
/*
** TH1 command: glob_match ?-one? ?--? patternList string
**
|
| ︙ | ︙ | |||
2119 2120 2121 2122 2123 2124 2125 |
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
if( nArg+2!=argc ){
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
}
zErr = re_compile(&pRe, argv[nArg], noCase);
if( !zErr ){
Th_SetResultInt(interp, re_match(pRe,
| | | 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 |
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
if( nArg+2!=argc ){
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
}
zErr = re_compile(&pRe, argv[nArg], noCase);
if( !zErr ){
Th_SetResultInt(interp, re_match(pRe,
(const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
rc = TH_OK;
}else{
Th_SetResult(interp, zErr, -1);
rc = TH_ERROR;
}
re_free(pRe);
return rc;
|
| ︙ | ︙ | |||
2158 2159 2160 2161 2162 2163 2164 |
Blob payload;
ReCompiled *pRe = 0;
UrlData urlData;
if( argc<2 || argc>5 ){
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
}
| | | 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 |
Blob payload;
ReCompiled *pRe = 0;
UrlData urlData;
if( argc<2 || argc>5 ){
return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
}
if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
fAsynchronous = 1; nArg++;
}
if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
if( nArg+1!=argc && nArg+2!=argc ){
return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
}
memset(&urlData, '\0', sizeof(urlData));
|
| ︙ | ︙ | |||
2187 2188 2189 2190 2191 2192 2193 |
Th_SetResult(interp, "url not allowed", -1);
re_free(pRe);
return TH_ERROR;
}
re_free(pRe);
blob_zero(&payload);
if( nArg+2==argc ){
| | | 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 |
Th_SetResult(interp, "url not allowed", -1);
re_free(pRe);
return TH_ERROR;
}
re_free(pRe);
blob_zero(&payload);
if( nArg+2==argc ){
blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
zType = "POST";
}else{
zType = "GET";
}
if( fAsynchronous ){
const char *zSep, *zParams;
Blob hdr;
|
| ︙ | ︙ | |||
2266 2267 2268 2269 2270 2271 2272 |
const char * zStr;
int nStr, rc;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "captureTh1 STRING");
}
pOrig = Th_SetOutputBlob(&out);
zStr = argv[1];
| | | 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 |
const char * zStr;
int nStr, rc;
if( argc!=2 ){
return Th_WrongNumArgs(interp, "captureTh1 STRING");
}
pOrig = Th_SetOutputBlob(&out);
zStr = argv[1];
nStr = TH1_LEN(argl[1]);
rc = Th_Eval(g.interp, 0, zStr, nStr);
Th_SetOutputBlob(pOrig);
if(0==rc){
Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
}
blob_reset(&out);
return rc;
|
| ︙ | ︙ | |||
2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 |
{"searchable", searchableCmd, 0},
{"setParameter", setParameterCmd, 0},
{"setting", settingCmd, 0},
{"styleFooter", styleFooterCmd, 0},
{"styleHeader", styleHeaderCmd, 0},
{"styleScript", styleScriptCmd, 0},
{"submenu", submenuCmd, 0},
{"tclReady", tclReadyCmd, 0},
{"trace", traceCmd, 0},
{"stime", stimeCmd, 0},
{"unversioned", unversionedCmd, 0},
{"utime", utimeCmd, 0},
{"verifyCsrf", verifyCsrfCmd, 0},
{"verifyLogin", verifyLoginCmd, 0},
{"wiki", wikiCmd, (void*)&aFlags[0]},
{"wiki_assoc", wikiAssocCmd, 0},
{0, 0, 0}
| > > | 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 |
{"searchable", searchableCmd, 0},
{"setParameter", setParameterCmd, 0},
{"setting", settingCmd, 0},
{"styleFooter", styleFooterCmd, 0},
{"styleHeader", styleHeaderCmd, 0},
{"styleScript", styleScriptCmd, 0},
{"submenu", submenuCmd, 0},
{"taint", taintCmd, 0},
{"tclReady", tclReadyCmd, 0},
{"trace", traceCmd, 0},
{"stime", stimeCmd, 0},
{"untaint", untaintCmd, 0},
{"unversioned", unversionedCmd, 0},
{"utime", utimeCmd, 0},
{"verifyCsrf", verifyCsrfCmd, 0},
{"verifyLogin", verifyLoginCmd, 0},
{"wiki", wikiCmd, (void*)&aFlags[0]},
{"wiki_assoc", wikiAssocCmd, 0},
{0, 0, 0}
|
| ︙ | ︙ | |||
2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 |
if( zValue ){
if( g.thTrace ){
Th_Trace("set %h {%h}<br>\n", zName, zValue);
}
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
}
}
/*
** Appends an element to a TH1 list value. This function is called by the
** transfer subsystem; therefore, it must be very careful to avoid doing
** any unnecessary work. To that end, the TH1 subsystem will not be called
** or initialized if the list pointer is zero (i.e. which will be the case
** when TH1 transfer hooks are disabled).
| > > > > > > > > > > > > > > > > | 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 |
if( zValue ){
if( g.thTrace ){
Th_Trace("set %h {%h}<br>\n", zName, zValue);
}
Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
}
}
/*
** Store a string value in a variable in the interpreter
** with the "taint" marking, so that TH1 knows that this
** variable contains content under the control of the remote
** user and presents a risk of XSS or SQL-injection attacks.
*/
void Th_StoreUnsafe(const char *zName, const char *zValue){
Th_FossilInit(TH_INIT_DEFAULT);
if( zValue ){
if( g.thTrace ){
Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
}
Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
}
}
/*
** Appends an element to a TH1 list value. This function is called by the
** transfer subsystem; therefore, it must be very careful to avoid doing
** any unnecessary work. To that end, the TH1 subsystem will not be called
** or initialized if the list pointer is zero (i.e. which will be the case
** when TH1 transfer hooks are disabled).
|
| ︙ | ︙ | |||
2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 |
if( rc==TH_ERROR ){
int nResult = 0;
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
/*
** Make sure that the TH1 script error was not caused by a "missing"
** command hook handler as that is not actually an error condition.
*/
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
sendError(0,zResult, nResult, 0);
}else{
/*
** There is no command hook handler "installed". This situation
** is NOT actually an error.
*/
| > | 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 |
if( rc==TH_ERROR ){
int nResult = 0;
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
/*
** Make sure that the TH1 script error was not caused by a "missing"
** command hook handler as that is not actually an error condition.
*/
nResult = TH1_LEN(nResult);
if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
sendError(0,zResult, nResult, 0);
}else{
/*
** There is no command hook handler "installed". This situation
** is NOT actually an error.
*/
|
| ︙ | ︙ | |||
2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 |
if( rc==TH_ERROR ){
int nResult = 0;
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
/*
** Make sure that the TH1 script error was not caused by a "missing"
** webpage hook handler as that is not actually an error condition.
*/
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
sendError(0,zResult, nResult, 1);
}else{
/*
** There is no webpage hook handler "installed". This situation
** is NOT actually an error.
*/
| > | 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 |
if( rc==TH_ERROR ){
int nResult = 0;
char *zResult = (char*)Th_GetResult(g.interp, &nResult);
/*
** Make sure that the TH1 script error was not caused by a "missing"
** webpage hook handler as that is not actually an error condition.
*/
nResult = TH1_LEN(nResult);
if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
sendError(0,zResult, nResult, 1);
}else{
/*
** There is no webpage hook handler "installed". This situation
** is NOT actually an error.
*/
|
| ︙ | ︙ | |||
2892 2893 2894 2895 2896 2897 2898 |
nVar = n;
encode = 0;
}
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
z += i+1+n;
i = 0;
zResult = (char*)Th_GetResult(g.interp, &n);
| > > > > | > | | 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 |
nVar = n;
encode = 0;
}
rc = Th_GetVar(g.interp, (char*)zVar, nVar);
z += i+1+n;
i = 0;
zResult = (char*)Th_GetResult(g.interp, &n);
if( !TH1_TAINTED(n)
|| encode
|| Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
){
sendText(pOut,(char*)zResult, n, encode);
}
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
sendText(pOut,z, i, 0);
z += i+5;
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
if( g.thTrace ){
Th_Trace("render_eval {<pre>%#h</pre>}<br>\n", i, z);
}
rc = Th_Eval(g.interp, 0, (const char*)z, i);
if( g.thTrace ){
int nTrRes;
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
Th_Trace("[render_eval] => %h {%#h}<br>\n",
Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
}
if( rc!=TH_OK ) break;
z += i;
if( z[0] ){ z += 6; }
i = 0;
}else{
i++;
|
| ︙ | ︙ | |||
2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 |
** Th_SetOutputBlob() has been called. If it has not been called,
** pThOut will be 0, which will redirect the output to CGI/stdout,
** as appropriate. We need to pass on g.th1Flags for the case of
** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
** inadvertently toggled off by a recursive call.
*/;
}
/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 |
** Th_SetOutputBlob() has been called. If it has not been called,
** pThOut will be 0, which will redirect the output to CGI/stdout,
** as appropriate. We need to pass on g.th1Flags for the case of
** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
** inadvertently toggled off by a recursive call.
*/;
}
/*
** SETTING: vuln-report width=8 default=log
**
** This setting controls Fossil's behavior when it encounters a potential
** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
** scripts. Choices are:
**
** off Do nothing. Ignore the vulnerability.
**
** log Write a report of the problem into the error log.
**
** block Like "log" but also prevent the offending TH1 command
** from running.
**
** fatal Render an error message page instead of the requested
** page.
*/
/*
** Report misuse of a tainted string in TH1.
**
** The behavior depends on the vuln-report setting. If "off", this routine
** is a no-op. Otherwise, right a message into the error log. If
** vuln-report is "log", that is all that happens. But for any other
** value of vuln-report, a fatal error is raised.
*/
int Th_ReportTaint(
Th_Interp *interp, /* Report error here, if an error is reported */
const char *zWhere, /* Where the tainted string appears */
const char *zStr, /* The tainted string */
int nStr /* Length of the tainted string */
){
static const char *zDisp = 0; /* Dispensation; what to do with the error */
const char *zVulnType; /* Type of vulnerability */
if( zDisp==0 ) zDisp = db_get("vuln-report","log");
if( is_false(zDisp) ) return 0;
if( strstr(zWhere,"SQL")!=0 ){
zVulnType = "SQL-injection";
}else{
zVulnType = "XSS";
}
nStr = TH1_LEN(nStr);
fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
zVulnType, zWhere, nStr, zStr);
if( strcmp(zDisp,"log")==0 ){
return 0;
}
if( strcmp(zDisp,"block")==0 ){
char *z = mprintf("tainted %s: \"", zWhere);
Th_ErrorMessage(interp, z, zStr, nStr);
fossil_free(z);
}else{
char *z = mprintf("%#h", nStr, zStr);
zDisp = "off";
cgi_reset_content();
style_submenu_enable(0);
style_set_current_feature("error");
style_header("Configuration Error");
@ <p>Error in a TH1 configuration script:
@ tainted %h(zWhere): "%z(z)"
style_finish_page();
cgi_reply();
fossil_exit(1);
}
return 1;
}
/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
|
| ︙ | ︙ | |||
2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 |
g.useLocalauth = 1;
}
if( find_option("set-user-caps", 0, 0)!=0 ){
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
login_set_capabilities(zCap ? zCap : "sx", 0);
g.useLocalauth = 1;
}
verify_all_options();
if( g.argc<3 ){
usage("FILE");
}
blob_zero(&in);
blob_read_from_file(&in, g.argv[2], ExtFILE);
Th_Render(blob_str(&in));
| > | 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 |
g.useLocalauth = 1;
}
if( find_option("set-user-caps", 0, 0)!=0 ){
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
login_set_capabilities(zCap ? zCap : "sx", 0);
g.useLocalauth = 1;
}
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
verify_all_options();
if( g.argc<3 ){
usage("FILE");
}
blob_zero(&in);
blob_read_from_file(&in, g.argv[2], ExtFILE);
Th_Render(blob_str(&in));
|
| ︙ | ︙ | |||
3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 |
g.useLocalauth = 1;
}
if( find_option("set-user-caps", 0, 0)!=0 ){
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
login_set_capabilities(zCap ? zCap : "sx", 0);
g.useLocalauth = 1;
}
verify_all_options();
if( g.argc!=3 ){
usage("script");
}
if(file_isfile(g.argv[2], ExtFILE)){
blob_read_from_file(&code, g.argv[2], ExtFILE);
zCode = blob_str(&code);
| > | 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 |
g.useLocalauth = 1;
}
if( find_option("set-user-caps", 0, 0)!=0 ){
const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
login_set_capabilities(zCap ? zCap : "sx", 0);
g.useLocalauth = 1;
}
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
verify_all_options();
if( g.argc!=3 ){
usage("script");
}
if(file_isfile(g.argv[2], ExtFILE)){
blob_read_from_file(&code, g.argv[2], ExtFILE);
zCode = blob_str(&code);
|
| ︙ | ︙ |
Changes to src/th_tcl.c.
| ︙ | ︙ | |||
39 40 41 42 43 44 45 | ** arguments from TH1 to Tcl. */ #define USE_ARGV_TO_OBJV() \ int objc; \ Tcl_Obj **objv; \ int obji; | | | | | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
** arguments from TH1 to Tcl.
*/
#define USE_ARGV_TO_OBJV() \
int objc; \
Tcl_Obj **objv; \
int obji;
#define COPY_ARGV_TO_OBJV() \
objc = argc-1; \
objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
for(obji=1; obji<argc; obji++){ \
objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
Tcl_IncrRefCount(objv[obji-1]); \
}
#define FREE_ARGV_TO_OBJV() \
for(obji=1; obji<argc; obji++){ \
Tcl_DecrRefCount(objv[obji-1]); \
objv[obji-1] = 0; \
} \
|
| ︙ | ︙ | |||
181 182 183 184 185 186 187 | ** when the Tcl library is being loaded dynamically by a stubs-enabled ** application (i.e. the inverse of using a stubs-enabled package). These are ** the only Tcl API functions that MUST be called prior to being able to call ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp ** and Tcl_Finalize function types are also required. */ | | | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | ** when the Tcl library is being loaded dynamically by a stubs-enabled ** application (i.e. the inverse of using a stubs-enabled package). These are ** the only Tcl API functions that MUST be called prior to being able to call ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp ** and Tcl_Finalize function types are also required. */ typedef const char *(tcl_FindExecutableProc) (const char *); typedef Tcl_Interp *(tcl_CreateInterpProc) (void); typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); typedef void (tcl_FinalizeProc) (void); /* ** The function types for the "hook" functions to be called before and after a ** TH1 command makes a call to evaluate a Tcl script. If the "pre" function |
| ︙ | ︙ | |||
319 320 321 322 323 324 325 | ** Creates and initializes a Tcl interpreter for use with the specified TH1 ** interpreter. Stores the created Tcl interpreter in the Tcl context supplied ** by the caller. This must be declared here because quite a few functions in ** this file need to use it before it can be defined. */ static int createTclInterp(Th_Interp *interp, void *pContext); | < < < < < < < < < < < < < < < < < | 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter. Stores the created Tcl interpreter in the Tcl context supplied
** by the caller. This must be declared here because quite a few functions in
** this file need to use it before it can be defined.
*/
static int createTclInterp(Th_Interp *interp, void *pContext);
/*
** Returns the Tcl return code corresponding to the specified TH1
** return code.
*/
static int getTclReturnCode(
int rc /* The TH1 return code value to convert. */
){
|
| ︙ | ︙ | |||
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
Tcl_Interp *pInterp,
int *pN
){
Tcl_Obj *resultPtr;
if( !pInterp ){ /* This should not happen. */
if( pN ) *pN = 0;
return 0;
}
resultPtr = Tcl_GetObjResult(pInterp);
if( !resultPtr ){ /* This should not happen either? */
if( pN ) *pN = 0;
return 0;
}
| > > | > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
Tcl_Interp *pInterp,
int *pN
){
Tcl_Obj *resultPtr;
Tcl_Size n;
char *zRes;
if( !pInterp ){ /* This should not happen. */
if( pN ) *pN = 0;
return 0;
}
resultPtr = Tcl_GetObjResult(pInterp);
if( !resultPtr ){ /* This should not happen either? */
if( pN ) *pN = 0;
return 0;
}
zRes = Tcl_GetStringFromObj(resultPtr, &n);
*pN = (int)n;
return zRes;
}
/*
** Tcl context information used by TH1. This structure definition has been
** copied from and should be kept in sync with the one in "main.c".
*/
struct TclContext {
int argc; /* Number of original arguments. */
char **argv; /* Full copy of the original arguments. */
void *hLibrary; /* The Tcl library module handle. */
tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */
tcl_CreateInterpProc *xCreateInterp; /* Tcl_CreateInterp() pointer. */
tcl_DeleteInterpProc *xDeleteInterp; /* Tcl_DeleteInterp() pointer. */
tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */
Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
int useObjProc; /* Non-zero if an objProc can be called directly. */
int useTip285; /* Non-zero if TIP #285 is available. */
const char *setup; /* The optional Tcl setup script. */
};
/*
** TH1 command: tclEval arg ?arg ...?
**
** Evaluates the Tcl script and returns its result verbatim. If a Tcl script
** error is generated, it will be transformed into a TH1 script error. The
** Tcl interpreter will be created automatically if it has not been already.
*/
|
| ︙ | ︙ | |||
483 484 485 486 487 488 489 |
return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
| < < < < | < < | 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 |
return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
if( argc==2 ){
objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
Tcl_IncrRefCount(objPtr);
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
Tcl_DecrRefCount(objPtr); objPtr = 0;
}else{
USE_ARGV_TO_OBJV();
COPY_ARGV_TO_OBJV();
objPtr = Tcl_ConcatObj(objc, objv);
Tcl_IncrRefCount(objPtr);
rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
Tcl_DecrRefCount(objPtr); objPtr = 0;
FREE_ARGV_TO_OBJV();
}
zResult = getTclResult(tclInterp, &nResult);
Th_SetResult(interp, zResult, nResult);
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** TH1 command: tclExpr arg ?arg ...?
**
** Evaluates the Tcl expression and returns its result verbatim. If a Tcl
|
| ︙ | ︙ | |||
543 544 545 546 547 548 549 |
return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
| < < < < | > | > | < < | 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
if( argc==2 ){
objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
Tcl_IncrRefCount(objPtr);
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
Tcl_DecrRefCount(objPtr); objPtr = 0;
}else{
USE_ARGV_TO_OBJV();
COPY_ARGV_TO_OBJV();
objPtr = Tcl_ConcatObj(objc, objv);
Tcl_IncrRefCount(objPtr);
rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
Tcl_DecrRefCount(objPtr); objPtr = 0;
FREE_ARGV_TO_OBJV();
}
if( rc==TCL_OK ){
Tcl_Size szResult = 0;
zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
nResult = (int)szResult;
}else{
zResult = getTclResult(tclInterp, &nResult);
}
Th_SetResult(interp, zResult, (int)nResult);
if( rc==TCL_OK ){
Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
}
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** TH1 command: tclInvoke command ?arg ...?
**
** Invokes the Tcl command using the supplied arguments. No additional
|
| ︙ | ︙ | |||
608 609 610 611 612 613 614 |
return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
| < < < < | | 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
}
tclInterp = GET_CTX_TCL_INTERP(ctx);
if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
return TH_ERROR;
}
Tcl_Preserve((ClientData)tclInterp);
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
if( GET_CTX_TCL_USEOBJPROC(ctx) ){
Tcl_Command command;
Tcl_CmdInfo cmdInfo;
Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
Tcl_IncrRefCount(objPtr);
command = Tcl_GetCommandFromObj(tclInterp, objPtr);
if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
Tcl_DecrRefCount(objPtr); objPtr = 0;
Tcl_Release((ClientData)tclInterp);
return TH_ERROR;
|
| ︙ | ︙ | |||
647 648 649 650 651 652 653 |
COPY_ARGV_TO_OBJV();
rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
FREE_ARGV_TO_OBJV();
}
zResult = getTclResult(tclInterp, &nResult);
Th_SetResult(interp, zResult, nResult);
Tcl_Release((ClientData)tclInterp);
| < < | 584 585 586 587 588 589 590 591 592 593 594 595 596 597 |
COPY_ARGV_TO_OBJV();
rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
FREE_ARGV_TO_OBJV();
}
zResult = getTclResult(tclInterp, &nResult);
Th_SetResult(interp, zResult, nResult);
Tcl_Release((ClientData)tclInterp);
return rc;
}
/*
** TH1 command: tclIsSafe
**
** Returns non-zero if the Tcl interpreter is "safe". The Tcl interpreter
|
| ︙ | ︙ | |||
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 |
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[]
){
Th_Interp *th1Interp;
int nArg;
const char *arg;
int rc;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "arg");
return TCL_ERROR;
}
th1Interp = (Th_Interp *)clientData;
if( !th1Interp ){
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
return TCL_ERROR;
}
| > | > | > | | | | 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 |
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[]
){
Th_Interp *th1Interp;
int nArg;
Tcl_Size szArg;
const char *arg;
int rc;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "arg");
return TCL_ERROR;
}
th1Interp = (Th_Interp *)clientData;
if( !th1Interp ){
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
return TCL_ERROR;
}
arg = Tcl_GetStringFromObj(objv[1], &szArg);
nArg = (int)szArg;
rc = Th_Eval(th1Interp, 0, arg, nArg);
arg = Th_GetResult(th1Interp, &nArg);
Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
return getTclReturnCode(rc);
}
/*
** Tcl command: th1Expr arg
**
** Evaluates the TH1 expression and returns its result verbatim. If a TH1
** script error is generated, it will be transformed into a Tcl script error.
*/
static int Th1ExprObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[]
){
Th_Interp *th1Interp;
int nArg;
Tcl_Size szArg;
const char *arg;
int rc;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "arg");
return TCL_ERROR;
}
th1Interp = (Th_Interp *)clientData;
if( !th1Interp ){
Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
return TCL_ERROR;
}
arg = Tcl_GetStringFromObj(objv[1], &szArg);
rc = Th_Expr(th1Interp, arg, (int)szArg);
arg = Th_GetResult(th1Interp, &nArg);
Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
return getTclReturnCode(rc);
}
/*
** Array of Tcl integration commands. Used when adding or removing the Tcl
** integration commands from TH1.
*/
|
| ︙ | ︙ |
Changes to src/timeline.c.
| ︙ | ︙ | |||
219 220 221 222 223 224 225 |
if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
vid = db_lget_int("checkout", 0);
}
zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
dateFormat = db_get_int("timeline-date-format", 0);
| | | | | | | | | | | | | | | | | | | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
vid = db_lget_int("checkout", 0);
}
zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
dateFormat = db_get_int("timeline-date-format", 0);
/*
** SETTING: timeline-truncate-at-blank boolean default=off
**
** If enabled, check-in comments displayed on the timeline are truncated
** at the first blank line of the comment text. The comment text after
** the first blank line is only seen in the /info or similar pages that
** show details about the check-in.
*/
bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
/*
** SETTING: timeline-tslink-info boolean default=off
**
** The hyperlink on the timestamp associated with each timeline entry,
** on the far left-hand side of the screen, normally targets another
** /timeline page that shows the entry in context. However, if this
** option is turned on, that hyperlink targets the /info page showing
** the details of the entry.
*/
bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
if( (tmFlags & TIMELINE_VIEWS)==0 ){
tmFlags |= timeline_ss_cookie();
}
if( tmFlags & TIMELINE_COLUMNAR ){
zStyle = "Columnar";
}else if( tmFlags & TIMELINE_COMPACT ){
|
| ︙ | ︙ | |||
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
}
}else{
zDateLink = mprintf("<a>");
}
@ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@ <td class="timelineGraph">
if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
if( tmFlags & TIMELINE_UCOLOR ){
zBgClr = zUser ? user_color(zUser) : 0;
}else if( tmFlags & TIMELINE_NOCOLOR ){
zBgClr = 0;
}else if( zType[0]=='c' ){
static Stmt qdelta;
db_static_prepare(&qdelta, "SELECT baseid IS NULL FROM plink"
" WHERE cid=:rid");
db_bind_int(&qdelta, ":rid", rid);
if( db_step(&qdelta)!=SQLITE_ROW ){
zBgClr = 0; /* Not a check-in */
}else if( db_column_int(&qdelta, 0) ){
zBgClr = hash_color("b"); /* baseline manifest */
}else{
zBgClr = hash_color("f"); /* delta manifest */
}
db_reset(&qdelta);
}
}
if( zType[0]=='c'
&& (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
){
zBr = branch_of_rid(rid);
if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
}else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
zBgClr = 0;
}else{
zBgClr = hash_color(zBr);
}
}
| > > > > > > > | 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
}
}else{
zDateLink = mprintf("<a>");
}
@ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@ <td class="timelineGraph">
if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
/* Don't use the requested background color. Use the background color
** override from query parameters instead. */
if( tmFlags & TIMELINE_UCOLOR ){
zBgClr = zUser ? user_color(zUser) : 0;
}else if( tmFlags & TIMELINE_NOCOLOR ){
zBgClr = 0;
}else if( zType[0]=='c' ){
static Stmt qdelta;
db_static_prepare(&qdelta, "SELECT baseid IS NULL FROM plink"
" WHERE cid=:rid");
db_bind_int(&qdelta, ":rid", rid);
if( db_step(&qdelta)!=SQLITE_ROW ){
zBgClr = 0; /* Not a check-in */
}else if( db_column_int(&qdelta, 0) ){
zBgClr = hash_color("b"); /* baseline manifest */
}else{
zBgClr = hash_color("f"); /* delta manifest */
}
db_reset(&qdelta);
}
}else{
/* Make sure the user-specified background color is reasonable */
zBgClr = reasonable_bg_color(zBgClr, 0);
}
if( zType[0]=='c'
&& (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
){
zBr = branch_of_rid(rid);
if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
/* If no background color is specified, use a color based on the
** branch name */
if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
}else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
zBgClr = 0;
}else{
zBgClr = hash_color(zBr);
}
}
|
| ︙ | ︙ | |||
589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
@ %W(blob_str(&truncated))
blob_reset(&truncated);
drawDetailEllipsis = 0;
}else{
cgi_printf("%W",blob_str(&comment));
}
}
@ </span>
blob_reset(&comment);
/* Generate extra information and hyperlinks to follow the comment.
** Example: "(check-in: [abcdefg], user: drh, tags: trunk)"
*/
if( drawDetailEllipsis ){
| > > > > > > > > > | 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
@ %W(blob_str(&truncated))
blob_reset(&truncated);
drawDetailEllipsis = 0;
}else{
cgi_printf("%W",blob_str(&comment));
}
}
if( zType[0]=='c' && strcmp(zUuid, MANIFEST_UUID)==0 ){
/* This will only ever happen when Fossil is drawing a timeline for
** its own self-host repository. If the timeline shows the specific
** check-in corresponding to the current executable, then tag that
** check-in with "This is me!". */
@ <b>← This is me!</b>
}
@ </span>
blob_reset(&comment);
/* Generate extra information and hyperlinks to follow the comment.
** Example: "(check-in: [abcdefg], user: drh, tags: trunk)"
*/
if( drawDetailEllipsis ){
|
| ︙ | ︙ | |||
1870 1871 1872 1873 1874 1875 1876 |
/* Finish preliminary processing of tag match queries. */
matchStyle = match_style(zMatchStyle, MS_EXACT);
if( zTagName ){
zType = "ci";
if( matchStyle==MS_EXACT ){
/* For exact maching, inhibit links to the selected tag. */
zThisTag = zTagName;
| | | 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 |
/* Finish preliminary processing of tag match queries. */
matchStyle = match_style(zMatchStyle, MS_EXACT);
if( zTagName ){
zType = "ci";
if( matchStyle==MS_EXACT ){
/* For exact maching, inhibit links to the selected tag. */
zThisTag = zTagName;
Th_StoreUnsafe("current_checkin", zTagName);
}
/* Display a checkbox to enable/disable display of related check-ins. */
if( advancedMenu ){
style_submenu_checkbox("rel", "Related", 0, 0);
}
|
| ︙ | ︙ | |||
3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 |
* zFilePattern. */
zFilePattern = 0;
}
}
if( mode==TIMELINE_MODE_NONE ) mode = TIMELINE_MODE_BEFORE;
blob_zero(&sql);
blob_append(&sql, timeline_query_for_tty(), -1);
blob_append_sql(&sql, "\n AND event.mtime %s %s",
( mode==TIMELINE_MODE_BEFORE ||
mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/
);
/* When zFilePattern is specified, compute complete ancestry;
* limit later at print_timeline() */
if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
if( mode==TIMELINE_MODE_CHILDREN ){
compute_descendants(objid, (zFilePattern ? 0 : n));
}else{
compute_ancestors(objid, (zFilePattern ? 0 : n), 0, 0);
}
blob_append_sql(&sql, "\n AND blob.rid IN ok");
}
| > > > > > > > < < < | 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 |
* zFilePattern. */
zFilePattern = 0;
}
}
if( mode==TIMELINE_MODE_NONE ) mode = TIMELINE_MODE_BEFORE;
blob_zero(&sql);
if( mode==TIMELINE_MODE_AFTER ){
/* Extra outer select to get older rows in reverse order */
blob_append(&sql, "SELECT *\nFROM (", -1);
}
blob_append(&sql, timeline_query_for_tty(), -1);
blob_append_sql(&sql, "\n AND event.mtime %s %s",
( mode==TIMELINE_MODE_BEFORE ||
mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/
);
if( zType && (zType[0]!='a') ){
blob_append_sql(&sql, "\n AND event.type=%Q ", zType);
}
/* When zFilePattern is specified, compute complete ancestry;
* limit later at print_timeline() */
if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
if( mode==TIMELINE_MODE_CHILDREN ){
compute_descendants(objid, (zFilePattern ? 0 : n));
}else{
compute_ancestors(objid, (zFilePattern ? 0 : n), 0, 0);
}
blob_append_sql(&sql, "\n AND blob.rid IN ok");
}
if( zFilePattern ){
blob_append(&sql,
"\n AND EXISTS(SELECT 1 FROM mlink\n"
" WHERE mlink.mid=event.objid\n"
" AND mlink.fnid IN ", -1);
if( filenames_are_case_sensitive() ){
blob_append_sql(&sql,
|
| ︙ | ︙ | |||
3792 3793 3794 3795 3796 3797 3798 |
" AND e.comment LIKE '_checkin/%%'\n"
" LEFT JOIN tagxref tx ON tx.rid=b.rid AND tx.tagid=%d\n"
" WHERE tx.value='%q'\n"
")\n" /* No merge closures */
" AND (tagxref.value IS NULL OR tagxref.value='%q')",
zBr, zBr, zBr, TAG_BRANCH, zBr, zBr);
}
| > > > > > > | > | 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 |
" AND e.comment LIKE '_checkin/%%'\n"
" LEFT JOIN tagxref tx ON tx.rid=b.rid AND tx.tagid=%d\n"
" WHERE tx.value='%q'\n"
")\n" /* No merge closures */
" AND (tagxref.value IS NULL OR tagxref.value='%q')",
zBr, zBr, zBr, TAG_BRANCH, zBr, zBr);
}
if( mode==TIMELINE_MODE_AFTER ){
/* Complete the above outer select. */
blob_append_sql(&sql,
"\nORDER BY event.mtime LIMIT abs(%d)) t ORDER BY t.mDateTime DESC;", n);
}else{
blob_append_sql(&sql, "\nORDER BY event.mtime DESC");
}
if( iOffset>0 ){
/* Don't handle LIMIT here, otherwise print_timeline()
* will not determine the end-marker correctly! */
blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset);
}
if( showSql ){
fossil_print("%s\n", blob_str(&sql));
|
| ︙ | ︙ |
Changes to src/tkt.c.
| ︙ | ︙ | |||
186 187 188 189 190 191 192 193 194 |
** Otherwise, db_reveal() is a no-op and the content remains
** obscured.
*/
static void initializeVariablesFromDb(void){
const char *zName;
Stmt q;
int i, n, size, j;
zName = PD("name","-none-");
| > | > > > > > > > | | | 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
** Otherwise, db_reveal() is a no-op and the content remains
** obscured.
*/
static void initializeVariablesFromDb(void){
const char *zName;
Stmt q;
int i, n, size, j;
const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";
zName = PD("name","-none-");
db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
"datetime(%s,toLocal()) AS tkt_datetime_creation, "
"julianday('now') - tkt_mtime, "
"julianday('now') - %s, *"
" FROM ticket WHERE tkt_uuid GLOB '%q*'",
zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
zName);
if( db_step(&q)==SQLITE_ROW ){
n = db_column_count(&q);
for(i=0; i<n; i++){
const char *zVal = db_column_text(&q, i);
const char *zName = db_column_name(&q, i);
char *zRevealed = 0;
if( zVal==0 ){
zVal = "";
}else if( strncmp(zName, "private_", 8)==0 ){
zVal = zRevealed = db_reveal(zVal);
}
if( (j = fieldId(zName))>=0 ){
aField[j].zValue = mprintf("%s", zVal);
}else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
/* TICKET table columns that begin with "tkt_" are always safe */
Th_Store(zName, zVal);
}
free(zRevealed);
}
Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
}
db_finalize(&q);
for(i=0; i<nField; i++){
if( Th_Fetch(aField[i].zName, &size)==0 ){
Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
}
}
}
/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
int i;
const char *z;
for(i=0; (z = cgi_parameter_name(i))!=0; i++){
Th_StoreUnsafe(z, P(z));
}
}
/*
** Information about a single J-card
*/
struct jCardInfo {
|
| ︙ | ︙ | |||
767 768 769 770 771 772 773 |
if( !showTimeline && g.perm.Hyperlink ){
style_submenu_element("Timeline", "%R/info/%T", zUuid);
}
zFullName = db_text(0,
"SELECT tkt_uuid FROM ticket"
" WHERE tkt_uuid GLOB '%q*'", zUuid);
if( g.perm.WrWiki && g.perm.WrTkt ){
| | > | 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 |
if( !showTimeline && g.perm.Hyperlink ){
style_submenu_element("Timeline", "%R/info/%T", zUuid);
}
zFullName = db_text(0,
"SELECT tkt_uuid FROM ticket"
" WHERE tkt_uuid GLOB '%q*'", zUuid);
if( g.perm.WrWiki && g.perm.WrTkt ){
style_submenu_element("Edit Description",
"%R/wikiedit?name=ticket/%T", zFullName);
}
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1);
ticket_init();
initializeVariablesFromCGI();
getAllTicketFields();
initializeVariablesFromDb();
zScript = ticket_viewpage_code();
|
| ︙ | ︙ | |||
810 811 812 813 814 815 816 |
int idx;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "append_field FIELD STRING");
}
if( g.thTrace ){
Th_Trace("append_field %#h {%#h}<br>\n",
| | | | | 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 |
int idx;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "append_field FIELD STRING");
}
if( g.thTrace ){
Th_Trace("append_field %#h {%#h}<br>\n",
TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
}
for(idx=0; idx<nField; idx++){
if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
&& aField[idx].zName[TH1_LEN(argl[1])]==0 ){
break;
}
}
if( idx>=nField ){
Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
return TH_ERROR;
}
|
| ︙ | ︙ | |||
930 931 932 933 934 935 936 937 938 939 940 941 942 943 |
}
for(i=0; i<nField; i++){
const char *zValue;
int nValue;
if( aField[i].zAppend ) continue;
zValue = Th_Fetch(aField[i].zName, &nValue);
if( zValue ){
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
|| memcmp(zValue, aField[i].zValue, nValue)!=0
||(int)strlen(aField[i].zValue)!=nValue
){
if( memcmp(aField[i].zName, "private_", 8)==0 ){
zValue = db_conceal(zValue, nValue);
| > | 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 |
}
for(i=0; i<nField; i++){
const char *zValue;
int nValue;
if( aField[i].zAppend ) continue;
zValue = Th_Fetch(aField[i].zName, &nValue);
if( zValue ){
nValue = TH1_LEN(nValue);
while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
|| memcmp(zValue, aField[i].zValue, nValue)!=0
||(int)strlen(aField[i].zValue)!=nValue
){
if( memcmp(aField[i].zName, "private_", 8)==0 ){
zValue = db_conceal(zValue, nValue);
|
| ︙ | ︙ | |||
1024 1025 1026 1027 1028 1029 1030 1031 |
initializeVariablesFromDb();
if( g.zPath[0]=='d' ) showAllFields();
form_begin(0, "%R/%s", g.zPath);
if( P("date_override") && g.perm.Setup ){
@ <input type="hidden" name="date_override" value="%h(P("date_override"))">
}
zScript = ticket_newpage_code();
if( g.zLogin && g.zLogin[0] ){
| > < < < | | | > > > | > | 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 |
initializeVariablesFromDb();
if( g.zPath[0]=='d' ) showAllFields();
form_begin(0, "%R/%s", g.zPath);
if( P("date_override") && g.perm.Setup ){
@ <input type="hidden" name="date_override" value="%h(P("date_override"))">
}
zScript = ticket_newpage_code();
Th_Store("private_contact", "");
if( g.zLogin && g.zLogin[0] ){
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.zLogin);
if( uid ){
char * zEmail =
db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
uid);
if( zEmail ){
Th_StoreUnsafe("private_contact", zEmail);
fossil_free(zEmail);
}
}
}
Th_StoreUnsafe("login", login_name());
Th_Store("date", db_text(0, "SELECT datetime('now')"));
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
(void*)&zNewUuid, 0);
if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
if( P("submitandnew") ){
cgi_redirect(mprintf("%R/tktnew/%s", zNewUuid));
}else{
cgi_redirect(mprintf("%R/tktview/%s", zNewUuid));
}
return;
}
captcha_generate(0);
@ </form>
if( g.thTrace ) Th_Trace("END_TKTVIEW<br>\n", -1);
style_finish_page();
}
|
| ︙ | ︙ | |||
1110 1111 1112 1113 1114 1115 1116 | getAllTicketFields(); initializeVariablesFromCGI(); initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); @ <input type="hidden" name="name" value="%s(zName)"> zScript = ticket_editpage_code(); | | | 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 |
getAllTicketFields();
initializeVariablesFromCGI();
initializeVariablesFromDb();
if( g.zPath[0]=='d' ) showAllFields();
form_begin(0, "%R/%s", g.zPath);
@ <input type="hidden" name="name" value="%s(zName)">
zScript = ticket_editpage_code();
Th_StoreUnsafe("login", login_name());
Th_Store("date", db_text(0, "SELECT datetime('now')"));
Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
cgi_redirect(mprintf("%R/tktview/%s", zName));
return;
|
| ︙ | ︙ |
Changes to src/tktsetup.c.
| ︙ | ︙ | |||
123 124 125 126 127 128 129 |
login_check_credentials();
if( !g.perm.Setup ){
login_needed(0);
return;
}
style_set_current_feature("tktsetup");
| | | 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
login_check_credentials();
if( !g.perm.Setup ){
login_needed(0);
return;
}
style_set_current_feature("tktsetup");
if( P("setup") ){
cgi_redirect("tktsetup");
}
isSubmit = P("submit")!=0;
z = P("x");
if( z==0 ){
z = db_get(zDbField, zDfltValue);
}
|
| ︙ | ︙ | |||
162 163 164 165 166 167 168 169 170 171 172 173 174 175 | @ </p></blockquote> @ </div></form> @ <hr> @ <h2>Default %s(zTitle)</h2> @ <blockquote><pre> @ %h(zDfltValue) @ </pre></blockquote> style_finish_page(); } /* ** WEBPAGE: tktsetup_tab ** Administrative page for defining the "ticket" table used ** to hold ticket information. | > | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
@ </p></blockquote>
@ </div></form>
@ <hr>
@ <h2>Default %s(zTitle)</h2>
@ <blockquote><pre>
@ %h(zDfltValue)
@ </pre></blockquote>
style_submenu_element("Back", "%R/tktsetup");
style_finish_page();
}
/*
** WEBPAGE: tktsetup_tab
** Administrative page for defining the "ticket" table used
** to hold ticket information.
|
| ︙ | ︙ | |||
299 300 301 302 303 304 305 |
30
);
}
static const char zDefaultNew[] =
@ <th1>
@ if {![info exists mutype]} {set mutype Markdown}
| | | 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
30
);
}
static const char zDefaultNew[] =
@ <th1>
@ if {![info exists mutype]} {set mutype Markdown}
@ if {[info exists submit] || [info exists submitandnew]} {
@ set status Open
@ if {$mutype eq "HTML"} {
@ set mimetype "text/html"
@ } elseif {$mutype eq "Wiki"} {
@ set mimetype "text/x-fossil-wiki"
@ } elseif {$mutype eq "Markdown"} {
@ set mimetype text/x-markdown
|
| ︙ | ︙ | |||
347 348 349 350 351 352 353 354 355 356 357 358 359 360 | @ <tr> @ <td align="right">Severity:</td> @ <td align="left"><th1>combobox severity $severity_choices 1</th1></td> @ <td align="left">How debilitating is the problem? How badly does the problem @ affect the operation of the product?</td> @ </tr> @ @ <tr> @ <td align="right">EMail:</td> @ <td align="left"> @ <input name="private_contact" value="$<private_contact>" size="30"> @ </td> @ <td align="left"><u>Not publicly visible</u> @ Used by developers to contact you with questions.</td> | > > > > > > > > > > > > > > > > > > | 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
@ <tr>
@ <td align="right">Severity:</td>
@ <td align="left"><th1>combobox severity $severity_choices 1</th1></td>
@ <td align="left">How debilitating is the problem? How badly does the problem
@ affect the operation of the product?</td>
@ </tr>
@
@ <th1>
@ if {[capexpr {w}]} {
@ html {<tr><td class="tktDspLabel">Priority:</td><td>}
@ combobox priority $priority_choices 1
@ html {
@ <td align="left">How important is the affected functionality?</td>
@ </td></tr>
@ }
@
@ html {<tr><td class="tktDspLabel">Subsystem:</td><td>}
@ combobox subsystem $subsystem_choices 1
@ html {
@ <td align="left">Which subsystem is affected?</td>
@ </td></tr>
@ }
@ }
@ </th1>
@
@ <tr>
@ <td align="right">EMail:</td>
@ <td align="left">
@ <input name="private_contact" value="$<private_contact>" size="30">
@ </td>
@ <td align="left"><u>Not publicly visible</u>
@ Used by developers to contact you with questions.</td>
|
| ︙ | ︙ | |||
403 404 405 406 407 408 409 | @ @ <th1>enable_output [info exists preview]</th1> @ <tr> @ <td><td align="left"> @ <input type="submit" name="submit" value="Submit"> @ </td> @ <td align="left">After filling in the information above, press this | | > > > > > > > > | | 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | @ @ <th1>enable_output [info exists preview]</th1> @ <tr> @ <td><td align="left"> @ <input type="submit" name="submit" value="Submit"> @ </td> @ <td align="left">After filling in the information above, press this @ button to create the new ticket.</td> @ </tr> @ @ <tr> @ <td><td align="left"> @ <input type="submit" name="submitandnew" value="Submit and New"> @ </td> @ <td align="left">Create the new ticket and start another @ ticket form with the inputs.</td> @ </tr> @ <th1>enable_output 1</th1> @ @ <tr> @ <td><td align="left"> @ <input type="submit" name="cancel" value="Cancel"> @ </td> @ <td>Abandon and forget this ticket.</td> @ </tr> @ </table> ; /* ** Return the code used to generate the new ticket page */ |
| ︙ | ︙ | |||
452 453 454 455 456 457 458 |
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@ html "<td class='tktDspValue' colspan='3'>"
@ copybtn hash-tk 0 $tkt_uuid 2
@ if {[hascap s]} {
| | > > > > | 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@ html "<td class='tktDspValue' colspan='3'>"
@ copybtn hash-tk 0 $tkt_uuid 2
@ if {[hascap s]} {
@ puts " ($tkt_id)"
@ }
@ html "</td></tr>\n"
@ } else {
@ if {[hascap s]} {
@ html "<td class='tktDspValue' colspan='3'>Deleted "
@ html "(0)</td></tr>\n"
@ } else {
@ html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n"
@ }
@ }
@
@ if {[capexpr {n}]} {
@ submenu link "Copy Ticket" /tktnew/$tkt_uuid
@ }
@ </th1>
@ <tr><td class="tktDspLabel">Title:</td>
@ <td class="tktDspValue" colspan="3">
@ $<title>
@ </td></tr>
@ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue">
@ $<status>
|
| ︙ | ︙ | |||
489 490 491 492 493 494 495 |
@ </td>
@ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue">
@ $<resolution>
@ </td></tr>
@ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime]} {
| | > > > > > > > > > > > > > > | > < > > > > > > > > > > > > > > > > > > | > > > > > > > > | 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 |
@ </td>
@ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue">
@ $<resolution>
@ </td></tr>
@ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime]} {
@ puts $tkt_datetime
@ }
@ if {[info exists tkt_mage]} {
@ html "<br>[htmlize $tkt_mage] ago"
@ }
@ </th1>
@ </td>
@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime_creation]} {
@ puts $tkt_datetime_creation
@ }
@ if {[info exists tkt_cage]} {
@ html "<br>[htmlize $tkt_cage] ago"
@ }
@ </th1>
@ </td></tr>
@ <th1>enable_output [hascap e]</th1>
@ <tr>
@ <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
@ $<private_contact>
@ </td>
@ </tr>
@ <th1>enable_output 1</th1>
@ <tr><td class="tktDspLabel">Version Found In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ <th1>
@ set versionlink ""
@ set urlfoundin [httpize $foundin]
@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
@ if [regexp $tagpattern $foundin] {
@ query {SELECT count(*) AS match FROM tag
@ WHERE tagname=concat('sym-',$foundin)} {
@ if {$match} {set versionlink "timeline?t=$urlfoundin"}
@ }
@ }
@ set hashpattern {^[0-9a-f]+$}
@ if [regexp $hashpattern $foundin] {
@ set pattern $foundin*
@ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
@ if {$match} {set versionlink "info/$urlfoundin"}
@ }
@ }
@ if {$versionlink eq ""} {
@ puts $foundin
@ } else {
@ html "<a href=\""
@ puts $versionlink
@ html "\">"
@ puts $foundin
@ html "</a>"
@ }
@ </th1>
@ </td></tr>
@ </table>
@
@ <th1>
@ wiki_assoc "ticket" $tkt_uuid
@ </th1>
@
|
| ︙ | ︙ | |||
535 536 537 538 539 540 541 |
@ mimetype as xmimetype, icomment AS xcomment,
@ username AS xusername
@ FROM ticketchng
@ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
@ if {$seenRow} {
@ html "<hr>\n"
@ } else {
| | > | | | | | 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
@ mimetype as xmimetype, icomment AS xcomment,
@ username AS xusername
@ FROM ticketchng
@ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
@ if {$seenRow} {
@ html "<hr>\n"
@ } else {
@ html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
@ html "User Comments:</td></tr>\n"
@ html "<tr><td colspan='5' class='tktDspValue'>\n"
@ set seenRow 1
@ }
@ html "<span class='tktDspCommenter'>"
@ puts $xlogin
@ if {$xlogin ne $xusername && [string length $xusername]>0} {
@ puts " (claiming to be $xusername)"
@ }
@ puts " added on $xdate:"
@ html "</span>\n"
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@ set r [randhex]
@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@ } elseif {$xmimetype eq "text/x-markdown"} {
@ html [lindex [markdown $xcomment] 1]
@ } elseif {$xmimetype eq "text/html"} {
@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
|
| ︙ | ︙ | |||
709 710 711 712 713 714 715 716 717 718 719 720 721 722 |
@ <tr>
@ <td align="right">
@ <input type="submit" name="cancel" value="Cancel">
@ </td>
@ <td>Abandon this edit</td>
@ </tr>
@
@ </table>
;
/*
** Return the code used to generate the edit ticket page
*/
const char *ticket_editpage_code(void){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 |
@ <tr>
@ <td align="right">
@ <input type="submit" name="cancel" value="Cancel">
@ </td>
@ <td>Abandon this edit</td>
@ </tr>
@
@ <th1>
@ set seenRow 0
@ set alwaysPlaintext [info exists plaintext]
@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
@ mimetype as xmimetype, icomment AS xcomment,
@ username AS xusername
@ FROM ticketchng
@ WHERE tkt_id=$tkt_id AND length(icomment)>0} {
@ if {$seenRow} {
@ html "<hr>\n"
@ } else {
@ html "<tr><td colspan='2'><hr></td></tr>\n"
@ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
@ html "Previous User Comments:</td></tr>\n"
@ html "<tr><td colspan='2' class='tktDspValue'>\n"
@ set seenRow 1
@ }
@ html "<span class='tktDspCommenter'>"
@ puts $xlogin
@ if {$xlogin ne $xusername && [string length $xusername]>0} {
@ puts " (claiming to be $xusername)"
@ }
@ puts " added on $xdate:"
@ html "</span>\n"
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@ set r [randhex]
@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@ } elseif {$xmimetype eq "text/x-markdown"} {
@ html [lindex [markdown $xcomment] 1]
@ } elseif {$xmimetype eq "text/html"} {
@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
@ } else {
@ set r [randhex]
@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
@ }
@ }
@ if {$seenRow} {html "</td></tr>\n"}
@ </th1>
@
@ </table>
;
/*
** Return the code used to generate the edit ticket page
*/
const char *ticket_editpage_code(void){
|
| ︙ | ︙ | |||
807 808 809 810 811 812 813 |
@ CASE WHEN status IN ('Open','Verified') THEN '#f2dcdc'
@ WHEN status='Review' THEN '#e8e8e8'
@ WHEN status='Fixed' THEN '#cfe8bd'
@ WHEN status='Tested' THEN '#bde5d6'
@ WHEN status='Deferred' THEN '#cacae5'
@ ELSE '#c8c8c8' END AS 'bgcolor',
@ substr(tkt_uuid,1,10) AS '#',
| > | | 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 |
@ CASE WHEN status IN ('Open','Verified') THEN '#f2dcdc'
@ WHEN status='Review' THEN '#e8e8e8'
@ WHEN status='Fixed' THEN '#cfe8bd'
@ WHEN status='Tested' THEN '#bde5d6'
@ WHEN status='Deferred' THEN '#cacae5'
@ ELSE '#c8c8c8' END AS 'bgcolor',
@ substr(tkt_uuid,1,10) AS '#',
@ datetime(tkt_ctime) AS 'created',
@ datetime(tkt_mtime) AS 'modified',
@ type,
@ status,
@ subsystem,
@ title,
@ comment AS '_comments'
@ FROM ticket
;
|
| ︙ | ︙ | |||
941 942 943 944 945 946 947 948 949 950 | @ <hr> @ <p> @ <input type="submit" name="submit" value="Apply Changes"> @ <input type="submit" name="setup" value="Cancel"> @ </p> @ </div></form> db_end_transaction(0); style_finish_page(); } | > | 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 |
@ <hr>
@ <p>
@ <input type="submit" name="submit" value="Apply Changes">
@ <input type="submit" name="setup" value="Cancel">
@ </p>
@ </div></form>
db_end_transaction(0);
style_submenu_element("Back", "%R/tktsetup");
style_finish_page();
}
|
Changes to src/undo.c.
| ︙ | ︙ | |||
68 69 70 71 72 73 74 |
blob_zero(&new);
old_exists = db_column_int(&q, 1);
old_exe = db_column_int(&q, 2);
if( old_exists ){
db_ephemeral_blob(&q, 0, &new);
}
if( file_unsafe_in_tree_path(zFullname) ){
| | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
blob_zero(&new);
old_exists = db_column_int(&q, 1);
old_exe = db_column_int(&q, 2);
if( old_exists ){
db_ephemeral_blob(&q, 0, &new);
}
if( file_unsafe_in_tree_path(zFullname) ){
/* do nothing with this unsafe file */
}else if( old_exists ){
if( new_exists ){
fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
}else{
fossil_print("NEW %s\n", zPathname);
}
if( new_exists && (new_link || old_link) ){
|
| ︙ | ︙ |
Changes to src/unversioned.c.
| ︙ | ︙ | |||
244 245 246 247 248 249 250 251 252 253 254 255 256 257 | ** the name to be different in the repository versus ** what appears on disk, but it only allows adding ** a single file at a time. ** ** cat FILE ... Concatenate the content of FILEs to stdout. ** ** edit FILE Bring up FILE in a text editor for modification. ** ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk ** ** list | ls Show all unversioned files held in the local ** repository. ** ** Options: | > > | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | ** the name to be different in the repository versus ** what appears on disk, but it only allows adding ** a single file at a time. ** ** cat FILE ... Concatenate the content of FILEs to stdout. ** ** edit FILE Bring up FILE in a text editor for modification. ** Options: ** --editor NAME Name of the text editor to use ** ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk ** ** list | ls Show all unversioned files held in the local ** repository. ** ** Options: |
| ︙ | ︙ | |||
359 360 361 362 363 364 365 |
}else if( strncmp(zCmd, "edit", nCmd)==0 ){
const char *zEditor; /* Name of the text-editor command */
const char *zTFile; /* Temporary file */
const char *zUVFile; /* Name of the unversioned file */
char *zCmd; /* Command to run the text editor */
Blob content; /* Content of the unversioned file */
| < < < > > > | 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
}else if( strncmp(zCmd, "edit", nCmd)==0 ){
const char *zEditor; /* Name of the text-editor command */
const char *zTFile; /* Temporary file */
const char *zUVFile; /* Name of the unversioned file */
char *zCmd; /* Command to run the text editor */
Blob content; /* Content of the unversioned file */
zEditor = fossil_text_editor();
if( zEditor==0 ){
fossil_fatal("no text editor - set the VISUAL env variable");
}
verify_all_options();
if( g.argc!=4) usage("edit UVFILE");
zUVFile = g.argv[3];
zTFile = fossil_temp_filename();
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
db_begin_transaction();
content_rcvid_init("#!fossil unversioned edit");
if( unversioned_content(zUVFile, &content)==0 ){
fossil_fatal("no such uv-file: %Q", zUVFile);
}
|
| ︙ | ︙ |
Changes to src/user.c.
| ︙ | ︙ | |||
324 325 326 327 328 329 330 | ** ** Query or set the capabilities for user USERNAME ** ** > fossil user contact USERNAME ?CONTACT-INFO? ** ** Query or set contact information for user USERNAME ** | | | > > > > > > > | 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | ** ** Query or set the capabilities for user USERNAME ** ** > fossil user contact USERNAME ?CONTACT-INFO? ** ** Query or set contact information for user USERNAME ** ** > fossil user default ?OPTIONS? ?USERNAME? ** ** Query or set the default user. The default user is the ** user for command-line interaction. If USERNAME is an ** empty string, then the default user is unset from the ** repository and will subsequently be determined by the -U ** command-line option or by environment variables ** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order. ** OPTIONS: ** ** -v|--verbose Show how the default user is computed ** ** > fossil user list | ls ** ** List all users known to the repository ** ** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD? ** |
| ︙ | ︙ | |||
383 384 385 386 387 388 389 |
"INSERT INTO user(login,pw,cap,info,mtime)"
"VALUES(%B,%Q,%B,%B,now())",
&login, zPw, &caps, &contact
);
db_protect_pop();
free(zPw);
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
| > > | | | > > > > > > | | | | | | | | > > > > > > > > > > > > > > > > > > > > > | 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 |
"INSERT INTO user(login,pw,cap,info,mtime)"
"VALUES(%B,%Q,%B,%B,now())",
&login, zPw, &caps, &contact
);
db_protect_pop();
free(zPw);
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
int eVerbose = find_option("verbose","v",0)!=0;
verify_all_options();
if( g.argc>3 ){
const char *zUser = g.argv[3];
if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
db_begin_transaction();
if( g.localOpen ){
db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
}
db_unset("default-user",0);
db_commit_transaction();
}else{
if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
fossil_fatal("no such user: %s", g.argv[3]);
}
if( g.localOpen ){
db_lset("default-user", g.argv[3]);
}else{
db_set("default-user", g.argv[3], 0);
}
}
}
if( g.argc==3 || eVerbose ){
int eHow = user_select();
const char *zHow = "???";
switch( eHow ){
case 1: zHow = "-U option"; break;
case 2: zHow = "previously set"; break;
case 3: zHow = "local check-out"; break;
case 4: zHow = "repository"; break;
case 5: zHow = "FOSSIL_USER"; break;
case 6: zHow = "USER"; break;
case 7: zHow = "LOGNAME"; break;
case 8: zHow = "USERNAME"; break;
case 9: zHow = "URL"; break;
}
if( eVerbose ){
fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
}else{
fossil_print("%s\n", g.zLogin);
}
}
}else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
Stmt q;
db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
while( db_step(&q)==SQLITE_ROW ){
|
| ︙ | ︙ | |||
494 495 496 497 498 499 500 | } /* ** Figure out what user is at the controls. ** ** (1) Use the --user and -U command-line options. ** | > > | | | | | | | | | | | | | | | | | | 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 |
}
/*
** Figure out what user is at the controls.
**
** (1) Use the --user and -U command-line options.
**
** (2) The name used for login (if there was a login).
**
** (3) If the local database is open, check in VVAR.
**
** (4) Check the default-user in the repository
**
** (5) Try the FOSSIL_USER environment variable.
**
** (6) Try the USER environment variable.
**
** (7) Try the LOGNAME environment variable.
**
** (8) Try the USERNAME environment variable.
**
** (9) Check if the user can be extracted from the remote URL.
**
** The user name is stored in g.zLogin. The uid is in g.userUid.
*/
int user_select(void){
UrlData url;
if( g.userUid ) return 1;
if( g.zLogin ){
if( attempt_user(g.zLogin)==0 ){
fossil_fatal("no such user: %s", g.zLogin);
}else{
return 2;
}
}
if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;
if( attempt_user(db_get("default-user", 0)) ) return 4;
if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;
if( attempt_user(fossil_getenv("USER")) ) return 6;
if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;
if( attempt_user(fossil_getenv("USERNAME")) ) return 8;
memset(&url, 0, sizeof(url));
url_parse_local(0, URL_USE_CONFIG, &url);
if( url.user && attempt_user(url.user) ) return 9;
fossil_print(
"Cannot figure out who you are! Consider using the --user\n"
"command line option, setting your USER environment variable,\n"
"or setting a default user with \"fossil user default USER\".\n"
);
fossil_fatal("cannot determine user");
|
| ︙ | ︙ |
Changes to src/util.c.
| ︙ | ︙ | |||
664 665 666 667 668 669 670 | } /* ** Return the name of the users preferred text editor. Return NULL if ** not found. ** ** Search algorithm: | > | | | | | > > > | > > > > > > | 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 |
}
/*
** Return the name of the users preferred text editor. Return NULL if
** not found.
**
** Search algorithm:
** (1) The value of the --editor command-line argument
** (2) The local "editor" setting
** (3) The global "editor" setting
** (4) The VISUAL environment variable
** (5) The EDITOR environment variable
** (6) Any of the following programs that are available:
** notepad, nano, pico, jove, edit, vi, vim, ed,
**
** The search only occurs once, the first time this routine is called.
** Second and subsequent invocations always return the same value.
*/
const char *fossil_text_editor(void){
static const char *zEditor = 0;
const char *azStdEd[] = {
"notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed"
};
int i = 0;
if( zEditor==0 ){
zEditor = find_option("editor",0,1);
}
if( zEditor==0 ){
zEditor = db_get("editor", 0);
}
if( zEditor==0 ){
zEditor = fossil_getenv("VISUAL");
}
if( zEditor==0 ){
zEditor = fossil_getenv("EDITOR");
}
while( zEditor==0 && i<count(azStdEd) ){
|
| ︙ | ︙ |
Changes to src/winfile.c.
| ︙ | ︙ | |||
503 504 505 506 507 508 509 |
fi2.FileId[5], fi2.FileId[4],
fi2.FileId[3], fi2.FileId[2],
fi2.FileId[1], fi2.FileId[0]);
}
}
if( zFileId==0 ){
if( GetFileInformationByHandle(hFile,&fi) ){
| | | | 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
fi2.FileId[5], fi2.FileId[4],
fi2.FileId[3], fi2.FileId[2],
fi2.FileId[1], fi2.FileId[0]);
}
}
if( zFileId==0 ){
if( GetFileInformationByHandle(hFile,&fi) ){
ULARGE_INTEGER FileId = {{
/*.LowPart = */ fi.nFileIndexLow,
/*.HighPart = */ fi.nFileIndexHigh
}};
zFileId = mprintf(
"%08x/%016llx",
fi.dwVolumeSerialNumber,(u64)FileId.QuadPart);
}
}
CloseHandle(hFile);
}
|
| ︙ | ︙ |
Changes to src/xfer.c.
| ︙ | ︙ | |||
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 |
int sz = db_column_int(&uvq,3);
if( zHash==0 ){ sz = 0; zHash = "-"; }
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
zName, mtime, zHash, sz);
}
db_finalize(&uvq);
}
/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
| > > > > > > > > > > > > | | 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 |
int sz = db_column_int(&uvq,3);
if( zHash==0 ){ sz = 0; zHash = "-"; }
blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
zName, mtime, zHash, sz);
}
db_finalize(&uvq);
}
/*
** Return a string that contains supplemental information about a
** "not authorized" error. The string might be empty if no additional
** information is available.
*/
static char *whyNotAuth(void){
if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
}
return "";
}
/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
@ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
}
/*
** Return the common TH1 code to evaluate prior to evaluating any other
** TH1 transfer notification scripts.
*/
const char *xfer_common_code(void){
|
| ︙ | ︙ | |||
1314 1315 1316 1317 1318 1319 1320 |
** file HASH DELTASRC SIZE \n CONTENT
**
** Server accepts a file from the client.
*/
if( blob_eq(&xfer.aToken[0], "file") ){
if( !isPush ){
cgi_reset_content();
| | | | 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 |
** file HASH DELTASRC SIZE \n CONTENT
**
** Server accepts a file from the client.
*/
if( blob_eq(&xfer.aToken[0], "file") ){
if( !isPush ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
nErr++;
break;
}
xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
if( blob_size(&xfer.err) ){
cgi_reset_content();
@ error %T(blob_str(&xfer.err))
nErr++;
break;
}
}else
/* cfile HASH USIZE CSIZE \n CONTENT
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
**
** Server accepts a compressed file from the client.
*/
if( blob_eq(&xfer.aToken[0], "cfile") ){
if( !isPush ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
nErr++;
break;
}
xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
if( blob_size(&xfer.err) ){
cgi_reset_content();
@ error %T(blob_str(&xfer.err))
|
| ︙ | ︙ | |||
1459 1460 1461 1462 1463 1464 1465 |
nErr++;
break;
}
login_check_credentials();
if( blob_eq(&xfer.aToken[0], "pull") ){
if( !g.perm.Read ){
cgi_reset_content();
| | | | | | 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 |
nErr++;
break;
}
login_check_credentials();
if( blob_eq(&xfer.aToken[0], "pull") ){
if( !g.perm.Read ){
cgi_reset_content();
@ error not\sauthorized\sto\sread%s(whyNotAuth())
nErr++;
break;
}
isPull = 1;
}else{
if( !g.perm.Write ){
if( !isPull ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite%s(whyNotAuth())
nErr++;
}else{
@ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
}
}else{
isPush = 1;
}
}
}else
/* clone ?PROTOCOL-VERSION? ?SEQUENCE-NUMBER?
**
** The client knows nothing. Tell all.
*/
if( blob_eq(&xfer.aToken[0], "clone") ){
int iVers;
login_check_credentials();
if( !g.perm.Clone ){
cgi_reset_content();
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
@ error not\sauthorized\sto\sclone%s(whyNotAuth())
nErr++;
break;
}
if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
@ pragma uv-pull-only
send_unversioned_catalog(&xfer);
uvCatalogSent = 1;
|
| ︙ | ︙ | |||
1590 1591 1592 1593 1594 1595 1596 |
xfer_fatal_error("invalid config record");
return;
}
blob_zero(&content);
blob_extract(xfer.pIn, size, &content);
if( !g.perm.Admin ){
cgi_reset_content();
| | | 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 |
xfer_fatal_error("invalid config record");
return;
}
blob_zero(&content);
blob_extract(xfer.pIn, size, &content);
if( !g.perm.Admin ){
cgi_reset_content();
@ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
nErr++;
break;
}
configure_receive(zName, &content, CONFIGSET_ALL);
blob_reset(&content);
blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
}else
|
| ︙ | ︙ |
Changes to test/many-www.tcl.
| ︙ | ︙ | |||
22 23 24 25 26 27 28 | /taglist /reportlist /setup /dir /wcontent /attachlist /taglist | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | /taglist /reportlist /setup /dir /wcontent /attachlist /taglist /test-env /stat /rcvfromlist /urllist /modreq /info/d5c4 /test-all-help /leaves |
| ︙ | ︙ |
Changes to test/tester.tcl.
| ︙ | ︙ | |||
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
max-upload \
mimetypes \
mtime-changes \
mv-rm-files \
pgp-command \
preferred-diff-type \
proxy \
redirect-to-https \
relative-paths \
repo-cksum \
repolist-skin \
robot-restrict \
robots-txt \
safe-html \
self-pw-reset \
self-register \
sitemap-extra \
ssh-command \
ssl-ca-location \
ssl-identity \
tclsh \
th1-setup \
th1-uri-regexp \
ticket-default-report \
timeline-utc \
user-color-map \
uv-sync \
web-browser]
fossil test-th-eval "hasfeature legacyMvRm"
if {[normalize_result] eq "1"} {
lappend result mv-rm-files
}
| > > > > > > > > > | 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
max-upload \
mimetypes \
mtime-changes \
mv-rm-files \
pgp-command \
preferred-diff-type \
proxy \
raw-bgcolor \
redirect-to-https \
relative-paths \
repo-cksum \
repolist-skin \
robot-restrict \
robots-txt \
safe-html \
self-pw-reset \
self-register \
show-repolist-desc \
show-repolist-lg \
sitemap-extra \
ssh-command \
ssl-ca-location \
ssl-identity \
tclsh \
th1-setup \
th1-uri-regexp \
ticket-default-report \
timeline-hard-newlines \
timeline-plaintext \
timeline-truncate-at-blank \
timeline-tslink-info \
timeline-utc \
user-color-map \
verify-comments \
uv-sync \
vuln-report \
web-browser]
fossil test-th-eval "hasfeature legacyMvRm"
if {[normalize_result] eq "1"} {
lappend result mv-rm-files
}
|
| ︙ | ︙ |
Added test/th1-taint.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#
# Copyright (c) 2025 D. Richard Hipp
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Simplified BSD License (also
# known as the "2-Clause License" or "FreeBSD License".)
#
# This program is distributed in the hope that it will be useful,
# but without any warranty; without even the implied warranty of
# merchantability or fitness for a particular purpose.
#
# Author contact information:
# drh@hwaci.com
# http://www.hwaci.com/drh/
#
############################################################################
#
# TH1 Commands
#
set path [file dirname [info script]]; test_setup
proc taint-test {testnum th1script expected} {
global fossilexe
set rc [catch {exec $fossilexe test-th-eval $th1script} got]
if {$rc} {
test th1-taint-$testnum 0
puts $got
return
}
if {$got ne $expected} {
test th1-taint-$testnum 0
puts " Expected: $expected"
puts " Got: $got"
} else {
test th1-taint-$testnum 1
}
}
taint-test 10 {string is tainted abcd} 0
taint-test 20 {string is tainted [taint abcd]} 1
taint-test 30 {string is tainted [untaint [taint abcd]]} 0
taint-test 40 {string is tainted [untaint abcde]} 0
taint-test 50 {string is tainted "abc[taint def]ghi"} 1
taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1
taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1
taint-test 200 {string is tainted [list abc def ghi]} 0
taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
taint-test 230 {string is tainted [list abc def [taint ghi]]} 1
taint-test 300 {
set res {}
foreach x [list abc [taint def] ghi] {
lappend res [string is tainted $x]
}
set res
} {1 1 1}
taint-test 310 {
set res {}
foreach {x y} [list abc [taint def] ghi jkl] {
lappend res [string is tainted $x] [string is tainted $y]
}
set res
} {1 1 1 1}
taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0
taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1
taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1
taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1
taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1
test_cleanup
|
Changes to test/th1.test.
| ︙ | ︙ | |||
793 794 795 796 797 798 799 |
test th1-defHeader-2 {[string match *<body> [normalize_result]] || \
[string match "*<body class=\"\$current_feature\
rpage-\$requested_page\
cpage-\$canonical_page\">" [normalize_result]]}
###############################################################################
| | | | | | 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 |
test th1-defHeader-2 {[string match *<body> [normalize_result]] || \
[string match "*<body class=\"\$current_feature\
rpage-\$requested_page\
cpage-\$canonical_page\">" [normalize_result]]}
###############################################################################
#fossil test-th-eval "styleHeader {Page Title Here}"
#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
test_in_checkout th1-header-2 {
fossil test-th-eval --open-config "styleHeader {Page Title Here}"
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}
###############################################################################
#fossil test-th-eval "styleFooter"
#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}
###############################################################################
|
| ︙ | ︙ | |||
877 878 879 880 881 882 883 |
fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
{TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
###############################################################################
| | | | | | | | | | 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 |
fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
{TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}
###############################################################################
#fossil test-th-eval "artifact tip"
#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
test_in_checkout th1-artifact-3 {
fossil test-th-eval --open-config "artifact tip"
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}
###############################################################################
#fossil test-th-eval "artifact 0000000000"
#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}
###############################################################################
#fossil test-th-eval "artifact tip test/th1.test"
#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
test_in_checkout th1-artifact-7 {
fossil test-th-eval --open-config "artifact tip test/th1.test"
} {[regexp -- {th1-artifact-7} $RESULT]}
###############################################################################
#fossil test-th-eval "artifact 0000000000 test/th1.test"
#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
###############################################################################
fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}
###############################################################################
|
| ︙ | ︙ | |||
945 946 947 948 949 950 951 |
test th1-globalState-2 {$RESULT eq \
[fossil test-th-eval --open-config checkout]}
}
}
###############################################################################
| | | | 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 |
test th1-globalState-2 {$RESULT eq \
[fossil test-th-eval --open-config checkout]}
}
}
###############################################################################
#fossil test-th-eval "globalState configuration"
#test th1-globalState-3 {[string length $RESULT] == 0}
###############################################################################
fossil test-th-eval --open-config "globalState configuration"
test th1-globalState-4 {[string length $RESULT] > 0}
###############################################################################
|
| ︙ | ︙ | |||
1039 1040 1041 1042 1043 1044 1045 |
###############################################################################
fossil test-th-eval "globalState flags"
test th1-globalState-16 {$RESULT eq "0"}
###############################################################################
| | | | | | | | | | | | | | | | | | | | | < > | 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 |
###############################################################################
fossil test-th-eval "globalState flags"
test th1-globalState-16 {$RESULT eq "0"}
###############################################################################
#fossil test-th-eval "reinitialize; globalState configuration"
#test th1-reinitialize-1 {$RESULT eq ""}
###############################################################################
fossil test-th-eval "reinitialize 1; globalState configuration"
test th1-reinitialize-2 {$RESULT ne ""}
###############################################################################
#
# NOTE: This test will fail if the command names are added to TH1, or
# moved from Tcl builds to plain or the reverse. Sorting the
# command lists eliminates a dependence on order.
#
#fossil test-th-eval "info commands"
#set sorted_result [lsort $RESULT]
#protOut "Sorted: $sorted_result"
#set base_commands {anoncap anycap array artifact break breakpoint \
# builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
# combobox continue copybtn date decorate defHeader dir enable_htmlify \
# enable_output encode64 error expr for foreach getParameter glob_match \
# globalState hascap hasfeature html htmlize http httpize if info \
# insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
# proc puts query randhex redirect regexp reinitialize rename render \
# repository return searchable set setParameter setting stime string \
# styleFooter styleHeader styleScript submenu tclReady trace unset \
# unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
#if {$th1Tcl} {
# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
#} else {
# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
#}
###############################################################################
fossil test-th-eval "info vars"
if {$th1Hooks} {
test th1-info-vars-1 {[lsort $RESULT] eq \
|
| ︙ | ︙ | |||
1324 1325 1326 1327 1328 1329 1330 |
test th1-string-is-3 {$RESULT eq \
{TH_ERROR: wrong # args: should be "string is class string"}}
###############################################################################
fossil test-th-eval {string is other 123}
test th1-string-is-4 {$RESULT eq \
| | | 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 |
test th1-string-is-3 {$RESULT eq \
{TH_ERROR: wrong # args: should be "string is class string"}}
###############################################################################
fossil test-th-eval {string is other 123}
test th1-string-is-4 {$RESULT eq \
"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}
###############################################################################
fossil test-th-eval {string is alnum 123}
test th1-string-is-5 {$RESULT eq "1"}
###############################################################################
|
| ︙ | ︙ |
Added tools/fake-smtpd.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#!/usr/bin/tclsh
#
# This script is a testing aid for working on the Relay notification method
# in Fossil.
#
# This script listens for connections on port 25 or probably some other TCP
# port specified by the "--port N" option. It pretend to be an SMTP server,
# though it does not actually relay any email. Instead, it just prints the
# SMTP conversation on stdout.
#
# If the "--max N" option is used, then the fake SMTP server shuts down
# with an error after receiving N messages from the client. This can be
# used to test retry capabilities in the client.
#
# Suggested Test Procedure For Fossil Relay Notifications
#
# 1. Bring up "fossil ui"
# 2. Configure notification for relay to localhost:8025
# 3. Start this script in a separate window. Something like:
# tclsh fake-smtpd.tcl -port 8025 -max 100
# 4. Send test messages using Fossil
#
proc conn_puts {chan txt} {
puts "S: $txt"
puts $chan $txt
flush $chan
}
set mxMsg 100000000
proc connection {chan ip port} {
global mxMsg
set nMsg 0
puts "*** begin connection from $ip:$port ***"
conn_puts $chan "220 localhost fake-SMTPD"
set inData 0
while {1} {
set line [string trimright [gets $chan]]
if {$line eq ""} {
if {[eof $chan]} break
}
puts "C: $line"
incr nMsg
if {$inData} {
if {$line eq "."} {
set inData 0
conn_puts $chan "250 Ok"
}
} elseif {$nMsg>$mxMsg} {
conn_puts $chan "999 I'm done!"
break
} elseif {[string match "HELO *" $line]} {
conn_puts $chan "250 Ok"
} elseif {[string match "EHLO *" $line]} {
conn_puts $chan "250-SIZE 100000"
conn_puts $chan "250 HELP"
} elseif {[string match "DATA*" $line]} {
conn_puts $chan "354 End data with <CR><LF>.<CR><LF>"
set inData 1
} elseif {[string match "QUIT*" $line]} {
conn_puts $chan "221 Bye"
break
} else {
conn_puts $chan "250 Ok"
}
}
puts "*** connection closed ($nMsg messages) ***"
close $chan
}
set port 25
set argc [llength $argv]
for {set i 0} {$i<$argc-1} {incr i} {
set arg [lindex $argv $i]
if {$arg eq "-port" || $arg eq "--port"} {
incr i
set port [lindex $argv $i]
}
if {$arg eq "-max" || $arg eq "--max"} {
incr i
set mxMsg [lindex $argv $i]
}
}
puts "listening on localhost:$port"
socket -server connection $port
set forever 0
vwait forever
|
Added tools/find-fossil-cgis.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
#!/usr/bin/tclsh
#
# This script scans a directory hierarchy looking for Fossil CGI files -
# the files that are used to launch Fossil as a CGI program. For each
# such file found, in prints the name of the file and also the file
# content, indented, if the --print option is used.
#
# tclsh find-fossil-cgis.tcl [OPTIONS] DIRECTORY
#
# The argument is the directory from which to begin the search.
#
# OPTIONS can be zero or more of the following:
#
# --has REGEXP Only show the CGI if the body matches REGEXP.
# May be repeated multiple times, in which case
# all must match.
#
# --hasnot REGEXP Only show the CGI if it does NOT match the
# REGEXP.
#
# --print Show the content of the CGI, indented by
# three spaces
#
# --symlink Process DIRECTORY arguments that are symlinks.
# Normally symlinks are silently ignored.
#
# -v Show progress information for debugging
#
# EXAMPLE USE CASES:
#
# Find all CGIs that do not have the "errorlog:" property set
#
# find-fossil-cgis.tcl *.website --has '\nrepository:' \
# --hasnot '\nerrorlog:'
#
# Add the errorlog: property to any CGI that does not have it:
#
# find-fossil-cgis.tcl *.website --has '\nrepository:' \
# --hasnot '\nerrorlog:' | while read x
# do
# echo 'errorlog: /logs/errors.txt' >>$x
# done
#
# Find and print all CGIs that do redirects
#
# find-fossil-cgis.tcl *.website --has '\nredirect:' --print
#
# Find the CGIs in directory $dir. Invoke recursively to
# scan subdirectories.
#
proc find_in_one_dir {dir} {
global HAS HASNOT PRINT V
if {$V>0} {
puts "# $dir"
}
foreach obj [lsort [glob -nocomplain -directory $dir *]] {
if {[file isdir $obj]} {
find_in_one_dir $obj
continue
}
if {![file isfile $obj]} continue
if {[file size $obj]>5000} continue
if {![file exec $obj]} continue
if {![file readable $obj]} continue
set fd [open $obj rb]
set txt [read $fd]
close $fd
if {![string match #!* $txt]} continue
if {![regexp {fossil} $txt]} continue
if {![regexp {\nrepository: } $txt] &&
![regexp {\ndirectory: } $txt] &&
![regexp {\nredirect: } $txt]} continue
set ok 1
foreach re $HAS {
if {![regexp $re $txt]} {set ok 0; break;}
}
if {!$ok} continue
foreach re $HASNOT {
if {[regexp $re $txt]} {set ok 0; break;}
}
if {!$ok} continue
#
# At this point assume we have found a CGI file.
#
puts $obj
if {$PRINT} {
regsub -all {\n} [string trim $txt] "\n " out
puts " $out"
}
}
}
set HAS [list]
set HASNOT [list]
set PRINT 0
set SYMLINK 0
set V 0
set N [llength $argv]
set DIRLIST [list]
# First pass: Gather all the command-line arguments but do no
# processing.
#
for {set i 0} {$i<$N} {incr i} {
set dir [lindex $argv $i]
if {($dir eq "-has" || $dir eq "--has") && $i<[expr {$N-1}]} {
incr i
lappend HAS [lindex $argv $i]
continue
}
if {($dir eq "-hasnot" || $dir eq "--hasnot") && $i<[expr {$N-1}]} {
incr i
lappend HASNOT [lindex $argv $i]
continue
}
if {$dir eq "-print" || $dir eq "--print"} {
set PRINT 1
continue
}
if {$dir eq "-symlink" || $dir eq "--symlink"} {
set SYMLINK 1
continue
}
if {$dir eq "-v"} {
set V 1
continue
}
if {[file type $dir]=="directory"} {
lappend DIRLIST $dir
}
}
# Second pass: Process the non-option arguments.
#
foreach dir $DIRLIST {
set type [file type $dir]
if {$type eq "directory" || ($SYMLINK && $type eq "link")} {
find_in_one_dir $dir
}
}
|
Changes to tools/fossil-stress.tcl.
| ︙ | ︙ | |||
91 92 93 94 95 96 97 | /vdiff?from=2015-01-01&to=trunk&diff=0 /wcontent /fileage /dir /tree /uvlist /stat | | | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /vdiff?from=2015-01-01&to=trunk&diff=0 /wcontent /fileage /dir /tree /uvlist /stat /test-env /sitemap /hash-collisions /artifact_stats /bloblist /bigbloblist /wiki_rules /md_rules |
| ︙ | ︙ |
Changes to www/aboutcgi.wiki.
| ︙ | ︙ | |||
65 66 67 68 69 70 71 |
In this example: "timeline/four".
<tr><td>QUERY_STRING
<td>The query string that follows the "?" in the URL, if there is one.
</table>
There are other CGI environment variables beyond those listed above.
Many Fossil servers implement the
| | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
In this example: "timeline/four".
<tr><td>QUERY_STRING
<td>The query string that follows the "?" in the URL, if there is one.
</table>
There are other CGI environment variables beyond those listed above.
Many Fossil servers implement the
[https://fossil-scm.org/home/test-env/two/three?abc=xyz|test-env]
webpage that shows some of the CGI environment
variables that Fossil pays attention to.
In addition to setting various CGI environment variables, if the HTTP
request contains POST content, then the web server relays the POST content
to standard input of the CGI script.
|
| ︙ | ︙ |
Changes to www/cgi.wiki.
| ︙ | ︙ | |||
77 78 79 80 81 82 83 84 85 86 87 88 89 90 | [/help?cmd=repolist-skin|repolist-skin] setting. If no repository has such a non-zero repolist-skin setting, then the repository list is generic HTML without any decoration, with the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> environment variable. The variable can be defined in the CGI control file using the [#setenv|<tt>setenv:</tt>] statement. The repolist-generated page recurses into subdirectories and will list all <tt>*.fossil</tt> files found, with the following exceptions: * Filenames starting with a period are treated as "hidden" and skipped. * Subdirectory names which match the base name of a fossil file in | > > > > > > | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | [/help?cmd=repolist-skin|repolist-skin] setting. If no repository has such a non-zero repolist-skin setting, then the repository list is generic HTML without any decoration, with the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> environment variable. The variable can be defined in the CGI control file using the [#setenv|<tt>setenv:</tt>] statement. The "Project Description" and "Login-Group" columns on the repolist page are optional. They are hidden by default. Show them by putting value "1" in the global settings "show-repolist-desc" and "show-repolist-lg", or by setting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to a string that contains substrings "description" and/or "login-group". The repolist-generated page recurses into subdirectories and will list all <tt>*.fossil</tt> files found, with the following exceptions: * Filenames starting with a period are treated as "hidden" and skipped. * Subdirectory names which match the base name of a fossil file in |
| ︙ | ︙ |
Changes to www/changes.wiki.
1 2 | <title>Change Log</title> | | < | | | | | > > > | | > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<title>Change Log</title>
<h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>
<li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
<ol type="a">
<li> The --from can optionally accept a directory name as its argument,
and uses files under that directory as the baseline for the diff.
<li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
are available, or a --by diff if not.
<li> The "Reload" button is added to --tk diffs, to bring the displayed
diff up to date with the latest changes on disk.
<li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
diffs of multiple files.
</ol>
<li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
about pending changes in a working check-out
<li>Enhancements to the [/help?cmd=ui|fossil ui] command:
<ol type="a">
<li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
start page. Or, if the new "--from PATH" option is present, the
default start page becomes "/ckout?exbase=PATH".
<li> The new "--extpage FILENAME" option opens the named file as if it
where in a [./serverext.wiki|CGI extension]. Example usage: the
person editing this change log has
"fossil ui --extpage www/changes.wiki" running and hence can
press "Reload" on the web browser to view edits.
<li> Accept both IPv4 and IPv6 connections on all platforms, including
Windows and OpenBSD. This also applies to the "fossil server"
command.
</ol>
<li>Enhancements to [/help?cmd=merge|fossil merge]:
<ol type="a">
<li> Added the [/help?cmd=merge-info|fossil merge-info] command and
especially the --tk option to that command, to provide analysis
of the most recent merge or update operation.
<li> When a merge conflict occurs, a new section is added to the conflict
text that shows Fossil's suggested resolution to the conflict.
</ol>
<li>Enhancements to [/help?cmd=commit|fossil commit]:
<ol type="a">
<li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
in the check-in comment, it will alert the developer and give
him or her the opportunity to edit the comment before continuing.
This feature is controllable by the
[/help?cmd=verify-comments|verify-comments setting].
<li> The new "--if-changes" option causes the commit to become
a quiet no-op if there are no pending changes.
<li> Added the ability to sign check-ins with SSH keys.
<li> Issue a warning if a user tries to commit on a check-in where the
branch has been changed.
<li> The interactive checkin comment prompt shows the formatting rules
set for that repository.
<li> Add the "--editor" option.
</ol>
<li>Deprecate the --comfmtflags and --comment-format global options and
no longer list them in the built-in help, but keep them working for
backwards compatibility.
Alternative TTY comment formatting can still be specified using the
[/help?cmd=comment-format|comment-format setting], if desired. The
default comment format is now called "canonical", not "legacy".
<li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
<ol type="a">
<li> Added the "ml=" ("Merge-in List") query parameter that works
like "rl=" ("Related List") but adds "mionly" style related
check-ins instead of the full "rel" style.
<li> For "tl=", "rl=", and "ml=", the order of the branches in the
graph now tries to match the order of the branches named in
the list.
|
| ︙ | ︙ | |||
82 83 84 85 86 87 88 89 |
"ymd" and "yw" query parameters.
<li> The new "min" query parameter, when added to a from=,to= query,
collapses long runs of check-ins on the same branch into just
end-points.
<li> The p= and d= parameters an reference different check-ins, which
case the timeline shows those check-ins that are both ancestors
of p= and descendants of d=.
</ol>
| > > > > > > > | | | | > > | | > > > > > > > > | | | > > > > > > > > > > > | | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
"ymd" and "yw" query parameters.
<li> The new "min" query parameter, when added to a from=,to= query,
collapses long runs of check-ins on the same branch into just
end-points.
<li> The p= and d= parameters an reference different check-ins, which
case the timeline shows those check-ins that are both ancestors
of p= and descendants of d=.
<li> The saturation and intensity of user-specified checkin and branch
background colors are automatically adjusted to keep the colors
compatible with the current skin, unless the
[/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
</ol>
<li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like
/doc but keeps the title of markdown documents with the document rather
that moving it up to the page title.
<li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
and debugging
<li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
decoding of the artifact described by NAME.
<li>Improvements to the [/help?cmd=patch|fossil patch] command:
<ol type="a">
<li> Fix a bug in "fossil patch create" that causes
[/help?cmd=revert|fossil revert] operations that happened
on individualfiles after a [/help?cmd=merge|fossil merge]
to be omitted from the patch.
<li> Added the [/help?cmd=patch|patch alias] command for managing
aliases for remote checkout names.
</ol>
<li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
<ol type="a">
<li> Add the ability to search the help text, either in the UI
(on the [/help?cmd=/search|/search page]) or from the command-line
(using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
<li> Accepts an optional SUBCOMMAND argument following the
COMMAND argument and only shows results for the specified
subcommand, not the entire command.
<li> The -u (--usage) option shows only the command-line syntax
<li> The -o (--options) option shows only the command-line options
</ol>
<li>Enhancements to the ticket system:
<ol type="a">
<li> Added the ability to attach wiki pages to a ticket for extended
descriptions.
<li> Added submenu to the 'View Ticket' page, to use it as
template for a new ticket.
<li> Added button 'Submit and New' to create multiple tickets
in a row.
<li> Link the version field in ticket view to a matching checkin or tag.
<li> Show creation time in report and ticket view.
<li> Show previous comments in edit ticket as reference.
</ol>
<li>Added the "hash" query parameter to the
[/help?cmd=/whatis|/whatis webpage].
<li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
which alerts subscribers when an admin creates a new user or
when a user's permissions change.
<li>Show project description on repository list.
<li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
the frequency of reconnection attempts over time and providing feedback
to the user when the connection is down.
<li>The [/doc/trunk/www/th1.md|TH1 script language] now makes a distinction
between [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
This is a security enhancement that makes it more difficult to write
custom TH1 scripts that contain XSS or SQL-injection bugs. As part of
this enhancement, the [/help?cmd=vuln-report|vuln-report] setting was
added to control what Fossil does when it encounters a potential TH1
security problem.
<li>Diverse minor fixes and additions.
</ol>
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
that have non-ASCII filenames
* Add the [/help?cmd=tree|fossil tree] command.
* On case-insensitive filesystems, store files using the filesystem's
|
| ︙ | ︙ |
Changes to www/chat.md.
| ︙ | ︙ | |||
162 163 164 165 166 167 168 169 170 171 172 173 174 175 | in a new timeline entry is announced in the chatroom. The announcement appears to come from a user whose name is given by the chat-timeline-user setting. This mechanism is similar to [email notification](./alerts.md) except that the notification is sent via chat instead of via email. ## Implementation Details *You do not need to understand how Fossil chat works in order to use it. But many developers prefer to know how their tools work. This section is provided for the benefit of those curious developers.* | > > > > > > > > > > > > > > > > > > | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
in a new timeline entry is announced in the chatroom. The announcement
appears to come from a user whose name is given by the chat-timeline-user
setting.
This mechanism is similar to [email notification](./alerts.md) except that
the notification is sent via chat instead of via email.
## Quirks
- There is no message-editing capability. This is by design and
desire of `/chat`'s developers.
- When `/chat` has problems connecting to the message poller (see
the next section) it will provide a subtle visual indicator of the
connection problem - a dotted line along the top of the input
field. If the connection recovers within a short time, that
indicator will go away, otherwise it will pop up a loud message
signifying that the connection to the poller is down and how long
it will wait to retry (progressively longer, up to some
maximum). `/chat` will recover automatically when the server is
reachable. Trying to send messages while the poller connection is
down is permitted, and the poller will attempt to recover
immediately if sending of a message succeeds. That applies to any
operations which send traffic, e.g. if the per-message "toggle
text mode" button is activated or a message is globally deleted.
## Implementation Details
*You do not need to understand how Fossil chat works in order to use it.
But many developers prefer to know how their tools work.
This section is provided for the benefit of those curious developers.*
|
| ︙ | ︙ |
Changes to www/env-opts.md.
| ︙ | ︙ | |||
152 153 154 155 156 157 158 | for additional information. `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page loaded by the `fossil all ui` or `fossil ui /` commands. Only used if none of the listed repositories has the `repolist_skin` property set. Can be set from the [CGI control file][cgictlfile]. | | | | > | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | for additional information. `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page loaded by the `fossil all ui` or `fossil ui /` commands. Only used if none of the listed repositories has the `repolist_skin` property set. Can be set from the [CGI control file][cgictlfile]. `FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value that contains the substring "description", then the "Project Description" column appears on the repolist page. If it contains the substring "login-group", then the Login-Group column appears on the repolist page. Can be set from the [CGI control file][cgictlfile]. `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for SEE as text to be hashed into the actual encryption key. This has no effect if Fossil was not compiled with SEE support enabled. |
| ︙ | ︙ |
Changes to www/loadmgmt.md.
| ︙ | ︙ | |||
75 76 77 78 79 80 81 |
chroot_jail_proc /home/www/proc proc ro 0 0
The `/home/www/proc` pathname should be adjusted so that the `/proc`
component is at the root of the chroot jail, of course.
To see if the load-average limiter is functional, visit the
| | | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
chroot_jail_proc /home/www/proc proc ro 0 0
The `/home/www/proc` pathname should be adjusted so that the `/proc`
component is at the root of the chroot jail, of course.
To see if the load-average limiter is functional, visit the
[`/test-env`][hte] page of the server to view the current load average.
If the value for the load average is greater than zero, that means that
it is possible to activate the load-average limiter on that repository.
If the load average shows exactly "0.0", then that means that Fossil is
unable to find the load average. This can either be because it is in a
`chroot(2)` jail without `/proc` access, or because it is running on a
system that does not support `getloadavg()` and so the load-average
limiter will not function.
[503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4
[hte]: /help?cmd=/test-env
[gla]: https://linux.die.net/man/3/getloadavg
[lin]: http://www.linode.com
[sh]: ./selfhost.wiki
|
Changes to www/quickstart.wiki.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | or <a href="build.wiki">compile it yourself</a> from sources. Install Fossil by putting the fossil binary someplace on your $PATH. You can test that Fossil is present and working like this: <pre><b>fossil version | | | < < | | | < | | > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
or <a href="build.wiki">compile it yourself</a> from sources.
Install Fossil by putting the fossil binary
someplace on your $PATH.
You can test that Fossil is present and working like this:
<pre><b>fossil version
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
</b></pre>
<h2 id="workflow" name="fslclone">General Work Flow</h2>
Fossil works with [./glossary.md#repository | repository files]
and [./glossary.md#check-out | check-out directories] using a
workflow like this:
<ul>
<li>Create or clone a repository file. ([/help/init|fossil init] or
[/help/clone | fossil clone])
<li>Check out a local tree. ([/help/open | fossil open])
<li>Perform operations on the repository (including repository
configuration).
</ul>
Fossil can be entirely driven from the command line. Many features
can also be conveniently accessed from the built-in web user interface.
The following sections give a brief overview of these
operations.
<h2 id="new">Starting A New Project</h2>
To start a new project with Fossil, [/help/init | create a new empty repository]:
<pre><b>fossil init</b> <i>repository-filename</i>
</pre>
You can name the database anything you like, and you can place it anywhere in the filesystem.
The <tt>.fossil</tt> extension is traditional, but it is only required if you are going to use the
<tt>[/help/server | fossil server DIRECTORY]</tt> feature.
Next, do something along the lines of:
<pre>
<b>mkdir -p ~/src/project/trunk</b>
<b>cd ~/src/project/trunk</b>
<b>fossil open</b> <i>repository-filename</i>
<b>fossil add</b> foo.c bar.h qux.md
<b>fossil commit</b>
</pre>
If your project directory already exists, obviating the <b>mkdir</b>
step, you will instead need to add the <tt>--force</tt> flag to the
<b>open</b> command to authorize Fossil to open the repo into a
non-empty checkout directory. (This is to avoid accidental opens into,
for example, your home directory.)
The convention of naming your checkout directory after a long-lived
branch name like "trunk" is in support of Fossil's ability to have as
many open checkouts as you like. This author frequently has additional
checkout directories named <tt>../release</tt>, <tt>../scratch</tt>,
etc. The release directory is open to the branch of the same name, while
the scratch directory is used when disturbing one of the other
long-lived checkout directories is undesireable, as when performing a
[/help/bisect | bisect] operation.
<h2 id="clone">Cloning An Existing Repository</h2>
Most fossil operations interact with a repository that is on the
local disk drive, not on a remote system. Hence, before accessing
a remote repository it is necessary to make a local copy of that
repository, a process called
"[/help/clone | cloning]".
This is done as follows:
<pre><b>fossil clone</b> <i>URL repository-filename</i>
</pre>
The <i>URL</i> specifies the fossil repository
you want to clone. The <i>repository-filename</i> is the new local
filename into which the cloned repository will be written. For
|
| ︙ | ︙ | |||
79 80 81 82 83 84 85 | Clone done, sent: 2424 received: 42965725 ip: 10.10.10.0 Rebuilding repository meta-data... 100% complete... Extra delta compression... Vacuuming the database... project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 | | > > > > > > > > | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | Clone done, sent: 2424 received: 42965725 ip: 10.10.10.0 Rebuilding repository meta-data... 100% complete... Extra delta compression... Vacuuming the database... project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")> </b></pre> This <i>exampleuser</i> will be used by Fossil as the author of commits when you checkin changes to the repository. It is also used by Fossil when you make your repository available to others using the built-in server mode by running <tt>[/help/server | fossil server]</tt> and will also be used when running <tt>[/help/ui | fossil ui]</tt> to view the repository through the Fossil UI. See the quick start topic for setting up a <a href="#server">server</a> for more details. If the remote repository requires a login, include a userid in the URL like this: <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre> You will be prompted separately for the password. |
| ︙ | ︙ | |||
125 126 127 128 129 130 131 | [https://www.mercurial-scm.org/|Mercurial]. Fossil can also import [https://subversion.apache.org/|Subversion projects] directly. <h2 id="checkout">Checking Out A Local Tree</h2> To work on a project in fossil, you need to check out a local copy of the source tree. Create the directory you want to be | | < | < < | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
[https://www.mercurial-scm.org/|Mercurial].
Fossil can also import [https://subversion.apache.org/|Subversion projects] directly.
<h2 id="checkout">Checking Out A Local Tree</h2>
To work on a project in fossil, you need to check out a local
copy of the source tree. Create the directory you want to be
the root of your tree, <tt>cd</tt> into that directory, and then:
<pre><b>fossil open</b> <i>repository-filename</i></pre>
For example:
<pre><b>fossil open ../myclone.fossil
BUILD.txt
COPYRIGHT-BSD2.txt
README.md
︙
</tt></b></pre>
This leaves you with the newest version of the tree
checked out.
From anywhere underneath the root of your local tree, you
can type commands like the following to find out the status of
your local tree:
<pre>
|
| ︙ | ︙ | |||
292 293 294 295 296 297 298 | the first checkin, and the default for any time a branch name is needed but not specified. This will get you started on identifying checkins. The <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including how timestamps can also be used. | | | < | < < > | > | < | | | > > > > | > > > > > > | > > > > > > > > | | | > > > | 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | the first checkin, and the default for any time a branch name is needed but not specified. This will get you started on identifying checkins. The <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including how timestamps can also be used. <h2 id="config">Accessing Your Local Repository's Web User Interface</h2> After you create a new repository, you usually want to do some local configuration. This is most easily accomplished by firing up the Fossil UI: <pre> <b>fossil ui</b> <i>repository-filename</i> </pre> You can shorten that to just [/help/ui | <b>fossil ui</b>] if you are inside a checked-out local tree. This command starts an internal web server, after which Fossil automatically launches your default browser, pointed at itself, presenting a special view of the repository, its web user interface. You may override Fossil's logic for selecting the default browser so: <pre> <b>fossil setting web-browser</b> <i>path-to-web-browser</i> </pre> When launched this way, Fossil binds its internal web server to the IP loopback address, 127.0.0.1, which it treats specially, bypassing all user controls, effectively giving visitors the [./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity]. Why is that a good idea, you ask? Because it is a safe presumption that only someone with direct file access to the repository database file could be using the resulting web interface. Anyone who can modify the repo DB directly could give themselves any and all access with a SQL query, or even by direct file manipulation; no amount of access control matters to such a user. (Contrast the [#server | many <i>other</i> ways] of setting Fossil up as an HTTP server, where the repo DB is on the other side of the HTTP server wall, inaccessible by all means other than Fossil's own mediation. For this reason, the "localhost bypasses access control" policy does <i>not</i> apply to these other interfaces. That is a very good thing, since without this difference in policy, it would be unsafe to bind a [/help?cmd=server | <b>fossil server</b>] instance to localhost on a high-numbered port and then reverse-proxy it out to the world via HTTPS, a practice this author does engage in, with confidence.) Once you are finished configuring Fossil, you may safely Control-C out of the <b>fossil ui</b> command to shut down this privileged built-in web server. Moreover, you may by grace of SQLite do this <i>at any time</i>: all changes are either committed durably to the repo DB or rolled back, in their totality. This includes configuration changes. <h2 id="sharing">Sharing Changes</h2> When [./concepts.wiki#workflow|autosync] is turned off, the changes you [/help/commit | commit] are only on your local repository. To share those changes with other repositories, do: |
| ︙ | ︙ | |||
382 383 384 385 386 387 388 | Is similar to update except that it does not honor the autosync setting, nor does it merge in local changes - it prefers to overwrite them and fails if local changes exist unless the <tt>--force</tt> flag is used. <h2 id="branch" name="merge">Branching And Merging</h2> | | | < < < < | 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 | Is similar to update except that it does not honor the autosync setting, nor does it merge in local changes - it prefers to overwrite them and fails if local changes exist unless the <tt>--force</tt> flag is used. <h2 id="branch" name="merge">Branching And Merging</h2> Use the --branch option to the [/help/commit | commit] command to start a new branch at the point of need. ([./gitusers.md#bneed | Contrast git].) To merge two branches back together, first [/help/update | update] to the branch you want to merge into. Then do a [/help/merge|merge] of the other branch that you want to incorporate the changes from. For example, to merge "featureX" changes into "trunk" do this: |
| ︙ | ︙ | |||
440 441 442 443 444 445 446 | mistake. Undo and redo only work for changes that have not yet been checked in using commit and there is only a single level of undo/redo. <h2 id="server">Setting Up A Server</h2> | > | < < > | | < < < | < < | > > | < < | < < < < | | | < > | | > < < | | < | | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
mistake. Undo and redo only work for changes that have
not yet been checked in using commit and there is only a single
level of undo/redo.
<h2 id="server">Setting Up A Server</h2>
In addition to the inward-facing <b>fossil ui</b> mode covered [#config
| above], Fossil can also act as an outward-facing web server:
<pre>
<b>[/help/server | fossil server]</b> <i>repository-filename</i>
</pre>
Just as with <b>fossil ui</b>, you may omit the
<i>repository-filename</i> parameter when running this from within an open
check-out.
<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
interfaces by default in this mode, and it enforces the configured
[./caps/ | role-based access controls]. Further, because it is meant to
provide external web service, it doesn't try to launch a local web
browser pointing to a "Fossil UI" presentation; external visitors see
your repository's configured home page instead.
To serve varying needs, there are additional ways to serve a Fossil repo
to external users:
<ul>
<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
self-hosting repositories]
<li>[./server/any/scgi.md|SCGI]
<li>[./server/any/inetd.md|inetd]
<li>[./server/debian/service.md|systemd]
</ul>
…along with [./server/#matrix | several other options].
We recommend that you read the [./server/whyuseaserver.wiki | Benefits
of a Fossil Server] article, because you might <i>need</i> to do this
and not yet know it.
<h2 id="proxy">HTTP Proxies</h2>
If you are behind a restrictive firewall that requires you to use
an HTTP proxy to reach the internet, then you can configure the proxy
in three different ways. You can tell fossil about your proxy using
a command-line option on commands that use the network,
|
| ︙ | ︙ |
Changes to www/server/debian/service.md.
| ︙ | ︙ | |||
50 51 52 53 54 55 56 | restrictions on TCP ports in every OS where `systemd` runs, but you can create a listener socket on a high-numbered (≥ 1024) TCP port, suitable for sharing a Fossil repo to a workgroup on a private LAN. To do this, write the following in `~/.local/share/systemd/user/fossil.service`: | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | restrictions on TCP ports in every OS where `systemd` runs, but you can create a listener socket on a high-numbered (≥ 1024) TCP port, suitable for sharing a Fossil repo to a workgroup on a private LAN. To do this, write the following in `~/.local/share/systemd/user/fossil.service`: > ```dosini [Unit] Description=Fossil user server After=network-online.target [Service] WorkingDirectory=/home/fossil/museum ExecStart=/home/fossil/bin/fossil server --port 9000 repo.fossil |
| ︙ | ︙ | |||
162 163 164 165 166 167 168 | socket listener, which `systemd` calls “[socket activation][sa],” roughly equivalent to [the ancient `inetd` method](../any/inetd.md). It’s more complicated, but it has some nice properties. We first need to define the privileged socket listener by writing `/etc/systemd/system/fossil.socket`: | | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | socket listener, which `systemd` calls “[socket activation][sa],” roughly equivalent to [the ancient `inetd` method](../any/inetd.md). It’s more complicated, but it has some nice properties. We first need to define the privileged socket listener by writing `/etc/systemd/system/fossil.socket`: > ```dosini [Unit] Description=Fossil socket [Socket] Accept=yes ListenStream=80 NoDelay=true |
| ︙ | ︙ | |||
187 188 189 190 191 192 193 | This configuration says more or less the same thing as the socket part of an `inetd` entry [exemplified elsewhere in this documentation](../any/inetd.md). Next, create the service definition file in that same directory as `fossil@.service`: | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | This configuration says more or less the same thing as the socket part of an `inetd` entry [exemplified elsewhere in this documentation](../any/inetd.md). Next, create the service definition file in that same directory as `fossil@.service`: > ```dosini [Unit] Description=Fossil socket server After=network-online.target [Service] WorkingDirectory=/home/fossil/museum ExecStart=/home/fossil/bin/fossil http repo.fossil |
| ︙ | ︙ |
Changes to www/server/windows/service.md.
| ︙ | ︙ | |||
53 54 55 56 57 58 59 | When the Fossil server will be used at times that files may be locked during virus scanning, it is prudent to arrange that its directory used for temporary files is exempted from such scanning. Ordinarily, this will be a subdirectory named "fossil" in the temporary directory given by the Windows GetTempPath(...) API, [namely](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks) the value of the first existing environment variable from `%TMP%`, `%TEMP%`, `%USERPROFILE%`, and `%SystemRoot%`; you can look for their actual values in | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | When the Fossil server will be used at times that files may be locked during virus scanning, it is prudent to arrange that its directory used for temporary files is exempted from such scanning. Ordinarily, this will be a subdirectory named "fossil" in the temporary directory given by the Windows GetTempPath(...) API, [namely](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks) the value of the first existing environment variable from `%TMP%`, `%TEMP%`, `%USERPROFILE%`, and `%SystemRoot%`; you can look for their actual values in your system by accessing the `/test-env` webpage. Excluding this subdirectory will avoid certain rare failures where the fossil.exe process is unable to use the directory normally during a scan. ### <a id='PowerShell'></a>Advanced service installation using PowerShell As great as `fossil winsrv` is, it does not have one to one reflection of all of the `fossil server` [options](/help?cmd=server). When you need to use some of |
| ︙ | ︙ |
Changes to www/serverext.wiki.
| ︙ | ︙ | |||
187 188 189 190 191 192 193 | Do a web search for "[https://duckduckgo.com/?q=cgi+environment_variables|cgi environment variables]" to find more detail about what each of the above variables mean and how they are used. Live listings of the values of some or all of these environment variables can be found at links like these: | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | Do a web search for "[https://duckduckgo.com/?q=cgi+environment_variables|cgi environment variables]" to find more detail about what each of the above variables mean and how they are used. Live listings of the values of some or all of these environment variables can be found at links like these: * [https://fossil-scm.org/home/test-env] * [https://sqlite.org/src/ext/checklist/top/env] In addition to the standard CGI environment variables listed above, Fossil adds the following: * FOSSIL_CAPABILITIES * FOSSIL_NONCE |
| ︙ | ︙ |
Changes to www/th1.md.
| ︙ | ︙ | |||
66 67 68 69 70 71 72 |
of the command, is `if`.
The second token is `$current eq "dev"` - an expression. (The outer {...}
are removed from each token by the command parser.) The third token
is the `puts "hello"`, with its whitespace and newlines. The fourth token
is `else` and the fifth and last token is `puts "world"`.
The `if` command evaluates its first argument (the second token)
| | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
of the command, is `if`.
The second token is `$current eq "dev"` - an expression. (The outer {...}
are removed from each token by the command parser.) The third token
is the `puts "hello"`, with its whitespace and newlines. The fourth token
is `else` and the fifth and last token is `puts "world"`.
The `if` command evaluates its first argument (the second token)
as an expression, and if that expression is true, it evaluates its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is `else`, then
the fourth argument is evaluated as a TH1 expression.
So, you see, even though the example above spans five lines, it is really
just a single command.
|
| ︙ | ︙ | |||
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
return [lindex [regexp -line -inline -nocase -- \
{^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
$repository "" info trunk]]] end]
Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.
Summary of Core TH1 Commands
----------------------------
The original Tcl language (after which TH1 is modeled) has a very rich
repertoire of commands. TH1, as it is designed to be minimalist and
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
return [lindex [regexp -line -inline -nocase -- \
{^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
$repository "" info trunk]]] end]
Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.
<a id="taint"></a>Tainted And Untainted Strings
-----------------------------------------------
Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
"tainted" and "untainted" strings. Tainted strings are strings that are
derived from user inputs that might contain text that is designed to subvert
the script. Untainted strings are known to come from secure sources and
are assumed to contain no malicious content.
Beginning with Fossil version 2.26, and depending on the value of the
[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
strings from being used in ways that might lead to XSS or SQL-injection
attacks. This feature helps to ensure that XSS and SQL-injection
vulnerabilities are not *accidentally* added to Fossil when
custom TH1 scripts for headers or footers or tickets are added to a
repository. Note that the tainted/untainted distinction in strings does
not make it impossible to introduce XSS and SQL-injections vulnerabilities
using poorly-written TH1 scripts; it just makes it more difficult and
less likely to happen by accident. Developers must still consider the
security implications TH1 customizations they add to Fossil, and take
appropriate precautions when writing custom TH1. Peer review of TH1
script changes is encouraged.
In Fossil version 2.26, if the vuln-report setting is set to "block"
or "fatal", the [html](#html) and [query](#query) TH1 commands will
fail with an error if their argument is a tainted string. This helps
to prevent XSS and SQL-injection attacks, respectively. Note that
the default value of the vuln-report setting is "log", which allows those
commands to continue working and only writes a warning message into the
error log. <b>Future versions of Fossil may change the default value
of the vuln-report setting to "block" or "fatal".</b> Fossil users
with customized TH1 scripts are encouraged to audit their customizations
and fix any potential vulnerabilities soon, so as to avoid breakage
caused by future upgrades. <b>Future versions of Fossil might also
place additional restrictions on the use of tainted strings.</b>
For example, it is likely that future versions of Fossil will disallow
using tainted strings as script, for example as the body of a "for"
loop or of a "proc".
Summary of Core TH1 Commands
----------------------------
The original Tcl language (after which TH1 is modeled) has a very rich
repertoire of commands. TH1, as it is designed to be minimalist and
|
| ︙ | ︙ | |||
145 146 147 148 149 150 151 152 153 154 155 156 157 158 | * string index STRING INDEX * string is CLASS STRING * string last NEEDLE HAYSTACK ?START-INDEX? * string match PATTERN STRING * string length STRING * string range STRING FIRST LAST * string repeat STRING COUNT * unset VARNAME * uplevel ?LEVEL? SCRIPT * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? All of the above commands work as in the original Tcl. Refer to the <a href="https://www.tcl-lang.org/man/tcl/contents.htm">Tcl documentation</a> for details. | > > > | 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | * string index STRING INDEX * string is CLASS STRING * string last NEEDLE HAYSTACK ?START-INDEX? * string match PATTERN STRING * string length STRING * string range STRING FIRST LAST * string repeat STRING COUNT * string trim STRING * string trimleft STRING * string trimright STRING * unset VARNAME * uplevel ?LEVEL? SCRIPT * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? All of the above commands work as in the original Tcl. Refer to the <a href="https://www.tcl-lang.org/man/tcl/contents.htm">Tcl documentation</a> for details. |
| ︙ | ︙ | |||
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | * [setParameter](#setParameter) * [setting](#setting) * [stime](#stime) * [styleHeader](#styleHeader) * [styleFooter](#styleFooter) * [styleScript](#styleScript) * [submenu](#submenu) * [tclEval](#tclEval) * [tclExpr](#tclExpr) * [tclInvoke](#tclInvoke) * [tclIsSafe](#tclIsSafe) * [tclMakeSafe](#tclMakeSafe) * [tclReady](#tclReady) * [trace](#trace) * [unversioned content](#unversioned_content) * [unversioned list](#unversioned_list) * [utime](#utime) * [verifyCsrf](#verifyCsrf) * [verifyLogin](#verifyLogin) * [wiki](#wiki) * [wiki_assoc](#wiki_assoc) | > > | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | * [setParameter](#setParameter) * [setting](#setting) * [stime](#stime) * [styleHeader](#styleHeader) * [styleFooter](#styleFooter) * [styleScript](#styleScript) * [submenu](#submenu) * [taint](#taintCmd) * [tclEval](#tclEval) * [tclExpr](#tclExpr) * [tclInvoke](#tclInvoke) * [tclIsSafe](#tclIsSafe) * [tclMakeSafe](#tclMakeSafe) * [tclReady](#tclReady) * [trace](#trace) * [untaint](#untaintCmd) * [unversioned content](#unversioned_content) * [unversioned list](#unversioned_list) * [utime](#utime) * [verifyCsrf](#verifyCsrf) * [verifyLogin](#verifyLogin) * [wiki](#wiki) * [wiki_assoc](#wiki_assoc) |
| ︙ | ︙ | |||
525 526 527 528 529 530 531 | raise a script error. <a id="html"></a>TH1 html Command ----------------------------------- * html STRING | | > > > > > > > > > > > > > > | 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 | raise a script error. <a id="html"></a>TH1 html Command ----------------------------------- * html STRING Outputs the STRING literally. It is assumed that STRING contains valid HTML, or that if STRING contains any characters that are significant to HTML (such as `<`, `>`, `'`, or `&`) have already been escaped, perhaps by the [htmlize](#htmlize) command. Use the [puts](#puts) command to output text that might contain unescaped HTML markup. **Beware of XSS attacks!** If the STRING value to the html command can be controlled by a hostile user, then he might be able to sneak in malicious HTML or Javascript which could result in a cross-site scripting (XSS) attack. Be careful that all text that in STRING that might come from user input has been sanitized by the [htmlize](#htmlize) command or similar. In recent versions of Fossil, the STRING value must be [untainted](#taint) or else the "html" command will fail. <a id="htmlize"></a>TH1 htmlize Command ----------------------------------------- * htmlize STRING Escape all characters of STRING which have special meaning in HTML. |
| ︙ | ︙ | |||
593 594 595 596 597 598 599 | Returns the value of the cryptographic nonce for the request being processed. <a id="puts"></a>TH1 puts Command ----------------------------------- * puts STRING | | | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
Returns the value of the cryptographic nonce for the request being processed.
<a id="puts"></a>TH1 puts Command
-----------------------------------
* puts STRING
Outputs STRING. Characters within STRING that have special meaning
in HTML are escaped prior to being output. Thus is it safe for STRING
to be derived from user inputs. See also the [html](#html) command
which behaves similarly except does not escape HTML markup. This
command ("puts") is safe to use on [tainted strings](#taint), but the "html"
command is not.
<a id="query"></a>TH1 query Command
-------------------------------------
* query ?-nocomplain? SQL CODE
Runs the SQL query given by the SQL argument. For each row in the result
set, run CODE.
In SQL, parameters such as $var are filled in using the value of variable
"var". Result values are stored in variables with the column name prior
to each invocation of CODE. The names of the variables in which results
are stored can be controlled using "AS name" clauses in the SQL. As
the database will often contain content that originates from untrusted
users, all result values are marked as [tainted](#taint).
**Beware of SQL injections in the `query` command!**
The SQL argument to the query command should always be literal SQL
text enclosed in {...}. The SQL argument should never be a double-quoted
string or the value of a \$variable, as those constructs can lead to
an SQL Injection attack. If you need to include the values of one or
more TH1 variables as part of the SQL, then put \$variable inside the
{...}. The \$variable keyword will then get passed down into the SQLite
parser which knows to look up the value of \$variable in the TH1 symbol
table. For example:
~~~
query {SELECT res FROM tab1 WHERE key=$mykey} {...}
~~~
SQLite will see the \$mykey token in the SQL and will know to resolve it
to the value of the "mykey" TH1 variable, safely and without the possibility
of SQL injection. The following is unsafe:
~~~
query "SELECT res FROM tab1 WHERE key='$mykey'" {...} ;# <-- UNSAFE!
~~~
In this second example, TH1 does the expansion of `$mykey` prior to passing
the text down into SQLite. So if `$mykey` contains a single-quote character,
followed by additional hostile text, that will result in an SQL injection.
To help guard against SQL-injections, recent versions of Fossil require
that the SQL argument be [untainted](#taint) or else the "query" command
will fail.
<a id="randhex"></a>TH1 randhex Command
-----------------------------------------
* randhex N
Returns a string of N*2 random hexadecimal digits with N<50. If N is
|
| ︙ | ︙ | |||
636 637 638 639 640 641 642 643 644 645 646 647 648 649 | --------------------------------------- * regexp ?-nocase? ?--? exp string Checks the string against the specified regular expression and returns non-zero if it matches. If the regular expression is invalid or cannot be compiled, an error will be generated. <a id="reinitialize"></a>TH1 reinitialize Command --------------------------------------------------- * reinitialize ?FLAGS? Reinitializes the TH1 interpreter using the specified flags. | > > | 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 | --------------------------------------- * regexp ?-nocase? ?--? exp string Checks the string against the specified regular expression and returns non-zero if it matches. If the regular expression is invalid or cannot be compiled, an error will be generated. See [fossil grep](./grep.md) for details on the regexp syntax. <a id="reinitialize"></a>TH1 reinitialize Command --------------------------------------------------- * reinitialize ?FLAGS? Reinitializes the TH1 interpreter using the specified flags. |
| ︙ | ︙ | |||
739 740 741 742 743 744 745 746 747 748 749 750 751 752 | <a id="submenu"></a>TH1 submenu Command ----------------------------------------- * submenu link LABEL URL Add hyperlink to the submenu of the current page. <a id="tclEval"></a>TH1 tclEval Command ----------------------------------------- **This command requires the Tcl integration feature.** * tclEval arg ?arg ...? | > > > > > > > > > > > > > > | 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 | <a id="submenu"></a>TH1 submenu Command ----------------------------------------- * submenu link LABEL URL Add hyperlink to the submenu of the current page. <a id="taintCmd"></a>TH1 taint Command ----------------------------------------- * taint STRING This command returns a copy of STRING that has been marked as [tainted](#taint). Tainted strings are strings which might be controlled by an attacker and might contain hostile inputs and are thus unsafe to use in certain contexts. For example, tainted strings should not be output as part of a webpage as they might contain rogue HTML or Javascript that could lead to an XSS vulnerability. Similarly, tainted strings should not be run as SQL since they might contain an SQL-injection vulerability. <a id="tclEval"></a>TH1 tclEval Command ----------------------------------------- **This command requires the Tcl integration feature.** * tclEval arg ?arg ...? |
| ︙ | ︙ | |||
810 811 812 813 814 815 816 817 818 819 820 821 822 823 | <a id="trace"></a>TH1 trace Command ------------------------------------- * trace STRING Generates a TH1 trace message if TH1 tracing is enabled. <a id="unversioned_content"></a>TH1 unversioned content Command ----------------------------------------------------------------- * unversioned content FILENAME Attempts to locate the specified unversioned file and return its contents. An error is generated if the repository is not open or the unversioned file | > > > > > > > > > > > > | 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 | <a id="trace"></a>TH1 trace Command ------------------------------------- * trace STRING Generates a TH1 trace message if TH1 tracing is enabled. <a id="untaintCmd"></a>TH1 taint Command ----------------------------------------- * untaint STRING This command returns a copy of STRING that has been marked as [untainted](#taint). Untainted strings are strings which are believed to be free of potentially hostile content. Use this command with caution, as it overwrites the tainted-string protection mechanisms that are built into TH1. If you do not understand all the implications of executing this command, then do not use it. <a id="unversioned_content"></a>TH1 unversioned content Command ----------------------------------------------------------------- * unversioned content FILENAME Attempts to locate the specified unversioned file and return its contents. An error is generated if the repository is not open or the unversioned file |
| ︙ | ︙ |