Fossil SCM
Merge from trunk.
Commit
fd1a5afe1e98ffbfda574712c44756bd3bf13d3e873a58ac09601c4d4fbb5030
Parent
2bcbc6e397e52cd…
63 files changed
+1
-1
+37
-25
+222
-132
+8
-6
+1
-1
+1
-1
+1
-1
+1
-1
+41
-20
+2
-2
+252
-140
+2
+4
-2
+2
-2
+1
+1
-1
+32
-5
+1
-1
+12
-12
+7
-5
+21
-8
+1
-1
+39
-11
+2
-2
+2
-2
+65
-6
+89
-182
+7
-2
+73
-28
+2
-4
+50
-11
+1
-1
+23
-19
+4
-3
+70
-29
+8
-2
+3
-2
+87
-54
+53
-14
+92
-53
+209
-89
+34
-88
+2
-2
+18
-12
+101
-12
+5
-3
+79
-41
+16
-6
+34
-19
+20
-8
+3
+7
+84
+36
-36
+84
+5
+59
-24
+37
-1
+6
+4
-4
+75
-65
+3
-3
+148
-28
~
VERSION
~
extsrc/shell.c
~
extsrc/sqlite3.c
~
extsrc/sqlite3.h
~
skins/default/header.txt
~
skins/eagle/header.txt
~
skins/original/header.txt
~
skins/xekri/header.txt
~
src/alerts.c
~
src/browse.c
~
src/cgi.c
~
src/checkin.c
~
src/clone.c
~
src/db.c
~
src/default.css
~
src/doc.c
~
src/forum.c
~
src/graph.c
~
src/http_ssl.c
~
src/info.c
~
src/loadctrl.c
~
src/login.c
~
src/main.c
~
src/manifest.c
~
src/markdown_html.c
~
src/name.c
~
src/pikchrshow.c
~
src/printf.c
~
src/repolist.c
~
src/report.c
~
src/security_audit.c
~
src/setup.c
~
src/setupuser.c
~
src/shun.c
~
src/smtp.c
~
src/stash.c
~
src/style.c
~
src/th.c
~
src/th.h
~
src/th_lang.c
~
src/th_main.c
~
src/th_tcl.c
~
src/timeline.c
~
src/tkt.c
~
src/tktsetup.c
~
src/unversioned.c
~
src/user.c
~
src/util.c
~
src/winfile.c
~
src/xfer.c
~
src/zip.c
~
test/tester.tcl
~
test/th1-taint.test
~
test/th1.test
~
tools/fake-smtpd.tcl
~
www/cgi.wiki
~
www/changes.wiki
~
www/customskin.md
~
www/env-opts.md
~
www/index.wiki
~
www/quickstart.wiki
~
www/server/debian/service.md
~
www/th1.md
M
VERSION
+1
-1
| --- VERSION | ||
| +++ VERSION | ||
| @@ -1,1 +1,1 @@ | ||
| 1 | -2.26 | |
| 1 | +2.27 | |
| 2 | 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.26 |
| 2 |
| --- VERSION | |
| +++ VERSION | |
| @@ -1,1 +1,1 @@ | |
| 1 | 2.27 |
| 2 |
+37
-25
| --- extsrc/shell.c | ||
| +++ extsrc/shell.c | ||
| @@ -1622,34 +1622,10 @@ | ||
| 1622 | 1622 | if( n>350 ) n = 350; |
| 1623 | 1623 | sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); |
| 1624 | 1624 | sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); |
| 1625 | 1625 | } |
| 1626 | 1626 | |
| 1627 | - | |
| 1628 | -/* | |
| 1629 | -** SQL function: shell_module_schema(X) | |
| 1630 | -** | |
| 1631 | -** Return a fake schema for the table-valued function or eponymous virtual | |
| 1632 | -** table X. | |
| 1633 | -*/ | |
| 1634 | -static void shellModuleSchema( | |
| 1635 | - sqlite3_context *pCtx, | |
| 1636 | - int nVal, | |
| 1637 | - sqlite3_value **apVal | |
| 1638 | -){ | |
| 1639 | - const char *zName; | |
| 1640 | - char *zFake; | |
| 1641 | - UNUSED_PARAMETER(nVal); | |
| 1642 | - zName = (const char*)sqlite3_value_text(apVal[0]); | |
| 1643 | - zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; | |
| 1644 | - if( zFake ){ | |
| 1645 | - sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), | |
| 1646 | - -1, sqlite3_free); | |
| 1647 | - free(zFake); | |
| 1648 | - } | |
| 1649 | -} | |
| 1650 | - | |
| 1651 | 1627 | /* |
| 1652 | 1628 | ** SQL function: shell_add_schema(S,X) |
| 1653 | 1629 | ** |
| 1654 | 1630 | ** Add the schema name X to the CREATE statement in S and return the result. |
| 1655 | 1631 | ** Examples: |
| @@ -18709,10 +18685,13 @@ | ||
| 18709 | 18685 | rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1); |
| 18710 | 18686 | } |
| 18711 | 18687 | return rc; |
| 18712 | 18688 | } |
| 18713 | 18689 | |
| 18690 | +#ifdef _WIN32 | |
| 18691 | + | |
| 18692 | +#endif | |
| 18714 | 18693 | int sqlite3_dbdata_init( |
| 18715 | 18694 | sqlite3 *db, |
| 18716 | 18695 | char **pzErrMsg, |
| 18717 | 18696 | const sqlite3_api_routines *pApi |
| 18718 | 18697 | ){ |
| @@ -25937,10 +25916,43 @@ | ||
| 25937 | 25916 | int sleep = sqlite3_value_int(argv[0]); |
| 25938 | 25917 | (void)argcUnused; |
| 25939 | 25918 | sqlite3_sleep(sleep/1000); |
| 25940 | 25919 | sqlite3_result_int(context, sleep); |
| 25941 | 25920 | } |
| 25921 | + | |
| 25922 | +/* | |
| 25923 | +** SQL function: shell_module_schema(X) | |
| 25924 | +** | |
| 25925 | +** Return a fake schema for the table-valued function or eponymous virtual | |
| 25926 | +** table X. | |
| 25927 | +*/ | |
| 25928 | +static void shellModuleSchema( | |
| 25929 | + sqlite3_context *pCtx, | |
| 25930 | + int nVal, | |
| 25931 | + sqlite3_value **apVal | |
| 25932 | +){ | |
| 25933 | + const char *zName; | |
| 25934 | + char *zFake; | |
| 25935 | + ShellState *p = (ShellState*)sqlite3_user_data(pCtx); | |
| 25936 | + FILE *pSavedLog = p->pLog; | |
| 25937 | + UNUSED_PARAMETER(nVal); | |
| 25938 | + zName = (const char*)sqlite3_value_text(apVal[0]); | |
| 25939 | + | |
| 25940 | + /* Temporarily disable the ".log" when calling shellFakeSchema() because | |
| 25941 | + ** shellFakeSchema() might generate failures for some ephemeral virtual | |
| 25942 | + ** tables due to missing arguments. Example: fts4aux. | |
| 25943 | + ** https://sqlite.org/forum/forumpost/42fe6520b803be51 */ | |
| 25944 | + p->pLog = 0; | |
| 25945 | + zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; | |
| 25946 | + p->pLog = pSavedLog; | |
| 25947 | + | |
| 25948 | + if( zFake ){ | |
| 25949 | + sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), | |
| 25950 | + -1, sqlite3_free); | |
| 25951 | + free(zFake); | |
| 25952 | + } | |
| 25953 | +} | |
| 25942 | 25954 | |
| 25943 | 25955 | /* Flags for open_db(). |
| 25944 | 25956 | ** |
| 25945 | 25957 | ** The default behavior of open_db() is to exit(1) if the database fails to |
| 25946 | 25958 | ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error |
| @@ -26081,11 +26093,11 @@ | ||
| 26081 | 26093 | shellDtostr, 0, 0); |
| 26082 | 26094 | sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, |
| 26083 | 26095 | shellDtostr, 0, 0); |
| 26084 | 26096 | sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, |
| 26085 | 26097 | shellAddSchemaName, 0, 0); |
| 26086 | - sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, | |
| 26098 | + sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, p, | |
| 26087 | 26099 | shellModuleSchema, 0, 0); |
| 26088 | 26100 | sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, |
| 26089 | 26101 | shellPutsFunc, 0, 0); |
| 26090 | 26102 | sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, |
| 26091 | 26103 | shellUSleepFunc, 0, 0); |
| 26092 | 26104 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -1622,34 +1622,10 @@ | |
| 1622 | if( n>350 ) n = 350; |
| 1623 | sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); |
| 1624 | sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); |
| 1625 | } |
| 1626 | |
| 1627 | |
| 1628 | /* |
| 1629 | ** SQL function: shell_module_schema(X) |
| 1630 | ** |
| 1631 | ** Return a fake schema for the table-valued function or eponymous virtual |
| 1632 | ** table X. |
| 1633 | */ |
| 1634 | static void shellModuleSchema( |
| 1635 | sqlite3_context *pCtx, |
| 1636 | int nVal, |
| 1637 | sqlite3_value **apVal |
| 1638 | ){ |
| 1639 | const char *zName; |
| 1640 | char *zFake; |
| 1641 | UNUSED_PARAMETER(nVal); |
| 1642 | zName = (const char*)sqlite3_value_text(apVal[0]); |
| 1643 | zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; |
| 1644 | if( zFake ){ |
| 1645 | sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), |
| 1646 | -1, sqlite3_free); |
| 1647 | free(zFake); |
| 1648 | } |
| 1649 | } |
| 1650 | |
| 1651 | /* |
| 1652 | ** SQL function: shell_add_schema(S,X) |
| 1653 | ** |
| 1654 | ** Add the schema name X to the CREATE statement in S and return the result. |
| 1655 | ** Examples: |
| @@ -18709,10 +18685,13 @@ | |
| 18709 | rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1); |
| 18710 | } |
| 18711 | return rc; |
| 18712 | } |
| 18713 | |
| 18714 | int sqlite3_dbdata_init( |
| 18715 | sqlite3 *db, |
| 18716 | char **pzErrMsg, |
| 18717 | const sqlite3_api_routines *pApi |
| 18718 | ){ |
| @@ -25937,10 +25916,43 @@ | |
| 25937 | int sleep = sqlite3_value_int(argv[0]); |
| 25938 | (void)argcUnused; |
| 25939 | sqlite3_sleep(sleep/1000); |
| 25940 | sqlite3_result_int(context, sleep); |
| 25941 | } |
| 25942 | |
| 25943 | /* Flags for open_db(). |
| 25944 | ** |
| 25945 | ** The default behavior of open_db() is to exit(1) if the database fails to |
| 25946 | ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error |
| @@ -26081,11 +26093,11 @@ | |
| 26081 | shellDtostr, 0, 0); |
| 26082 | sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, |
| 26083 | shellDtostr, 0, 0); |
| 26084 | sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, |
| 26085 | shellAddSchemaName, 0, 0); |
| 26086 | sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0, |
| 26087 | shellModuleSchema, 0, 0); |
| 26088 | sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, |
| 26089 | shellPutsFunc, 0, 0); |
| 26090 | sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, |
| 26091 | shellUSleepFunc, 0, 0); |
| 26092 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -1622,34 +1622,10 @@ | |
| 1622 | if( n>350 ) n = 350; |
| 1623 | sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); |
| 1624 | sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); |
| 1625 | } |
| 1626 | |
| 1627 | /* |
| 1628 | ** SQL function: shell_add_schema(S,X) |
| 1629 | ** |
| 1630 | ** Add the schema name X to the CREATE statement in S and return the result. |
| 1631 | ** Examples: |
| @@ -18709,10 +18685,13 @@ | |
| 18685 | rc = sqlite3_create_module(db, "sqlite_dbptr", &dbdata_module, (void*)1); |
| 18686 | } |
| 18687 | return rc; |
| 18688 | } |
| 18689 | |
| 18690 | #ifdef _WIN32 |
| 18691 | |
| 18692 | #endif |
| 18693 | int sqlite3_dbdata_init( |
| 18694 | sqlite3 *db, |
| 18695 | char **pzErrMsg, |
| 18696 | const sqlite3_api_routines *pApi |
| 18697 | ){ |
| @@ -25937,10 +25916,43 @@ | |
| 25916 | int sleep = sqlite3_value_int(argv[0]); |
| 25917 | (void)argcUnused; |
| 25918 | sqlite3_sleep(sleep/1000); |
| 25919 | sqlite3_result_int(context, sleep); |
| 25920 | } |
| 25921 | |
| 25922 | /* |
| 25923 | ** SQL function: shell_module_schema(X) |
| 25924 | ** |
| 25925 | ** Return a fake schema for the table-valued function or eponymous virtual |
| 25926 | ** table X. |
| 25927 | */ |
| 25928 | static void shellModuleSchema( |
| 25929 | sqlite3_context *pCtx, |
| 25930 | int nVal, |
| 25931 | sqlite3_value **apVal |
| 25932 | ){ |
| 25933 | const char *zName; |
| 25934 | char *zFake; |
| 25935 | ShellState *p = (ShellState*)sqlite3_user_data(pCtx); |
| 25936 | FILE *pSavedLog = p->pLog; |
| 25937 | UNUSED_PARAMETER(nVal); |
| 25938 | zName = (const char*)sqlite3_value_text(apVal[0]); |
| 25939 | |
| 25940 | /* Temporarily disable the ".log" when calling shellFakeSchema() because |
| 25941 | ** shellFakeSchema() might generate failures for some ephemeral virtual |
| 25942 | ** tables due to missing arguments. Example: fts4aux. |
| 25943 | ** https://sqlite.org/forum/forumpost/42fe6520b803be51 */ |
| 25944 | p->pLog = 0; |
| 25945 | zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; |
| 25946 | p->pLog = pSavedLog; |
| 25947 | |
| 25948 | if( zFake ){ |
| 25949 | sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), |
| 25950 | -1, sqlite3_free); |
| 25951 | free(zFake); |
| 25952 | } |
| 25953 | } |
| 25954 | |
| 25955 | /* Flags for open_db(). |
| 25956 | ** |
| 25957 | ** The default behavior of open_db() is to exit(1) if the database fails to |
| 25958 | ** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error |
| @@ -26081,11 +26093,11 @@ | |
| 26093 | shellDtostr, 0, 0); |
| 26094 | sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0, |
| 26095 | shellDtostr, 0, 0); |
| 26096 | sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0, |
| 26097 | shellAddSchemaName, 0, 0); |
| 26098 | sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, p, |
| 26099 | shellModuleSchema, 0, 0); |
| 26100 | sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p, |
| 26101 | shellPutsFunc, 0, 0); |
| 26102 | sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0, |
| 26103 | shellUSleepFunc, 0, 0); |
| 26104 |
+222
-132
| --- extsrc/sqlite3.c | ||
| +++ extsrc/sqlite3.c | ||
| @@ -16,11 +16,11 @@ | ||
| 16 | 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | 19 | ** |
| 20 | 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | -** 20acd630b91609725794ce84f9eda01d5f3c with changes in files: | |
| 21 | +** 20abf1ec107f942e4527901685d61283c9c2 with changes in files: | |
| 22 | 22 | ** |
| 23 | 23 | ** |
| 24 | 24 | */ |
| 25 | 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | 26 | #define SQLITE_CORE 1 |
| @@ -465,11 +465,11 @@ | ||
| 465 | 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | 467 | */ |
| 468 | 468 | #define SQLITE_VERSION "3.50.0" |
| 469 | 469 | #define SQLITE_VERSION_NUMBER 3050000 |
| 470 | -#define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6" | |
| 470 | +#define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849" | |
| 471 | 471 | |
| 472 | 472 | /* |
| 473 | 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | 475 | ** |
| @@ -11872,13 +11872,14 @@ | ||
| 11872 | 11872 | ** This may appear to have some counter-intuitive effects if a single row |
| 11873 | 11873 | ** is written to more than once during a session. For example, if a row |
| 11874 | 11874 | ** is inserted while a session object is enabled, then later deleted while |
| 11875 | 11875 | ** the same session object is disabled, no INSERT record will appear in the |
| 11876 | 11876 | ** changeset, even though the delete took place while the session was disabled. |
| 11877 | -** Or, if one field of a row is updated while a session is disabled, and | |
| 11878 | -** another field of the same row is updated while the session is enabled, the | |
| 11879 | -** resulting changeset will contain an UPDATE change that updates both fields. | |
| 11877 | +** Or, if one field of a row is updated while a session is enabled, and | |
| 11878 | +** then another field of the same row is updated while the session is disabled, | |
| 11879 | +** the resulting changeset will contain an UPDATE change that updates both | |
| 11880 | +** fields. | |
| 11880 | 11881 | */ |
| 11881 | 11882 | SQLITE_API int sqlite3session_changeset( |
| 11882 | 11883 | sqlite3_session *pSession, /* Session object */ |
| 11883 | 11884 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11884 | 11885 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11946,12 +11947,13 @@ | ||
| 11946 | 11947 | ** To clarify, if this function is called and then a changeset constructed |
| 11947 | 11948 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11948 | 11949 | ** database zFrom the contents of the two compatible tables would be |
| 11949 | 11950 | ** identical. |
| 11950 | 11951 | ** |
| 11951 | -** It an error if database zFrom does not exist or does not contain the | |
| 11952 | -** required compatible table. | |
| 11952 | +** Unless the call to this function is a no-op as described above, it is an | |
| 11953 | +** error if database zFrom does not exist or does not contain the required | |
| 11954 | +** compatible table. | |
| 11953 | 11955 | ** |
| 11954 | 11956 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11955 | 11957 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11956 | 11958 | ** may be set to point to a buffer containing an English language error |
| 11957 | 11959 | ** message. It is the responsibility of the caller to free this buffer using |
| @@ -19162,10 +19164,11 @@ | ||
| 19162 | 19164 | unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ |
| 19163 | 19165 | unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ |
| 19164 | 19166 | unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */ |
| 19165 | 19167 | unsigned bNoQuery:1; /* Do not use this index to optimize queries */ |
| 19166 | 19168 | unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ |
| 19169 | + unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */ | |
| 19167 | 19170 | unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ |
| 19168 | 19171 | unsigned bHasExpr:1; /* Index contains an expression, either a literal |
| 19169 | 19172 | ** expression, or a reference to a VIRTUAL column */ |
| 19170 | 19173 | #ifdef SQLITE_ENABLE_STAT4 |
| 19171 | 19174 | int nSample; /* Number of elements in aSample[] */ |
| @@ -32985,10 +32988,19 @@ | ||
| 32985 | 32988 | va_end(ap); |
| 32986 | 32989 | zBuf[acc.nChar] = 0; |
| 32987 | 32990 | return zBuf; |
| 32988 | 32991 | } |
| 32989 | 32992 | |
| 32993 | +/* Maximum size of an sqlite3_log() message. */ | |
| 32994 | +#if defined(SQLITE_MAX_LOG_MESSAGE) | |
| 32995 | + /* Leave the definition as supplied */ | |
| 32996 | +#elif SQLITE_PRINT_BUF_SIZE*10>10000 | |
| 32997 | +# define SQLITE_MAX_LOG_MESSAGE 10000 | |
| 32998 | +#else | |
| 32999 | +# define SQLITE_MAX_LOG_MESSAGE (SQLITE_PRINT_BUF_SIZE*10) | |
| 33000 | +#endif | |
| 33001 | + | |
| 32990 | 33002 | /* |
| 32991 | 33003 | ** This is the routine that actually formats the sqlite3_log() message. |
| 32992 | 33004 | ** We house it in a separate routine from sqlite3_log() to avoid using |
| 32993 | 33005 | ** stack space on small-stack systems when logging is disabled. |
| 32994 | 33006 | ** |
| @@ -33001,11 +33013,11 @@ | ||
| 33001 | 33013 | ** Care must be taken that any sqlite3_log() calls that occur while the |
| 33002 | 33014 | ** memory mutex is held do not use these mechanisms. |
| 33003 | 33015 | */ |
| 33004 | 33016 | static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ |
| 33005 | 33017 | StrAccum acc; /* String accumulator */ |
| 33006 | - char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ | |
| 33018 | + char zMsg[SQLITE_MAX_LOG_MESSAGE]; /* Complete log message */ | |
| 33007 | 33019 | |
| 33008 | 33020 | sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); |
| 33009 | 33021 | sqlite3_str_vappendf(&acc, zFormat, ap); |
| 33010 | 33022 | sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, |
| 33011 | 33023 | sqlite3StrAccumFinish(&acc)); |
| @@ -95714,11 +95726,11 @@ | ||
| 95714 | 95726 | } |
| 95715 | 95727 | }else{ |
| 95716 | 95728 | sqlite3VdbeError(p, "%s", pOp->p4.z); |
| 95717 | 95729 | } |
| 95718 | 95730 | pcx = (int)(pOp - aOp); |
| 95719 | - sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); | |
| 95731 | + sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql); | |
| 95720 | 95732 | } |
| 95721 | 95733 | rc = sqlite3VdbeHalt(p); |
| 95722 | 95734 | assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); |
| 95723 | 95735 | if( rc==SQLITE_BUSY ){ |
| 95724 | 95736 | p->rc = SQLITE_BUSY; |
| @@ -97040,11 +97052,11 @@ | ||
| 97040 | 97052 | pOut->u.i = ~sqlite3VdbeIntValue(pIn1); |
| 97041 | 97053 | } |
| 97042 | 97054 | break; |
| 97043 | 97055 | } |
| 97044 | 97056 | |
| 97045 | -/* Opcode: Once P1 P2 * * * | |
| 97057 | +/* Opcode: Once P1 P2 P3 * * | |
| 97046 | 97058 | ** |
| 97047 | 97059 | ** Fall through to the next instruction the first time this opcode is |
| 97048 | 97060 | ** encountered on each invocation of the byte-code program. Jump to P2 |
| 97049 | 97061 | ** on the second and all subsequent encounters during the same invocation. |
| 97050 | 97062 | ** |
| @@ -97056,10 +97068,16 @@ | ||
| 97056 | 97068 | ** |
| 97057 | 97069 | ** For subprograms, there is a bitmask in the VdbeFrame that determines |
| 97058 | 97070 | ** whether or not the jump should be taken. The bitmask is necessary |
| 97059 | 97071 | ** because the self-altering code trick does not work for recursive |
| 97060 | 97072 | ** triggers. |
| 97073 | +** | |
| 97074 | +** The P3 operand is not used directly by this opcode. However P3 is | |
| 97075 | +** used by the code generator as follows: If this opcode is the start | |
| 97076 | +** of a subroutine and that subroutine uses a Bloom filter, then P3 will | |
| 97077 | +** be the register that holds that Bloom filter. See tag-202407032019 | |
| 97078 | +** in the source code for implementation details. | |
| 97061 | 97079 | */ |
| 97062 | 97080 | case OP_Once: { /* jump */ |
| 97063 | 97081 | u32 iAddr; /* Address of this instruction */ |
| 97064 | 97082 | assert( p->aOp[0].opcode==OP_Init ); |
| 97065 | 97083 | if( p->pFrame ){ |
| @@ -103549,12 +103567,12 @@ | ||
| 103549 | 103567 | sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); |
| 103550 | 103568 | } |
| 103551 | 103569 | p->rc = rc; |
| 103552 | 103570 | sqlite3SystemError(db, rc); |
| 103553 | 103571 | testcase( sqlite3GlobalConfig.xLog!=0 ); |
| 103554 | - sqlite3_log(rc, "statement aborts at %d: [%s] %s", | |
| 103555 | - (int)(pOp - aOp), p->zSql, p->zErrMsg); | |
| 103572 | + sqlite3_log(rc, "statement aborts at %d: %s; [%s]", | |
| 103573 | + (int)(pOp - aOp), p->zErrMsg, p->zSql); | |
| 103556 | 103574 | if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); |
| 103557 | 103575 | if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); |
| 103558 | 103576 | if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ |
| 103559 | 103577 | db->flags |= SQLITE_CorruptRdOnly; |
| 103560 | 103578 | } |
| @@ -114020,15 +114038,16 @@ | ||
| 114020 | 114038 | pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); |
| 114021 | 114039 | rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); |
| 114022 | 114040 | sqlite3SelectDelete(pParse->db, pCopy); |
| 114023 | 114041 | sqlite3DbFree(pParse->db, dest.zAffSdst); |
| 114024 | 114042 | if( addrBloom ){ |
| 114043 | + /* Remember that location of the Bloom filter in the P3 operand | |
| 114044 | + ** of the OP_Once that began this subroutine. tag-202407032019 */ | |
| 114025 | 114045 | sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; |
| 114026 | 114046 | if( dest.iSDParm2==0 ){ |
| 114027 | - sqlite3VdbeChangeToNoop(v, addrBloom); | |
| 114028 | - }else{ | |
| 114029 | - sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; | |
| 114047 | + /* If the Bloom filter won't actually be used, keep it small */ | |
| 114048 | + sqlite3VdbeGetOp(v, addrBloom)->p1 = 10; | |
| 114030 | 114049 | } |
| 114031 | 114050 | } |
| 114032 | 114051 | if( rc ){ |
| 114033 | 114052 | sqlite3KeyInfoUnref(pKeyInfo); |
| 114034 | 114053 | return; |
| @@ -114471,11 +114490,11 @@ | ||
| 114471 | 114490 | if( destIfFalse==destIfNull ){ |
| 114472 | 114491 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 114473 | 114492 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 114474 | 114493 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 114475 | 114494 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| 114476 | - if( pOp->opcode==OP_Once && pOp->p3>0 ){ | |
| 114495 | + if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */ | |
| 114477 | 114496 | assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); |
| 114478 | 114497 | sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, |
| 114479 | 114498 | rLhs, nVector); VdbeCoverage(v); |
| 114480 | 114499 | } |
| 114481 | 114500 | } |
| @@ -124089,11 +124108,11 @@ | ||
| 124089 | 124108 | */ |
| 124090 | 124109 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 124091 | 124110 | int i; |
| 124092 | 124111 | i16 iCol16; |
| 124093 | 124112 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 124094 | - assert( pIdx->nColumn<=SQLITE_MAX_COLUMN ); | |
| 124113 | + assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 ); | |
| 124095 | 124114 | iCol16 = iCol; |
| 124096 | 124115 | for(i=0; i<pIdx->nColumn; i++){ |
| 124097 | 124116 | if( iCol16==pIdx->aiColumn[i] ){ |
| 124098 | 124117 | return i; |
| 124099 | 124118 | } |
| @@ -127237,10 +127256,11 @@ | ||
| 127237 | 127256 | }else{ |
| 127238 | 127257 | j = pCExpr->iColumn; |
| 127239 | 127258 | assert( j<=0x7fff ); |
| 127240 | 127259 | if( j<0 ){ |
| 127241 | 127260 | j = pTab->iPKey; |
| 127261 | + pIndex->bIdxRowid = 1; | |
| 127242 | 127262 | }else{ |
| 127243 | 127263 | if( pTab->aCol[j].notNull==0 ){ |
| 127244 | 127264 | pIndex->uniqNotNull = 0; |
| 127245 | 127265 | } |
| 127246 | 127266 | if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ |
| @@ -149706,11 +149726,12 @@ | ||
| 149706 | 149726 | && pE2->iColumn==pColumn->iColumn |
| 149707 | 149727 | ){ |
| 149708 | 149728 | return; /* Already present. Return without doing anything. */ |
| 149709 | 149729 | } |
| 149710 | 149730 | } |
| 149711 | - if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ | |
| 149731 | + assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB ); | |
| 149732 | + if( sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){ | |
| 149712 | 149733 | pConst->bHasAffBlob = 1; |
| 149713 | 149734 | } |
| 149714 | 149735 | |
| 149715 | 149736 | pConst->nConst++; |
| 149716 | 149737 | pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, |
| @@ -149781,11 +149802,12 @@ | ||
| 149781 | 149802 | for(i=0; i<pConst->nConst; i++){ |
| 149782 | 149803 | Expr *pColumn = pConst->apExpr[i*2]; |
| 149783 | 149804 | if( pColumn==pExpr ) continue; |
| 149784 | 149805 | if( pColumn->iTable!=pExpr->iTable ) continue; |
| 149785 | 149806 | if( pColumn->iColumn!=pExpr->iColumn ) continue; |
| 149786 | - if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ | |
| 149807 | + assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB ); | |
| 149808 | + if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){ | |
| 149787 | 149809 | break; |
| 149788 | 149810 | } |
| 149789 | 149811 | /* A match is found. Add the EP_FixedCol property */ |
| 149790 | 149812 | pConst->nChng++; |
| 149791 | 149813 | ExprClearProperty(pExpr, EP_Leaf); |
| @@ -152965,10 +152987,16 @@ | ||
| 152965 | 152987 | pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, |
| 152966 | 152988 | p->pEList, p, wctrlFlags, p->nSelectRow); |
| 152967 | 152989 | if( pWInfo==0 ) goto select_end; |
| 152968 | 152990 | if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ |
| 152969 | 152991 | p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); |
| 152992 | + if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){ | |
| 152993 | + /* TUNING: For a UNION CTE, because UNION is implies DISTINCT, | |
| 152994 | + ** reduce the estimated output row count by 8 (LogEst 30). | |
| 152995 | + ** Search for tag-20250414a to see other cases */ | |
| 152996 | + p->nSelectRow -= 30; | |
| 152997 | + } | |
| 152970 | 152998 | } |
| 152971 | 152999 | if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ |
| 152972 | 153000 | sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); |
| 152973 | 153001 | } |
| 152974 | 153002 | if( sSort.pOrderBy ){ |
| @@ -160131,11 +160159,11 @@ | ||
| 160131 | 160159 | |
| 160132 | 160160 | |
| 160133 | 160161 | /* |
| 160134 | 160162 | ** pX is an expression of the form: (vector) IN (SELECT ...) |
| 160135 | 160163 | ** In other words, it is a vector IN operator with a SELECT clause on the |
| 160136 | -** LHS. But not all terms in the vector are indexable and the terms might | |
| 160164 | +** RHS. But not all terms in the vector are indexable and the terms might | |
| 160137 | 160165 | ** not be in the correct order for indexing. |
| 160138 | 160166 | ** |
| 160139 | 160167 | ** This routine makes a copy of the input pX expression and then adjusts |
| 160140 | 160168 | ** the vector on the LHS with corresponding changes to the SELECT so that |
| 160141 | 160169 | ** the vector contains only index terms and those terms are in the correct |
| @@ -167926,11 +167954,11 @@ | ||
| 167926 | 167954 | } |
| 167927 | 167955 | |
| 167928 | 167956 | if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 |
| 167929 | 167957 | && pNew->u.btree.nEq<pProbe->nColumn |
| 167930 | 167958 | && (pNew->u.btree.nEq<pProbe->nKeyCol || |
| 167931 | - pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) | |
| 167959 | + (pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid)) | |
| 167932 | 167960 | ){ |
| 167933 | 167961 | if( pNew->u.btree.nEq>3 ){ |
| 167934 | 167962 | sqlite3ProgressCheck(pParse); |
| 167935 | 167963 | } |
| 167936 | 167964 | whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); |
| @@ -171384,11 +171412,12 @@ | ||
| 171384 | 171412 | wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1); |
| 171385 | 171413 | if( db->mallocFailed ) goto whereBeginError; |
| 171386 | 171414 | } |
| 171387 | 171415 | |
| 171388 | 171416 | /* TUNING: Assume that a DISTINCT clause on a subquery reduces |
| 171389 | - ** the output size by a factor of 8 (LogEst -30). | |
| 171417 | + ** the output size by a factor of 8 (LogEst -30). Search for | |
| 171418 | + ** tag-20250414a to see other cases. | |
| 171390 | 171419 | */ |
| 171391 | 171420 | if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ |
| 171392 | 171421 | WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n", |
| 171393 | 171422 | pWInfo->nRowOut, pWInfo->nRowOut-30)); |
| 171394 | 171423 | pWInfo->nRowOut -= 30; |
| @@ -206261,20 +206290,20 @@ | ||
| 206261 | 206290 | case FTS3_MATCHINFO_LCS: |
| 206262 | 206291 | nVal = pInfo->nCol; |
| 206263 | 206292 | break; |
| 206264 | 206293 | |
| 206265 | 206294 | case FTS3_MATCHINFO_LHITS: |
| 206266 | - nVal = pInfo->nCol * pInfo->nPhrase; | |
| 206295 | + nVal = (size_t)pInfo->nCol * pInfo->nPhrase; | |
| 206267 | 206296 | break; |
| 206268 | 206297 | |
| 206269 | 206298 | case FTS3_MATCHINFO_LHITS_BM: |
| 206270 | - nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32); | |
| 206299 | + nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32); | |
| 206271 | 206300 | break; |
| 206272 | 206301 | |
| 206273 | 206302 | default: |
| 206274 | 206303 | assert( cArg==FTS3_MATCHINFO_HITS ); |
| 206275 | - nVal = pInfo->nCol * pInfo->nPhrase * 3; | |
| 206304 | + nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3; | |
| 206276 | 206305 | break; |
| 206277 | 206306 | } |
| 206278 | 206307 | |
| 206279 | 206308 | return nVal; |
| 206280 | 206309 | } |
| @@ -207956,60 +207985,113 @@ | ||
| 207956 | 207985 | ** Growing our own isspace() routine this way is twice as fast as |
| 207957 | 207986 | ** the library isspace() function, resulting in a 7% overall performance |
| 207958 | 207987 | ** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). |
| 207959 | 207988 | */ |
| 207960 | 207989 | static const char jsonIsSpace[] = { |
| 207961 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, | |
| 207962 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207963 | - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207964 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207965 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207966 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207967 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207968 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207969 | - | |
| 207970 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207971 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207972 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207973 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207974 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207975 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207976 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207977 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207990 | +#ifdef SQLITE_ASCII | |
| 207991 | +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 207992 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 */ | |
| 207993 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ | |
| 207994 | + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ | |
| 207995 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ | |
| 207996 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ | |
| 207997 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ | |
| 207998 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ | |
| 207999 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ | |
| 208000 | + | |
| 208001 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ | |
| 208002 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ | |
| 208003 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ | |
| 208004 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ | |
| 208005 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ | |
| 208006 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ | |
| 208007 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ | |
| 208008 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ | |
| 208009 | +#endif | |
| 208010 | +#ifdef SQLITE_EBCDIC | |
| 208011 | +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 208012 | + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0 */ | |
| 208013 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ | |
| 208014 | + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ | |
| 208015 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ | |
| 208016 | + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ | |
| 208017 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ | |
| 208018 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ | |
| 208019 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ | |
| 208020 | + | |
| 208021 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ | |
| 208022 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ | |
| 208023 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ | |
| 208024 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ | |
| 208025 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ | |
| 208026 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ | |
| 208027 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ | |
| 208028 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ | |
| 208029 | +#endif | |
| 208030 | + | |
| 207978 | 208031 | }; |
| 207979 | 208032 | #define jsonIsspace(x) (jsonIsSpace[(unsigned char)x]) |
| 207980 | 208033 | |
| 207981 | 208034 | /* |
| 207982 | 208035 | ** The set of all space characters recognized by jsonIsspace(). |
| 207983 | 208036 | ** Useful as the second argument to strspn(). |
| 207984 | 208037 | */ |
| 208038 | +#ifdef SQLITE_ASCII | |
| 207985 | 208039 | static const char jsonSpaces[] = "\011\012\015\040"; |
| 208040 | +#endif | |
| 208041 | +#ifdef SQLITE_EBCDIC | |
| 208042 | +static const char jsonSpaces[] = "\005\045\015\100"; | |
| 208043 | +#endif | |
| 208044 | + | |
| 207986 | 208045 | |
| 207987 | 208046 | /* |
| 207988 | 208047 | ** Characters that are special to JSON. Control characters, |
| 207989 | 208048 | ** '"' and '\\' and '\''. Actually, '\'' is not special to |
| 207990 | 208049 | ** canonical JSON, but it is special in JSON-5, so we include |
| 207991 | 208050 | ** it in the set of special characters. |
| 207992 | 208051 | */ |
| 207993 | 208052 | static const char jsonIsOk[256] = { |
| 207994 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207995 | - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 207996 | - 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 207997 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 207998 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 207999 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, | |
| 208000 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208001 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208002 | - | |
| 208003 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208004 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208005 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208006 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208007 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208008 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208009 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 208010 | - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 | |
| 208053 | +#ifdef SQLITE_ASCII | |
| 208054 | +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 208055 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ | |
| 208056 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ | |
| 208057 | + 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 2 */ | |
| 208058 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 3 */ | |
| 208059 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ | |
| 208060 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 5 */ | |
| 208061 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ | |
| 208062 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ | |
| 208063 | + | |
| 208064 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ | |
| 208065 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ | |
| 208066 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ | |
| 208067 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ | |
| 208068 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ | |
| 208069 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ | |
| 208070 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ | |
| 208071 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ | |
| 208072 | +#endif | |
| 208073 | +#ifdef SQLITE_EBCDIC | |
| 208074 | +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 208075 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ | |
| 208076 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ | |
| 208077 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ | |
| 208078 | + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 3 */ | |
| 208079 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ | |
| 208080 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5 */ | |
| 208081 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ | |
| 208082 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, /* 7 */ | |
| 208083 | + | |
| 208084 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ | |
| 208085 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ | |
| 208086 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ | |
| 208087 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ | |
| 208088 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ | |
| 208089 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ | |
| 208090 | + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ | |
| 208091 | + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ | |
| 208092 | +#endif | |
| 208011 | 208093 | }; |
| 208012 | 208094 | |
| 208013 | 208095 | /* Objects */ |
| 208014 | 208096 | typedef struct JsonCache JsonCache; |
| 208015 | 208097 | typedef struct JsonString JsonString; |
| @@ -208150,11 +208232,11 @@ | ||
| 208150 | 208232 | |
| 208151 | 208233 | /************************************************************************** |
| 208152 | 208234 | ** Forward references |
| 208153 | 208235 | **************************************************************************/ |
| 208154 | 208236 | static void jsonReturnStringAsBlob(JsonString*); |
| 208155 | -static int jsonFuncArgMightBeBinary(sqlite3_value *pJson); | |
| 208237 | +static int jsonArgIsJsonb(sqlite3_value *pJson, JsonParse *p); | |
| 208156 | 208238 | static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*); |
| 208157 | 208239 | static void jsonReturnParse(sqlite3_context*,JsonParse*); |
| 208158 | 208240 | static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); |
| 208159 | 208241 | static void jsonParseFree(JsonParse*); |
| 208160 | 208242 | static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); |
| @@ -208568,15 +208650,13 @@ | ||
| 208568 | 208650 | jsonAppendString(p, z, n); |
| 208569 | 208651 | } |
| 208570 | 208652 | break; |
| 208571 | 208653 | } |
| 208572 | 208654 | default: { |
| 208573 | - if( jsonFuncArgMightBeBinary(pValue) ){ | |
| 208574 | - JsonParse px; | |
| 208575 | - memset(&px, 0, sizeof(px)); | |
| 208576 | - px.aBlob = (u8*)sqlite3_value_blob(pValue); | |
| 208577 | - px.nBlob = sqlite3_value_bytes(pValue); | |
| 208655 | + JsonParse px; | |
| 208656 | + memset(&px, 0, sizeof(px)); | |
| 208657 | + if( jsonArgIsJsonb(pValue, &px) ){ | |
| 208578 | 208658 | jsonTranslateBlobToText(&px, 0, p); |
| 208579 | 208659 | }else if( p->eErr==0 ){ |
| 208580 | 208660 | sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); |
| 208581 | 208661 | p->eErr = JSTRING_ERR; |
| 208582 | 208662 | jsonStringReset(p); |
| @@ -210246,37 +210326,10 @@ | ||
| 210246 | 210326 | } |
| 210247 | 210327 | } |
| 210248 | 210328 | return i; |
| 210249 | 210329 | } |
| 210250 | 210330 | |
| 210251 | - | |
| 210252 | -/* Return true if the input pJson | |
| 210253 | -** | |
| 210254 | -** For performance reasons, this routine does not do a detailed check of the | |
| 210255 | -** input BLOB to ensure that it is well-formed. Hence, false positives are | |
| 210256 | -** possible. False negatives should never occur, however. | |
| 210257 | -*/ | |
| 210258 | -static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){ | |
| 210259 | - u32 sz, n; | |
| 210260 | - const u8 *aBlob; | |
| 210261 | - int nBlob; | |
| 210262 | - JsonParse s; | |
| 210263 | - if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0; | |
| 210264 | - aBlob = sqlite3_value_blob(pJson); | |
| 210265 | - nBlob = sqlite3_value_bytes(pJson); | |
| 210266 | - if( nBlob<1 ) return 0; | |
| 210267 | - if( NEVER(aBlob==0) || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0; | |
| 210268 | - memset(&s, 0, sizeof(s)); | |
| 210269 | - s.aBlob = (u8*)aBlob; | |
| 210270 | - s.nBlob = nBlob; | |
| 210271 | - n = jsonbPayloadSize(&s, 0, &sz); | |
| 210272 | - if( n==0 ) return 0; | |
| 210273 | - if( sz+n!=(u32)nBlob ) return 0; | |
| 210274 | - if( (aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0 ) return 0; | |
| 210275 | - return sz+n==(u32)nBlob; | |
| 210276 | -} | |
| 210277 | - | |
| 210278 | 210331 | /* |
| 210279 | 210332 | ** Given that a JSONB_ARRAY object starts at offset i, return |
| 210280 | 210333 | ** the number of entries in that array. |
| 210281 | 210334 | */ |
| 210282 | 210335 | static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){ |
| @@ -211100,14 +211153,11 @@ | ||
| 211100 | 211153 | pParse->aBlob = aNull; |
| 211101 | 211154 | pParse->nBlob = 1; |
| 211102 | 211155 | return 0; |
| 211103 | 211156 | } |
| 211104 | 211157 | case SQLITE_BLOB: { |
| 211105 | - if( jsonFuncArgMightBeBinary(pArg) ){ | |
| 211106 | - pParse->aBlob = (u8*)sqlite3_value_blob(pArg); | |
| 211107 | - pParse->nBlob = sqlite3_value_bytes(pArg); | |
| 211108 | - }else{ | |
| 211158 | + if( !jsonArgIsJsonb(pArg, pParse) ){ | |
| 211109 | 211159 | sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1); |
| 211110 | 211160 | return 1; |
| 211111 | 211161 | } |
| 211112 | 211162 | break; |
| 211113 | 211163 | } |
| @@ -211254,31 +211304,50 @@ | ||
| 211254 | 211304 | } |
| 211255 | 211305 | |
| 211256 | 211306 | /* |
| 211257 | 211307 | ** If pArg is a blob that seems like a JSONB blob, then initialize |
| 211258 | 211308 | ** p to point to that JSONB and return TRUE. If pArg does not seem like |
| 211259 | -** a JSONB blob, then return FALSE; | |
| 211309 | +** a JSONB blob, then return FALSE. | |
| 211260 | 211310 | ** |
| 211261 | -** This routine is only called if it is already known that pArg is a | |
| 211262 | -** blob. The only open question is whether or not the blob appears | |
| 211263 | -** to be a JSONB blob. | |
| 211311 | +** For small BLOBs (having no more than 7 bytes of payload) a full | |
| 211312 | +** validity check is done. So for small BLOBs this routine only returns | |
| 211313 | +** true if the value is guaranteed to be a valid JSONB. For larger BLOBs | |
| 211314 | +** (8 byte or more of payload) only the size of the outermost element is | |
| 211315 | +** checked to verify that the BLOB is superficially valid JSONB. | |
| 211316 | +** | |
| 211317 | +** A full JSONB validation is done on smaller BLOBs because those BLOBs might | |
| 211318 | +** also be text JSON that has been incorrectly cast into a BLOB. | |
| 211319 | +** (See tag-20240123-a and https://sqlite.org/forum/forumpost/012136abd5) | |
| 211320 | +** If the BLOB is 9 bytes are larger, then it is not possible for the | |
| 211321 | +** superficial size check done here to pass if the input is really text | |
| 211322 | +** JSON so we do not need to look deeper in that case. | |
| 211323 | +** | |
| 211324 | +** Why we only need to do full JSONB validation for smaller BLOBs: | |
| 211325 | +** | |
| 211326 | +** The first byte of valid JSON text must be one of: '{', '[', '"', ' ', '\n', | |
| 211327 | +** '\r', '\t', '-', or a digit '0' through '9'. Of these, only a subset | |
| 211328 | +** can also be the first byte of JSONB: '{', '[', and digits '3' | |
| 211329 | +** through '9'. In every one of those cases, the payload size is 7 bytes | |
| 211330 | +** or less. So if we do full JSONB validation for every BLOB where the | |
| 211331 | +** payload is less than 7 bytes, we will never get a false positive for | |
| 211332 | +** JSONB on an input that is really text JSON. | |
| 211264 | 211333 | */ |
| 211265 | 211334 | static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){ |
| 211266 | 211335 | u32 n, sz = 0; |
| 211336 | + u8 c; | |
| 211337 | + if( sqlite3_value_type(pArg)!=SQLITE_BLOB ) return 0; | |
| 211267 | 211338 | p->aBlob = (u8*)sqlite3_value_blob(pArg); |
| 211268 | 211339 | p->nBlob = (u32)sqlite3_value_bytes(pArg); |
| 211269 | - if( p->nBlob==0 ){ | |
| 211270 | - p->aBlob = 0; | |
| 211271 | - return 0; | |
| 211272 | - } | |
| 211273 | - if( NEVER(p->aBlob==0) ){ | |
| 211274 | - return 0; | |
| 211275 | - } | |
| 211276 | - if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT | |
| 211340 | + if( p->nBlob>0 | |
| 211341 | + && ALWAYS(p->aBlob!=0) | |
| 211342 | + && ((c = p->aBlob[0]) & 0x0f)<=JSONB_OBJECT | |
| 211277 | 211343 | && (n = jsonbPayloadSize(p, 0, &sz))>0 |
| 211278 | 211344 | && sz+n==p->nBlob |
| 211279 | - && ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0) | |
| 211345 | + && ((c & 0x0f)>JSONB_FALSE || sz==0) | |
| 211346 | + && (sz>7 | |
| 211347 | + || (c!=0x7b && c!=0x5b && !sqlite3Isdigit(c)) | |
| 211348 | + || jsonbValidityCheck(p, 0, p->nBlob, 1)==0) | |
| 211280 | 211349 | ){ |
| 211281 | 211350 | return 1; |
| 211282 | 211351 | } |
| 211283 | 211352 | p->aBlob = 0; |
| 211284 | 211353 | p->nBlob = 0; |
| @@ -212367,25 +212436,21 @@ | ||
| 212367 | 212436 | sqlite3_result_int(ctx, 0); |
| 212368 | 212437 | #endif |
| 212369 | 212438 | return; |
| 212370 | 212439 | } |
| 212371 | 212440 | case SQLITE_BLOB: { |
| 212372 | - if( jsonFuncArgMightBeBinary(argv[0]) ){ | |
| 212441 | + JsonParse py; | |
| 212442 | + memset(&py, 0, sizeof(py)); | |
| 212443 | + if( jsonArgIsJsonb(argv[0], &py) ){ | |
| 212373 | 212444 | if( flags & 0x04 ){ |
| 212374 | 212445 | /* Superficial checking only - accomplished by the |
| 212375 | - ** jsonFuncArgMightBeBinary() call above. */ | |
| 212446 | + ** jsonArgIsJsonb() call above. */ | |
| 212376 | 212447 | res = 1; |
| 212377 | 212448 | }else if( flags & 0x08 ){ |
| 212378 | 212449 | /* Strict checking. Check by translating BLOB->TEXT->BLOB. If |
| 212379 | 212450 | ** no errors occur, call that a "strict check". */ |
| 212380 | - JsonParse px; | |
| 212381 | - u32 iErr; | |
| 212382 | - memset(&px, 0, sizeof(px)); | |
| 212383 | - px.aBlob = (u8*)sqlite3_value_blob(argv[0]); | |
| 212384 | - px.nBlob = sqlite3_value_bytes(argv[0]); | |
| 212385 | - iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1); | |
| 212386 | - res = iErr==0; | |
| 212451 | + res = 0==jsonbValidityCheck(&py, 0, py.nBlob, 1); | |
| 212387 | 212452 | } |
| 212388 | 212453 | break; |
| 212389 | 212454 | } |
| 212390 | 212455 | /* Fall through into interpreting the input as text. See note |
| 212391 | 212456 | ** above at tag-20240123-a. */ |
| @@ -212439,13 +212504,11 @@ | ||
| 212439 | 212504 | |
| 212440 | 212505 | assert( argc==1 ); |
| 212441 | 212506 | UNUSED_PARAMETER(argc); |
| 212442 | 212507 | memset(&s, 0, sizeof(s)); |
| 212443 | 212508 | s.db = sqlite3_context_db_handle(ctx); |
| 212444 | - if( jsonFuncArgMightBeBinary(argv[0]) ){ | |
| 212445 | - s.aBlob = (u8*)sqlite3_value_blob(argv[0]); | |
| 212446 | - s.nBlob = sqlite3_value_bytes(argv[0]); | |
| 212509 | + if( jsonArgIsJsonb(argv[0], &s) ){ | |
| 212447 | 212510 | iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1); |
| 212448 | 212511 | }else{ |
| 212449 | 212512 | s.zJson = (char*)sqlite3_value_text(argv[0]); |
| 212450 | 212513 | if( s.zJson==0 ) return; /* NULL input or OOM */ |
| 212451 | 212514 | s.nJson = sqlite3_value_bytes(argv[0]); |
| @@ -213126,13 +213189,12 @@ | ||
| 213126 | 213189 | jsonEachCursorReset(p); |
| 213127 | 213190 | if( idxNum==0 ) return SQLITE_OK; |
| 213128 | 213191 | memset(&p->sParse, 0, sizeof(p->sParse)); |
| 213129 | 213192 | p->sParse.nJPRef = 1; |
| 213130 | 213193 | p->sParse.db = p->db; |
| 213131 | - if( jsonFuncArgMightBeBinary(argv[0]) ){ | |
| 213132 | - p->sParse.nBlob = sqlite3_value_bytes(argv[0]); | |
| 213133 | - p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]); | |
| 213194 | + if( jsonArgIsJsonb(argv[0], &p->sParse) ){ | |
| 213195 | + /* We have JSONB */ | |
| 213134 | 213196 | }else{ |
| 213135 | 213197 | p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); |
| 213136 | 213198 | p->sParse.nJson = sqlite3_value_bytes(argv[0]); |
| 213137 | 213199 | if( p->sParse.zJson==0 ){ |
| 213138 | 213200 | p->i = p->iEnd = 0; |
| @@ -229245,10 +229307,12 @@ | ||
| 229245 | 229307 | int rc = SQLITE_OK; |
| 229246 | 229308 | |
| 229247 | 229309 | if( pTab->nCol==0 ){ |
| 229248 | 229310 | u8 *abPK; |
| 229249 | 229311 | assert( pTab->azCol==0 || pTab->abPK==0 ); |
| 229312 | + sqlite3_free(pTab->azCol); | |
| 229313 | + pTab->abPK = 0; | |
| 229250 | 229314 | rc = sessionTableInfo(pSession, db, zDb, |
| 229251 | 229315 | pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol, |
| 229252 | 229316 | &pTab->azDflt, &pTab->aiIdx, &abPK, |
| 229253 | 229317 | ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) |
| 229254 | 229318 | ); |
| @@ -230265,21 +230329,47 @@ | ||
| 230265 | 230329 | |
| 230266 | 230330 | /* Check the table schemas match */ |
| 230267 | 230331 | if( rc==SQLITE_OK ){ |
| 230268 | 230332 | int bHasPk = 0; |
| 230269 | 230333 | int bMismatch = 0; |
| 230270 | - int nCol; /* Columns in zFrom.zTbl */ | |
| 230334 | + int nCol = 0; /* Columns in zFrom.zTbl */ | |
| 230271 | 230335 | int bRowid = 0; |
| 230272 | - u8 *abPK; | |
| 230336 | + u8 *abPK = 0; | |
| 230273 | 230337 | const char **azCol = 0; |
| 230274 | - rc = sessionTableInfo(0, db, zFrom, zTbl, | |
| 230275 | - &nCol, 0, 0, &azCol, 0, 0, &abPK, | |
| 230276 | - pSession->bImplicitPK ? &bRowid : 0 | |
| 230277 | - ); | |
| 230338 | + char *zDbExists = 0; | |
| 230339 | + | |
| 230340 | + /* Check that database zFrom is attached. */ | |
| 230341 | + zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom); | |
| 230342 | + if( zDbExists==0 ){ | |
| 230343 | + rc = SQLITE_NOMEM; | |
| 230344 | + }else{ | |
| 230345 | + sqlite3_stmt *pDbExists = 0; | |
| 230346 | + rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0); | |
| 230347 | + if( rc==SQLITE_ERROR ){ | |
| 230348 | + rc = SQLITE_OK; | |
| 230349 | + nCol = -1; | |
| 230350 | + } | |
| 230351 | + sqlite3_finalize(pDbExists); | |
| 230352 | + sqlite3_free(zDbExists); | |
| 230353 | + } | |
| 230354 | + | |
| 230355 | + if( rc==SQLITE_OK && nCol==0 ){ | |
| 230356 | + rc = sessionTableInfo(0, db, zFrom, zTbl, | |
| 230357 | + &nCol, 0, 0, &azCol, 0, 0, &abPK, | |
| 230358 | + pSession->bImplicitPK ? &bRowid : 0 | |
| 230359 | + ); | |
| 230360 | + } | |
| 230278 | 230361 | if( rc==SQLITE_OK ){ |
| 230279 | 230362 | if( pTo->nCol!=nCol ){ |
| 230280 | - bMismatch = 1; | |
| 230363 | + if( nCol<=0 ){ | |
| 230364 | + rc = SQLITE_SCHEMA; | |
| 230365 | + if( pzErrMsg ){ | |
| 230366 | + *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl); | |
| 230367 | + } | |
| 230368 | + }else{ | |
| 230369 | + bMismatch = 1; | |
| 230370 | + } | |
| 230281 | 230371 | }else{ |
| 230282 | 230372 | int i; |
| 230283 | 230373 | for(i=0; i<nCol; i++){ |
| 230284 | 230374 | if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1; |
| 230285 | 230375 | if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1; |
| @@ -257173,11 +257263,11 @@ | ||
| 257173 | 257263 | int nArg, /* Number of args */ |
| 257174 | 257264 | sqlite3_value **apUnused /* Function arguments */ |
| 257175 | 257265 | ){ |
| 257176 | 257266 | assert( nArg==0 ); |
| 257177 | 257267 | UNUSED_PARAM2(nArg, apUnused); |
| 257178 | - sqlite3_result_text(pCtx, "fts5: 2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6", -1, SQLITE_TRANSIENT); | |
| 257268 | + sqlite3_result_text(pCtx, "fts5: 2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849", -1, SQLITE_TRANSIENT); | |
| 257179 | 257269 | } |
| 257180 | 257270 | |
| 257181 | 257271 | /* |
| 257182 | 257272 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 257183 | 257273 | ** |
| 257184 | 257274 |
| --- extsrc/sqlite3.c | |
| +++ extsrc/sqlite3.c | |
| @@ -16,11 +16,11 @@ | |
| 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | ** |
| 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | ** 20acd630b91609725794ce84f9eda01d5f3c with changes in files: |
| 22 | ** |
| 23 | ** |
| 24 | */ |
| 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | #define SQLITE_CORE 1 |
| @@ -465,11 +465,11 @@ | |
| 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | */ |
| 468 | #define SQLITE_VERSION "3.50.0" |
| 469 | #define SQLITE_VERSION_NUMBER 3050000 |
| 470 | #define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -11872,13 +11872,14 @@ | |
| 11872 | ** This may appear to have some counter-intuitive effects if a single row |
| 11873 | ** is written to more than once during a session. For example, if a row |
| 11874 | ** is inserted while a session object is enabled, then later deleted while |
| 11875 | ** the same session object is disabled, no INSERT record will appear in the |
| 11876 | ** changeset, even though the delete took place while the session was disabled. |
| 11877 | ** Or, if one field of a row is updated while a session is disabled, and |
| 11878 | ** another field of the same row is updated while the session is enabled, the |
| 11879 | ** resulting changeset will contain an UPDATE change that updates both fields. |
| 11880 | */ |
| 11881 | SQLITE_API int sqlite3session_changeset( |
| 11882 | sqlite3_session *pSession, /* Session object */ |
| 11883 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11884 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11946,12 +11947,13 @@ | |
| 11946 | ** To clarify, if this function is called and then a changeset constructed |
| 11947 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11948 | ** database zFrom the contents of the two compatible tables would be |
| 11949 | ** identical. |
| 11950 | ** |
| 11951 | ** It an error if database zFrom does not exist or does not contain the |
| 11952 | ** required compatible table. |
| 11953 | ** |
| 11954 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11955 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11956 | ** may be set to point to a buffer containing an English language error |
| 11957 | ** message. It is the responsibility of the caller to free this buffer using |
| @@ -19162,10 +19164,11 @@ | |
| 19162 | unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ |
| 19163 | unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ |
| 19164 | unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */ |
| 19165 | unsigned bNoQuery:1; /* Do not use this index to optimize queries */ |
| 19166 | unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ |
| 19167 | unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ |
| 19168 | unsigned bHasExpr:1; /* Index contains an expression, either a literal |
| 19169 | ** expression, or a reference to a VIRTUAL column */ |
| 19170 | #ifdef SQLITE_ENABLE_STAT4 |
| 19171 | int nSample; /* Number of elements in aSample[] */ |
| @@ -32985,10 +32988,19 @@ | |
| 32985 | va_end(ap); |
| 32986 | zBuf[acc.nChar] = 0; |
| 32987 | return zBuf; |
| 32988 | } |
| 32989 | |
| 32990 | /* |
| 32991 | ** This is the routine that actually formats the sqlite3_log() message. |
| 32992 | ** We house it in a separate routine from sqlite3_log() to avoid using |
| 32993 | ** stack space on small-stack systems when logging is disabled. |
| 32994 | ** |
| @@ -33001,11 +33013,11 @@ | |
| 33001 | ** Care must be taken that any sqlite3_log() calls that occur while the |
| 33002 | ** memory mutex is held do not use these mechanisms. |
| 33003 | */ |
| 33004 | static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ |
| 33005 | StrAccum acc; /* String accumulator */ |
| 33006 | char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ |
| 33007 | |
| 33008 | sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); |
| 33009 | sqlite3_str_vappendf(&acc, zFormat, ap); |
| 33010 | sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, |
| 33011 | sqlite3StrAccumFinish(&acc)); |
| @@ -95714,11 +95726,11 @@ | |
| 95714 | } |
| 95715 | }else{ |
| 95716 | sqlite3VdbeError(p, "%s", pOp->p4.z); |
| 95717 | } |
| 95718 | pcx = (int)(pOp - aOp); |
| 95719 | sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); |
| 95720 | } |
| 95721 | rc = sqlite3VdbeHalt(p); |
| 95722 | assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); |
| 95723 | if( rc==SQLITE_BUSY ){ |
| 95724 | p->rc = SQLITE_BUSY; |
| @@ -97040,11 +97052,11 @@ | |
| 97040 | pOut->u.i = ~sqlite3VdbeIntValue(pIn1); |
| 97041 | } |
| 97042 | break; |
| 97043 | } |
| 97044 | |
| 97045 | /* Opcode: Once P1 P2 * * * |
| 97046 | ** |
| 97047 | ** Fall through to the next instruction the first time this opcode is |
| 97048 | ** encountered on each invocation of the byte-code program. Jump to P2 |
| 97049 | ** on the second and all subsequent encounters during the same invocation. |
| 97050 | ** |
| @@ -97056,10 +97068,16 @@ | |
| 97056 | ** |
| 97057 | ** For subprograms, there is a bitmask in the VdbeFrame that determines |
| 97058 | ** whether or not the jump should be taken. The bitmask is necessary |
| 97059 | ** because the self-altering code trick does not work for recursive |
| 97060 | ** triggers. |
| 97061 | */ |
| 97062 | case OP_Once: { /* jump */ |
| 97063 | u32 iAddr; /* Address of this instruction */ |
| 97064 | assert( p->aOp[0].opcode==OP_Init ); |
| 97065 | if( p->pFrame ){ |
| @@ -103549,12 +103567,12 @@ | |
| 103549 | sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); |
| 103550 | } |
| 103551 | p->rc = rc; |
| 103552 | sqlite3SystemError(db, rc); |
| 103553 | testcase( sqlite3GlobalConfig.xLog!=0 ); |
| 103554 | sqlite3_log(rc, "statement aborts at %d: [%s] %s", |
| 103555 | (int)(pOp - aOp), p->zSql, p->zErrMsg); |
| 103556 | if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); |
| 103557 | if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); |
| 103558 | if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ |
| 103559 | db->flags |= SQLITE_CorruptRdOnly; |
| 103560 | } |
| @@ -114020,15 +114038,16 @@ | |
| 114020 | pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); |
| 114021 | rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); |
| 114022 | sqlite3SelectDelete(pParse->db, pCopy); |
| 114023 | sqlite3DbFree(pParse->db, dest.zAffSdst); |
| 114024 | if( addrBloom ){ |
| 114025 | sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; |
| 114026 | if( dest.iSDParm2==0 ){ |
| 114027 | sqlite3VdbeChangeToNoop(v, addrBloom); |
| 114028 | }else{ |
| 114029 | sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; |
| 114030 | } |
| 114031 | } |
| 114032 | if( rc ){ |
| 114033 | sqlite3KeyInfoUnref(pKeyInfo); |
| 114034 | return; |
| @@ -114471,11 +114490,11 @@ | |
| 114471 | if( destIfFalse==destIfNull ){ |
| 114472 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 114473 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 114474 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 114475 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| 114476 | if( pOp->opcode==OP_Once && pOp->p3>0 ){ |
| 114477 | assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); |
| 114478 | sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, |
| 114479 | rLhs, nVector); VdbeCoverage(v); |
| 114480 | } |
| 114481 | } |
| @@ -124089,11 +124108,11 @@ | |
| 124089 | */ |
| 124090 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 124091 | int i; |
| 124092 | i16 iCol16; |
| 124093 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 124094 | assert( pIdx->nColumn<=SQLITE_MAX_COLUMN ); |
| 124095 | iCol16 = iCol; |
| 124096 | for(i=0; i<pIdx->nColumn; i++){ |
| 124097 | if( iCol16==pIdx->aiColumn[i] ){ |
| 124098 | return i; |
| 124099 | } |
| @@ -127237,10 +127256,11 @@ | |
| 127237 | }else{ |
| 127238 | j = pCExpr->iColumn; |
| 127239 | assert( j<=0x7fff ); |
| 127240 | if( j<0 ){ |
| 127241 | j = pTab->iPKey; |
| 127242 | }else{ |
| 127243 | if( pTab->aCol[j].notNull==0 ){ |
| 127244 | pIndex->uniqNotNull = 0; |
| 127245 | } |
| 127246 | if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ |
| @@ -149706,11 +149726,12 @@ | |
| 149706 | && pE2->iColumn==pColumn->iColumn |
| 149707 | ){ |
| 149708 | return; /* Already present. Return without doing anything. */ |
| 149709 | } |
| 149710 | } |
| 149711 | if( sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ |
| 149712 | pConst->bHasAffBlob = 1; |
| 149713 | } |
| 149714 | |
| 149715 | pConst->nConst++; |
| 149716 | pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, |
| @@ -149781,11 +149802,12 @@ | |
| 149781 | for(i=0; i<pConst->nConst; i++){ |
| 149782 | Expr *pColumn = pConst->apExpr[i*2]; |
| 149783 | if( pColumn==pExpr ) continue; |
| 149784 | if( pColumn->iTable!=pExpr->iTable ) continue; |
| 149785 | if( pColumn->iColumn!=pExpr->iColumn ) continue; |
| 149786 | if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)==SQLITE_AFF_BLOB ){ |
| 149787 | break; |
| 149788 | } |
| 149789 | /* A match is found. Add the EP_FixedCol property */ |
| 149790 | pConst->nChng++; |
| 149791 | ExprClearProperty(pExpr, EP_Leaf); |
| @@ -152965,10 +152987,16 @@ | |
| 152965 | pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, |
| 152966 | p->pEList, p, wctrlFlags, p->nSelectRow); |
| 152967 | if( pWInfo==0 ) goto select_end; |
| 152968 | if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ |
| 152969 | p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); |
| 152970 | } |
| 152971 | if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ |
| 152972 | sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); |
| 152973 | } |
| 152974 | if( sSort.pOrderBy ){ |
| @@ -160131,11 +160159,11 @@ | |
| 160131 | |
| 160132 | |
| 160133 | /* |
| 160134 | ** pX is an expression of the form: (vector) IN (SELECT ...) |
| 160135 | ** In other words, it is a vector IN operator with a SELECT clause on the |
| 160136 | ** LHS. But not all terms in the vector are indexable and the terms might |
| 160137 | ** not be in the correct order for indexing. |
| 160138 | ** |
| 160139 | ** This routine makes a copy of the input pX expression and then adjusts |
| 160140 | ** the vector on the LHS with corresponding changes to the SELECT so that |
| 160141 | ** the vector contains only index terms and those terms are in the correct |
| @@ -167926,11 +167954,11 @@ | |
| 167926 | } |
| 167927 | |
| 167928 | if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 |
| 167929 | && pNew->u.btree.nEq<pProbe->nColumn |
| 167930 | && (pNew->u.btree.nEq<pProbe->nKeyCol || |
| 167931 | pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) |
| 167932 | ){ |
| 167933 | if( pNew->u.btree.nEq>3 ){ |
| 167934 | sqlite3ProgressCheck(pParse); |
| 167935 | } |
| 167936 | whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); |
| @@ -171384,11 +171412,12 @@ | |
| 171384 | wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1); |
| 171385 | if( db->mallocFailed ) goto whereBeginError; |
| 171386 | } |
| 171387 | |
| 171388 | /* TUNING: Assume that a DISTINCT clause on a subquery reduces |
| 171389 | ** the output size by a factor of 8 (LogEst -30). |
| 171390 | */ |
| 171391 | if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ |
| 171392 | WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n", |
| 171393 | pWInfo->nRowOut, pWInfo->nRowOut-30)); |
| 171394 | pWInfo->nRowOut -= 30; |
| @@ -206261,20 +206290,20 @@ | |
| 206261 | case FTS3_MATCHINFO_LCS: |
| 206262 | nVal = pInfo->nCol; |
| 206263 | break; |
| 206264 | |
| 206265 | case FTS3_MATCHINFO_LHITS: |
| 206266 | nVal = pInfo->nCol * pInfo->nPhrase; |
| 206267 | break; |
| 206268 | |
| 206269 | case FTS3_MATCHINFO_LHITS_BM: |
| 206270 | nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32); |
| 206271 | break; |
| 206272 | |
| 206273 | default: |
| 206274 | assert( cArg==FTS3_MATCHINFO_HITS ); |
| 206275 | nVal = pInfo->nCol * pInfo->nPhrase * 3; |
| 206276 | break; |
| 206277 | } |
| 206278 | |
| 206279 | return nVal; |
| 206280 | } |
| @@ -207956,60 +207985,113 @@ | |
| 207956 | ** Growing our own isspace() routine this way is twice as fast as |
| 207957 | ** the library isspace() function, resulting in a 7% overall performance |
| 207958 | ** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). |
| 207959 | */ |
| 207960 | static const char jsonIsSpace[] = { |
| 207961 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, |
| 207962 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207963 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207964 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207965 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207966 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207967 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207968 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207969 | |
| 207970 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207971 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207972 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207973 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207974 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207975 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207976 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207977 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207978 | }; |
| 207979 | #define jsonIsspace(x) (jsonIsSpace[(unsigned char)x]) |
| 207980 | |
| 207981 | /* |
| 207982 | ** The set of all space characters recognized by jsonIsspace(). |
| 207983 | ** Useful as the second argument to strspn(). |
| 207984 | */ |
| 207985 | static const char jsonSpaces[] = "\011\012\015\040"; |
| 207986 | |
| 207987 | /* |
| 207988 | ** Characters that are special to JSON. Control characters, |
| 207989 | ** '"' and '\\' and '\''. Actually, '\'' is not special to |
| 207990 | ** canonical JSON, but it is special in JSON-5, so we include |
| 207991 | ** it in the set of special characters. |
| 207992 | */ |
| 207993 | static const char jsonIsOk[256] = { |
| 207994 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207995 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 207996 | 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, |
| 207997 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 207998 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 207999 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, |
| 208000 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208001 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208002 | |
| 208003 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208004 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208005 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208006 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208007 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208008 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208009 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 208010 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
| 208011 | }; |
| 208012 | |
| 208013 | /* Objects */ |
| 208014 | typedef struct JsonCache JsonCache; |
| 208015 | typedef struct JsonString JsonString; |
| @@ -208150,11 +208232,11 @@ | |
| 208150 | |
| 208151 | /************************************************************************** |
| 208152 | ** Forward references |
| 208153 | **************************************************************************/ |
| 208154 | static void jsonReturnStringAsBlob(JsonString*); |
| 208155 | static int jsonFuncArgMightBeBinary(sqlite3_value *pJson); |
| 208156 | static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*); |
| 208157 | static void jsonReturnParse(sqlite3_context*,JsonParse*); |
| 208158 | static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); |
| 208159 | static void jsonParseFree(JsonParse*); |
| 208160 | static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); |
| @@ -208568,15 +208650,13 @@ | |
| 208568 | jsonAppendString(p, z, n); |
| 208569 | } |
| 208570 | break; |
| 208571 | } |
| 208572 | default: { |
| 208573 | if( jsonFuncArgMightBeBinary(pValue) ){ |
| 208574 | JsonParse px; |
| 208575 | memset(&px, 0, sizeof(px)); |
| 208576 | px.aBlob = (u8*)sqlite3_value_blob(pValue); |
| 208577 | px.nBlob = sqlite3_value_bytes(pValue); |
| 208578 | jsonTranslateBlobToText(&px, 0, p); |
| 208579 | }else if( p->eErr==0 ){ |
| 208580 | sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); |
| 208581 | p->eErr = JSTRING_ERR; |
| 208582 | jsonStringReset(p); |
| @@ -210246,37 +210326,10 @@ | |
| 210246 | } |
| 210247 | } |
| 210248 | return i; |
| 210249 | } |
| 210250 | |
| 210251 | |
| 210252 | /* Return true if the input pJson |
| 210253 | ** |
| 210254 | ** For performance reasons, this routine does not do a detailed check of the |
| 210255 | ** input BLOB to ensure that it is well-formed. Hence, false positives are |
| 210256 | ** possible. False negatives should never occur, however. |
| 210257 | */ |
| 210258 | static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){ |
| 210259 | u32 sz, n; |
| 210260 | const u8 *aBlob; |
| 210261 | int nBlob; |
| 210262 | JsonParse s; |
| 210263 | if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0; |
| 210264 | aBlob = sqlite3_value_blob(pJson); |
| 210265 | nBlob = sqlite3_value_bytes(pJson); |
| 210266 | if( nBlob<1 ) return 0; |
| 210267 | if( NEVER(aBlob==0) || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0; |
| 210268 | memset(&s, 0, sizeof(s)); |
| 210269 | s.aBlob = (u8*)aBlob; |
| 210270 | s.nBlob = nBlob; |
| 210271 | n = jsonbPayloadSize(&s, 0, &sz); |
| 210272 | if( n==0 ) return 0; |
| 210273 | if( sz+n!=(u32)nBlob ) return 0; |
| 210274 | if( (aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0 ) return 0; |
| 210275 | return sz+n==(u32)nBlob; |
| 210276 | } |
| 210277 | |
| 210278 | /* |
| 210279 | ** Given that a JSONB_ARRAY object starts at offset i, return |
| 210280 | ** the number of entries in that array. |
| 210281 | */ |
| 210282 | static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){ |
| @@ -211100,14 +211153,11 @@ | |
| 211100 | pParse->aBlob = aNull; |
| 211101 | pParse->nBlob = 1; |
| 211102 | return 0; |
| 211103 | } |
| 211104 | case SQLITE_BLOB: { |
| 211105 | if( jsonFuncArgMightBeBinary(pArg) ){ |
| 211106 | pParse->aBlob = (u8*)sqlite3_value_blob(pArg); |
| 211107 | pParse->nBlob = sqlite3_value_bytes(pArg); |
| 211108 | }else{ |
| 211109 | sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1); |
| 211110 | return 1; |
| 211111 | } |
| 211112 | break; |
| 211113 | } |
| @@ -211254,31 +211304,50 @@ | |
| 211254 | } |
| 211255 | |
| 211256 | /* |
| 211257 | ** If pArg is a blob that seems like a JSONB blob, then initialize |
| 211258 | ** p to point to that JSONB and return TRUE. If pArg does not seem like |
| 211259 | ** a JSONB blob, then return FALSE; |
| 211260 | ** |
| 211261 | ** This routine is only called if it is already known that pArg is a |
| 211262 | ** blob. The only open question is whether or not the blob appears |
| 211263 | ** to be a JSONB blob. |
| 211264 | */ |
| 211265 | static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){ |
| 211266 | u32 n, sz = 0; |
| 211267 | p->aBlob = (u8*)sqlite3_value_blob(pArg); |
| 211268 | p->nBlob = (u32)sqlite3_value_bytes(pArg); |
| 211269 | if( p->nBlob==0 ){ |
| 211270 | p->aBlob = 0; |
| 211271 | return 0; |
| 211272 | } |
| 211273 | if( NEVER(p->aBlob==0) ){ |
| 211274 | return 0; |
| 211275 | } |
| 211276 | if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT |
| 211277 | && (n = jsonbPayloadSize(p, 0, &sz))>0 |
| 211278 | && sz+n==p->nBlob |
| 211279 | && ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0) |
| 211280 | ){ |
| 211281 | return 1; |
| 211282 | } |
| 211283 | p->aBlob = 0; |
| 211284 | p->nBlob = 0; |
| @@ -212367,25 +212436,21 @@ | |
| 212367 | sqlite3_result_int(ctx, 0); |
| 212368 | #endif |
| 212369 | return; |
| 212370 | } |
| 212371 | case SQLITE_BLOB: { |
| 212372 | if( jsonFuncArgMightBeBinary(argv[0]) ){ |
| 212373 | if( flags & 0x04 ){ |
| 212374 | /* Superficial checking only - accomplished by the |
| 212375 | ** jsonFuncArgMightBeBinary() call above. */ |
| 212376 | res = 1; |
| 212377 | }else if( flags & 0x08 ){ |
| 212378 | /* Strict checking. Check by translating BLOB->TEXT->BLOB. If |
| 212379 | ** no errors occur, call that a "strict check". */ |
| 212380 | JsonParse px; |
| 212381 | u32 iErr; |
| 212382 | memset(&px, 0, sizeof(px)); |
| 212383 | px.aBlob = (u8*)sqlite3_value_blob(argv[0]); |
| 212384 | px.nBlob = sqlite3_value_bytes(argv[0]); |
| 212385 | iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1); |
| 212386 | res = iErr==0; |
| 212387 | } |
| 212388 | break; |
| 212389 | } |
| 212390 | /* Fall through into interpreting the input as text. See note |
| 212391 | ** above at tag-20240123-a. */ |
| @@ -212439,13 +212504,11 @@ | |
| 212439 | |
| 212440 | assert( argc==1 ); |
| 212441 | UNUSED_PARAMETER(argc); |
| 212442 | memset(&s, 0, sizeof(s)); |
| 212443 | s.db = sqlite3_context_db_handle(ctx); |
| 212444 | if( jsonFuncArgMightBeBinary(argv[0]) ){ |
| 212445 | s.aBlob = (u8*)sqlite3_value_blob(argv[0]); |
| 212446 | s.nBlob = sqlite3_value_bytes(argv[0]); |
| 212447 | iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1); |
| 212448 | }else{ |
| 212449 | s.zJson = (char*)sqlite3_value_text(argv[0]); |
| 212450 | if( s.zJson==0 ) return; /* NULL input or OOM */ |
| 212451 | s.nJson = sqlite3_value_bytes(argv[0]); |
| @@ -213126,13 +213189,12 @@ | |
| 213126 | jsonEachCursorReset(p); |
| 213127 | if( idxNum==0 ) return SQLITE_OK; |
| 213128 | memset(&p->sParse, 0, sizeof(p->sParse)); |
| 213129 | p->sParse.nJPRef = 1; |
| 213130 | p->sParse.db = p->db; |
| 213131 | if( jsonFuncArgMightBeBinary(argv[0]) ){ |
| 213132 | p->sParse.nBlob = sqlite3_value_bytes(argv[0]); |
| 213133 | p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]); |
| 213134 | }else{ |
| 213135 | p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); |
| 213136 | p->sParse.nJson = sqlite3_value_bytes(argv[0]); |
| 213137 | if( p->sParse.zJson==0 ){ |
| 213138 | p->i = p->iEnd = 0; |
| @@ -229245,10 +229307,12 @@ | |
| 229245 | int rc = SQLITE_OK; |
| 229246 | |
| 229247 | if( pTab->nCol==0 ){ |
| 229248 | u8 *abPK; |
| 229249 | assert( pTab->azCol==0 || pTab->abPK==0 ); |
| 229250 | rc = sessionTableInfo(pSession, db, zDb, |
| 229251 | pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol, |
| 229252 | &pTab->azDflt, &pTab->aiIdx, &abPK, |
| 229253 | ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) |
| 229254 | ); |
| @@ -230265,21 +230329,47 @@ | |
| 230265 | |
| 230266 | /* Check the table schemas match */ |
| 230267 | if( rc==SQLITE_OK ){ |
| 230268 | int bHasPk = 0; |
| 230269 | int bMismatch = 0; |
| 230270 | int nCol; /* Columns in zFrom.zTbl */ |
| 230271 | int bRowid = 0; |
| 230272 | u8 *abPK; |
| 230273 | const char **azCol = 0; |
| 230274 | rc = sessionTableInfo(0, db, zFrom, zTbl, |
| 230275 | &nCol, 0, 0, &azCol, 0, 0, &abPK, |
| 230276 | pSession->bImplicitPK ? &bRowid : 0 |
| 230277 | ); |
| 230278 | if( rc==SQLITE_OK ){ |
| 230279 | if( pTo->nCol!=nCol ){ |
| 230280 | bMismatch = 1; |
| 230281 | }else{ |
| 230282 | int i; |
| 230283 | for(i=0; i<nCol; i++){ |
| 230284 | if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1; |
| 230285 | if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1; |
| @@ -257173,11 +257263,11 @@ | |
| 257173 | int nArg, /* Number of args */ |
| 257174 | sqlite3_value **apUnused /* Function arguments */ |
| 257175 | ){ |
| 257176 | assert( nArg==0 ); |
| 257177 | UNUSED_PARAM2(nArg, apUnused); |
| 257178 | sqlite3_result_text(pCtx, "fts5: 2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6", -1, SQLITE_TRANSIENT); |
| 257179 | } |
| 257180 | |
| 257181 | /* |
| 257182 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 257183 | ** |
| 257184 |
| --- extsrc/sqlite3.c | |
| +++ extsrc/sqlite3.c | |
| @@ -16,11 +16,11 @@ | |
| 16 | ** if you want a wrapper to interface SQLite with your choice of programming |
| 17 | ** language. The code for the "sqlite3" command-line shell is also in a |
| 18 | ** separate file. This file contains only code for the core SQLite library. |
| 19 | ** |
| 20 | ** The content in this amalgamation comes from Fossil check-in |
| 21 | ** 20abf1ec107f942e4527901685d61283c9c2 with changes in files: |
| 22 | ** |
| 23 | ** |
| 24 | */ |
| 25 | #ifndef SQLITE_AMALGAMATION |
| 26 | #define SQLITE_CORE 1 |
| @@ -465,11 +465,11 @@ | |
| 465 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 466 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 467 | */ |
| 468 | #define SQLITE_VERSION "3.50.0" |
| 469 | #define SQLITE_VERSION_NUMBER 3050000 |
| 470 | #define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -11872,13 +11872,14 @@ | |
| 11872 | ** This may appear to have some counter-intuitive effects if a single row |
| 11873 | ** is written to more than once during a session. For example, if a row |
| 11874 | ** is inserted while a session object is enabled, then later deleted while |
| 11875 | ** the same session object is disabled, no INSERT record will appear in the |
| 11876 | ** changeset, even though the delete took place while the session was disabled. |
| 11877 | ** Or, if one field of a row is updated while a session is enabled, and |
| 11878 | ** then another field of the same row is updated while the session is disabled, |
| 11879 | ** the resulting changeset will contain an UPDATE change that updates both |
| 11880 | ** fields. |
| 11881 | */ |
| 11882 | SQLITE_API int sqlite3session_changeset( |
| 11883 | sqlite3_session *pSession, /* Session object */ |
| 11884 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11885 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11946,12 +11947,13 @@ | |
| 11947 | ** To clarify, if this function is called and then a changeset constructed |
| 11948 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11949 | ** database zFrom the contents of the two compatible tables would be |
| 11950 | ** identical. |
| 11951 | ** |
| 11952 | ** Unless the call to this function is a no-op as described above, it is an |
| 11953 | ** error if database zFrom does not exist or does not contain the required |
| 11954 | ** compatible table. |
| 11955 | ** |
| 11956 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11957 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11958 | ** may be set to point to a buffer containing an English language error |
| 11959 | ** message. It is the responsibility of the caller to free this buffer using |
| @@ -19162,10 +19164,11 @@ | |
| 19164 | unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ |
| 19165 | unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ |
| 19166 | unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */ |
| 19167 | unsigned bNoQuery:1; /* Do not use this index to optimize queries */ |
| 19168 | unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ |
| 19169 | unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */ |
| 19170 | unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ |
| 19171 | unsigned bHasExpr:1; /* Index contains an expression, either a literal |
| 19172 | ** expression, or a reference to a VIRTUAL column */ |
| 19173 | #ifdef SQLITE_ENABLE_STAT4 |
| 19174 | int nSample; /* Number of elements in aSample[] */ |
| @@ -32985,10 +32988,19 @@ | |
| 32988 | va_end(ap); |
| 32989 | zBuf[acc.nChar] = 0; |
| 32990 | return zBuf; |
| 32991 | } |
| 32992 | |
| 32993 | /* Maximum size of an sqlite3_log() message. */ |
| 32994 | #if defined(SQLITE_MAX_LOG_MESSAGE) |
| 32995 | /* Leave the definition as supplied */ |
| 32996 | #elif SQLITE_PRINT_BUF_SIZE*10>10000 |
| 32997 | # define SQLITE_MAX_LOG_MESSAGE 10000 |
| 32998 | #else |
| 32999 | # define SQLITE_MAX_LOG_MESSAGE (SQLITE_PRINT_BUF_SIZE*10) |
| 33000 | #endif |
| 33001 | |
| 33002 | /* |
| 33003 | ** This is the routine that actually formats the sqlite3_log() message. |
| 33004 | ** We house it in a separate routine from sqlite3_log() to avoid using |
| 33005 | ** stack space on small-stack systems when logging is disabled. |
| 33006 | ** |
| @@ -33001,11 +33013,11 @@ | |
| 33013 | ** Care must be taken that any sqlite3_log() calls that occur while the |
| 33014 | ** memory mutex is held do not use these mechanisms. |
| 33015 | */ |
| 33016 | static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ |
| 33017 | StrAccum acc; /* String accumulator */ |
| 33018 | char zMsg[SQLITE_MAX_LOG_MESSAGE]; /* Complete log message */ |
| 33019 | |
| 33020 | sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); |
| 33021 | sqlite3_str_vappendf(&acc, zFormat, ap); |
| 33022 | sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, |
| 33023 | sqlite3StrAccumFinish(&acc)); |
| @@ -95714,11 +95726,11 @@ | |
| 95726 | } |
| 95727 | }else{ |
| 95728 | sqlite3VdbeError(p, "%s", pOp->p4.z); |
| 95729 | } |
| 95730 | pcx = (int)(pOp - aOp); |
| 95731 | sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql); |
| 95732 | } |
| 95733 | rc = sqlite3VdbeHalt(p); |
| 95734 | assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); |
| 95735 | if( rc==SQLITE_BUSY ){ |
| 95736 | p->rc = SQLITE_BUSY; |
| @@ -97040,11 +97052,11 @@ | |
| 97052 | pOut->u.i = ~sqlite3VdbeIntValue(pIn1); |
| 97053 | } |
| 97054 | break; |
| 97055 | } |
| 97056 | |
| 97057 | /* Opcode: Once P1 P2 P3 * * |
| 97058 | ** |
| 97059 | ** Fall through to the next instruction the first time this opcode is |
| 97060 | ** encountered on each invocation of the byte-code program. Jump to P2 |
| 97061 | ** on the second and all subsequent encounters during the same invocation. |
| 97062 | ** |
| @@ -97056,10 +97068,16 @@ | |
| 97068 | ** |
| 97069 | ** For subprograms, there is a bitmask in the VdbeFrame that determines |
| 97070 | ** whether or not the jump should be taken. The bitmask is necessary |
| 97071 | ** because the self-altering code trick does not work for recursive |
| 97072 | ** triggers. |
| 97073 | ** |
| 97074 | ** The P3 operand is not used directly by this opcode. However P3 is |
| 97075 | ** used by the code generator as follows: If this opcode is the start |
| 97076 | ** of a subroutine and that subroutine uses a Bloom filter, then P3 will |
| 97077 | ** be the register that holds that Bloom filter. See tag-202407032019 |
| 97078 | ** in the source code for implementation details. |
| 97079 | */ |
| 97080 | case OP_Once: { /* jump */ |
| 97081 | u32 iAddr; /* Address of this instruction */ |
| 97082 | assert( p->aOp[0].opcode==OP_Init ); |
| 97083 | if( p->pFrame ){ |
| @@ -103549,12 +103567,12 @@ | |
| 103567 | sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); |
| 103568 | } |
| 103569 | p->rc = rc; |
| 103570 | sqlite3SystemError(db, rc); |
| 103571 | testcase( sqlite3GlobalConfig.xLog!=0 ); |
| 103572 | sqlite3_log(rc, "statement aborts at %d: %s; [%s]", |
| 103573 | (int)(pOp - aOp), p->zErrMsg, p->zSql); |
| 103574 | if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); |
| 103575 | if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); |
| 103576 | if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ |
| 103577 | db->flags |= SQLITE_CorruptRdOnly; |
| 103578 | } |
| @@ -114020,15 +114038,16 @@ | |
| 114038 | pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); |
| 114039 | rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); |
| 114040 | sqlite3SelectDelete(pParse->db, pCopy); |
| 114041 | sqlite3DbFree(pParse->db, dest.zAffSdst); |
| 114042 | if( addrBloom ){ |
| 114043 | /* Remember that location of the Bloom filter in the P3 operand |
| 114044 | ** of the OP_Once that began this subroutine. tag-202407032019 */ |
| 114045 | sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; |
| 114046 | if( dest.iSDParm2==0 ){ |
| 114047 | /* If the Bloom filter won't actually be used, keep it small */ |
| 114048 | sqlite3VdbeGetOp(v, addrBloom)->p1 = 10; |
| 114049 | } |
| 114050 | } |
| 114051 | if( rc ){ |
| 114052 | sqlite3KeyInfoUnref(pKeyInfo); |
| 114053 | return; |
| @@ -114471,11 +114490,11 @@ | |
| 114490 | if( destIfFalse==destIfNull ){ |
| 114491 | /* Combine Step 3 and Step 5 into a single opcode */ |
| 114492 | if( ExprHasProperty(pExpr, EP_Subrtn) ){ |
| 114493 | const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); |
| 114494 | assert( pOp->opcode==OP_Once || pParse->nErr ); |
| 114495 | if( pOp->opcode==OP_Once && pOp->p3>0 ){ /* tag-202407032019 */ |
| 114496 | assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) ); |
| 114497 | sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, |
| 114498 | rLhs, nVector); VdbeCoverage(v); |
| 114499 | } |
| 114500 | } |
| @@ -124089,11 +124108,11 @@ | |
| 124108 | */ |
| 124109 | SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ |
| 124110 | int i; |
| 124111 | i16 iCol16; |
| 124112 | assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); |
| 124113 | assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 ); |
| 124114 | iCol16 = iCol; |
| 124115 | for(i=0; i<pIdx->nColumn; i++){ |
| 124116 | if( iCol16==pIdx->aiColumn[i] ){ |
| 124117 | return i; |
| 124118 | } |
| @@ -127237,10 +127256,11 @@ | |
| 127256 | }else{ |
| 127257 | j = pCExpr->iColumn; |
| 127258 | assert( j<=0x7fff ); |
| 127259 | if( j<0 ){ |
| 127260 | j = pTab->iPKey; |
| 127261 | pIndex->bIdxRowid = 1; |
| 127262 | }else{ |
| 127263 | if( pTab->aCol[j].notNull==0 ){ |
| 127264 | pIndex->uniqNotNull = 0; |
| 127265 | } |
| 127266 | if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ |
| @@ -149706,11 +149726,12 @@ | |
| 149726 | && pE2->iColumn==pColumn->iColumn |
| 149727 | ){ |
| 149728 | return; /* Already present. Return without doing anything. */ |
| 149729 | } |
| 149730 | } |
| 149731 | assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB ); |
| 149732 | if( sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){ |
| 149733 | pConst->bHasAffBlob = 1; |
| 149734 | } |
| 149735 | |
| 149736 | pConst->nConst++; |
| 149737 | pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, |
| @@ -149781,11 +149802,12 @@ | |
| 149802 | for(i=0; i<pConst->nConst; i++){ |
| 149803 | Expr *pColumn = pConst->apExpr[i*2]; |
| 149804 | if( pColumn==pExpr ) continue; |
| 149805 | if( pColumn->iTable!=pExpr->iTable ) continue; |
| 149806 | if( pColumn->iColumn!=pExpr->iColumn ) continue; |
| 149807 | assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB ); |
| 149808 | if( bIgnoreAffBlob && sqlite3ExprAffinity(pColumn)<=SQLITE_AFF_BLOB ){ |
| 149809 | break; |
| 149810 | } |
| 149811 | /* A match is found. Add the EP_FixedCol property */ |
| 149812 | pConst->nChng++; |
| 149813 | ExprClearProperty(pExpr, EP_Leaf); |
| @@ -152965,10 +152987,16 @@ | |
| 152987 | pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, |
| 152988 | p->pEList, p, wctrlFlags, p->nSelectRow); |
| 152989 | if( pWInfo==0 ) goto select_end; |
| 152990 | if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ |
| 152991 | p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); |
| 152992 | if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){ |
| 152993 | /* TUNING: For a UNION CTE, because UNION is implies DISTINCT, |
| 152994 | ** reduce the estimated output row count by 8 (LogEst 30). |
| 152995 | ** Search for tag-20250414a to see other cases */ |
| 152996 | p->nSelectRow -= 30; |
| 152997 | } |
| 152998 | } |
| 152999 | if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ |
| 153000 | sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); |
| 153001 | } |
| 153002 | if( sSort.pOrderBy ){ |
| @@ -160131,11 +160159,11 @@ | |
| 160159 | |
| 160160 | |
| 160161 | /* |
| 160162 | ** pX is an expression of the form: (vector) IN (SELECT ...) |
| 160163 | ** In other words, it is a vector IN operator with a SELECT clause on the |
| 160164 | ** RHS. But not all terms in the vector are indexable and the terms might |
| 160165 | ** not be in the correct order for indexing. |
| 160166 | ** |
| 160167 | ** This routine makes a copy of the input pX expression and then adjusts |
| 160168 | ** the vector on the LHS with corresponding changes to the SELECT so that |
| 160169 | ** the vector contains only index terms and those terms are in the correct |
| @@ -167926,11 +167954,11 @@ | |
| 167954 | } |
| 167955 | |
| 167956 | if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 |
| 167957 | && pNew->u.btree.nEq<pProbe->nColumn |
| 167958 | && (pNew->u.btree.nEq<pProbe->nKeyCol || |
| 167959 | (pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid)) |
| 167960 | ){ |
| 167961 | if( pNew->u.btree.nEq>3 ){ |
| 167962 | sqlite3ProgressCheck(pParse); |
| 167963 | } |
| 167964 | whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); |
| @@ -171384,11 +171412,12 @@ | |
| 171412 | wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1); |
| 171413 | if( db->mallocFailed ) goto whereBeginError; |
| 171414 | } |
| 171415 | |
| 171416 | /* TUNING: Assume that a DISTINCT clause on a subquery reduces |
| 171417 | ** the output size by a factor of 8 (LogEst -30). Search for |
| 171418 | ** tag-20250414a to see other cases. |
| 171419 | */ |
| 171420 | if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ |
| 171421 | WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n", |
| 171422 | pWInfo->nRowOut, pWInfo->nRowOut-30)); |
| 171423 | pWInfo->nRowOut -= 30; |
| @@ -206261,20 +206290,20 @@ | |
| 206290 | case FTS3_MATCHINFO_LCS: |
| 206291 | nVal = pInfo->nCol; |
| 206292 | break; |
| 206293 | |
| 206294 | case FTS3_MATCHINFO_LHITS: |
| 206295 | nVal = (size_t)pInfo->nCol * pInfo->nPhrase; |
| 206296 | break; |
| 206297 | |
| 206298 | case FTS3_MATCHINFO_LHITS_BM: |
| 206299 | nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32); |
| 206300 | break; |
| 206301 | |
| 206302 | default: |
| 206303 | assert( cArg==FTS3_MATCHINFO_HITS ); |
| 206304 | nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3; |
| 206305 | break; |
| 206306 | } |
| 206307 | |
| 206308 | return nVal; |
| 206309 | } |
| @@ -207956,60 +207985,113 @@ | |
| 207985 | ** Growing our own isspace() routine this way is twice as fast as |
| 207986 | ** the library isspace() function, resulting in a 7% overall performance |
| 207987 | ** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). |
| 207988 | */ |
| 207989 | static const char jsonIsSpace[] = { |
| 207990 | #ifdef SQLITE_ASCII |
| 207991 | /*0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
| 207992 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 */ |
| 207993 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ |
| 207994 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ |
| 207995 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ |
| 207996 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ |
| 207997 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ |
| 207998 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ |
| 207999 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ |
| 208000 | |
| 208001 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ |
| 208002 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ |
| 208003 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ |
| 208004 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ |
| 208005 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ |
| 208006 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ |
| 208007 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ |
| 208008 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ |
| 208009 | #endif |
| 208010 | #ifdef SQLITE_EBCDIC |
| 208011 | /*0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
| 208012 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0 */ |
| 208013 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ |
| 208014 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ |
| 208015 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ |
| 208016 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ |
| 208017 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ |
| 208018 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ |
| 208019 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ |
| 208020 | |
| 208021 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ |
| 208022 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ |
| 208023 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ |
| 208024 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ |
| 208025 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ |
| 208026 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ |
| 208027 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ |
| 208028 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ |
| 208029 | #endif |
| 208030 | |
| 208031 | }; |
| 208032 | #define jsonIsspace(x) (jsonIsSpace[(unsigned char)x]) |
| 208033 | |
| 208034 | /* |
| 208035 | ** The set of all space characters recognized by jsonIsspace(). |
| 208036 | ** Useful as the second argument to strspn(). |
| 208037 | */ |
| 208038 | #ifdef SQLITE_ASCII |
| 208039 | static const char jsonSpaces[] = "\011\012\015\040"; |
| 208040 | #endif |
| 208041 | #ifdef SQLITE_EBCDIC |
| 208042 | static const char jsonSpaces[] = "\005\045\015\100"; |
| 208043 | #endif |
| 208044 | |
| 208045 | |
| 208046 | /* |
| 208047 | ** Characters that are special to JSON. Control characters, |
| 208048 | ** '"' and '\\' and '\''. Actually, '\'' is not special to |
| 208049 | ** canonical JSON, but it is special in JSON-5, so we include |
| 208050 | ** it in the set of special characters. |
| 208051 | */ |
| 208052 | static const char jsonIsOk[256] = { |
| 208053 | #ifdef SQLITE_ASCII |
| 208054 | /*0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
| 208055 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ |
| 208056 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ |
| 208057 | 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 2 */ |
| 208058 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 3 */ |
| 208059 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ |
| 208060 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 5 */ |
| 208061 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ |
| 208062 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ |
| 208063 | |
| 208064 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ |
| 208065 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ |
| 208066 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ |
| 208067 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ |
| 208068 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ |
| 208069 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ |
| 208070 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ |
| 208071 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ |
| 208072 | #endif |
| 208073 | #ifdef SQLITE_EBCDIC |
| 208074 | /*0 1 2 3 4 5 6 7 8 9 a b c d e f */ |
| 208075 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ |
| 208076 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ |
| 208077 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ |
| 208078 | 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 3 */ |
| 208079 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ |
| 208080 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5 */ |
| 208081 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ |
| 208082 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, /* 7 */ |
| 208083 | |
| 208084 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ |
| 208085 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ |
| 208086 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ |
| 208087 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ |
| 208088 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ |
| 208089 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ |
| 208090 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ |
| 208091 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ |
| 208092 | #endif |
| 208093 | }; |
| 208094 | |
| 208095 | /* Objects */ |
| 208096 | typedef struct JsonCache JsonCache; |
| 208097 | typedef struct JsonString JsonString; |
| @@ -208150,11 +208232,11 @@ | |
| 208232 | |
| 208233 | /************************************************************************** |
| 208234 | ** Forward references |
| 208235 | **************************************************************************/ |
| 208236 | static void jsonReturnStringAsBlob(JsonString*); |
| 208237 | static int jsonArgIsJsonb(sqlite3_value *pJson, JsonParse *p); |
| 208238 | static u32 jsonTranslateBlobToText(const JsonParse*,u32,JsonString*); |
| 208239 | static void jsonReturnParse(sqlite3_context*,JsonParse*); |
| 208240 | static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); |
| 208241 | static void jsonParseFree(JsonParse*); |
| 208242 | static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); |
| @@ -208568,15 +208650,13 @@ | |
| 208650 | jsonAppendString(p, z, n); |
| 208651 | } |
| 208652 | break; |
| 208653 | } |
| 208654 | default: { |
| 208655 | JsonParse px; |
| 208656 | memset(&px, 0, sizeof(px)); |
| 208657 | if( jsonArgIsJsonb(pValue, &px) ){ |
| 208658 | jsonTranslateBlobToText(&px, 0, p); |
| 208659 | }else if( p->eErr==0 ){ |
| 208660 | sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); |
| 208661 | p->eErr = JSTRING_ERR; |
| 208662 | jsonStringReset(p); |
| @@ -210246,37 +210326,10 @@ | |
| 210326 | } |
| 210327 | } |
| 210328 | return i; |
| 210329 | } |
| 210330 | |
| 210331 | /* |
| 210332 | ** Given that a JSONB_ARRAY object starts at offset i, return |
| 210333 | ** the number of entries in that array. |
| 210334 | */ |
| 210335 | static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){ |
| @@ -211100,14 +211153,11 @@ | |
| 211153 | pParse->aBlob = aNull; |
| 211154 | pParse->nBlob = 1; |
| 211155 | return 0; |
| 211156 | } |
| 211157 | case SQLITE_BLOB: { |
| 211158 | if( !jsonArgIsJsonb(pArg, pParse) ){ |
| 211159 | sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1); |
| 211160 | return 1; |
| 211161 | } |
| 211162 | break; |
| 211163 | } |
| @@ -211254,31 +211304,50 @@ | |
| 211304 | } |
| 211305 | |
| 211306 | /* |
| 211307 | ** If pArg is a blob that seems like a JSONB blob, then initialize |
| 211308 | ** p to point to that JSONB and return TRUE. If pArg does not seem like |
| 211309 | ** a JSONB blob, then return FALSE. |
| 211310 | ** |
| 211311 | ** For small BLOBs (having no more than 7 bytes of payload) a full |
| 211312 | ** validity check is done. So for small BLOBs this routine only returns |
| 211313 | ** true if the value is guaranteed to be a valid JSONB. For larger BLOBs |
| 211314 | ** (8 byte or more of payload) only the size of the outermost element is |
| 211315 | ** checked to verify that the BLOB is superficially valid JSONB. |
| 211316 | ** |
| 211317 | ** A full JSONB validation is done on smaller BLOBs because those BLOBs might |
| 211318 | ** also be text JSON that has been incorrectly cast into a BLOB. |
| 211319 | ** (See tag-20240123-a and https://sqlite.org/forum/forumpost/012136abd5) |
| 211320 | ** If the BLOB is 9 bytes are larger, then it is not possible for the |
| 211321 | ** superficial size check done here to pass if the input is really text |
| 211322 | ** JSON so we do not need to look deeper in that case. |
| 211323 | ** |
| 211324 | ** Why we only need to do full JSONB validation for smaller BLOBs: |
| 211325 | ** |
| 211326 | ** The first byte of valid JSON text must be one of: '{', '[', '"', ' ', '\n', |
| 211327 | ** '\r', '\t', '-', or a digit '0' through '9'. Of these, only a subset |
| 211328 | ** can also be the first byte of JSONB: '{', '[', and digits '3' |
| 211329 | ** through '9'. In every one of those cases, the payload size is 7 bytes |
| 211330 | ** or less. So if we do full JSONB validation for every BLOB where the |
| 211331 | ** payload is less than 7 bytes, we will never get a false positive for |
| 211332 | ** JSONB on an input that is really text JSON. |
| 211333 | */ |
| 211334 | static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){ |
| 211335 | u32 n, sz = 0; |
| 211336 | u8 c; |
| 211337 | if( sqlite3_value_type(pArg)!=SQLITE_BLOB ) return 0; |
| 211338 | p->aBlob = (u8*)sqlite3_value_blob(pArg); |
| 211339 | p->nBlob = (u32)sqlite3_value_bytes(pArg); |
| 211340 | if( p->nBlob>0 |
| 211341 | && ALWAYS(p->aBlob!=0) |
| 211342 | && ((c = p->aBlob[0]) & 0x0f)<=JSONB_OBJECT |
| 211343 | && (n = jsonbPayloadSize(p, 0, &sz))>0 |
| 211344 | && sz+n==p->nBlob |
| 211345 | && ((c & 0x0f)>JSONB_FALSE || sz==0) |
| 211346 | && (sz>7 |
| 211347 | || (c!=0x7b && c!=0x5b && !sqlite3Isdigit(c)) |
| 211348 | || jsonbValidityCheck(p, 0, p->nBlob, 1)==0) |
| 211349 | ){ |
| 211350 | return 1; |
| 211351 | } |
| 211352 | p->aBlob = 0; |
| 211353 | p->nBlob = 0; |
| @@ -212367,25 +212436,21 @@ | |
| 212436 | sqlite3_result_int(ctx, 0); |
| 212437 | #endif |
| 212438 | return; |
| 212439 | } |
| 212440 | case SQLITE_BLOB: { |
| 212441 | JsonParse py; |
| 212442 | memset(&py, 0, sizeof(py)); |
| 212443 | if( jsonArgIsJsonb(argv[0], &py) ){ |
| 212444 | if( flags & 0x04 ){ |
| 212445 | /* Superficial checking only - accomplished by the |
| 212446 | ** jsonArgIsJsonb() call above. */ |
| 212447 | res = 1; |
| 212448 | }else if( flags & 0x08 ){ |
| 212449 | /* Strict checking. Check by translating BLOB->TEXT->BLOB. If |
| 212450 | ** no errors occur, call that a "strict check". */ |
| 212451 | res = 0==jsonbValidityCheck(&py, 0, py.nBlob, 1); |
| 212452 | } |
| 212453 | break; |
| 212454 | } |
| 212455 | /* Fall through into interpreting the input as text. See note |
| 212456 | ** above at tag-20240123-a. */ |
| @@ -212439,13 +212504,11 @@ | |
| 212504 | |
| 212505 | assert( argc==1 ); |
| 212506 | UNUSED_PARAMETER(argc); |
| 212507 | memset(&s, 0, sizeof(s)); |
| 212508 | s.db = sqlite3_context_db_handle(ctx); |
| 212509 | if( jsonArgIsJsonb(argv[0], &s) ){ |
| 212510 | iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1); |
| 212511 | }else{ |
| 212512 | s.zJson = (char*)sqlite3_value_text(argv[0]); |
| 212513 | if( s.zJson==0 ) return; /* NULL input or OOM */ |
| 212514 | s.nJson = sqlite3_value_bytes(argv[0]); |
| @@ -213126,13 +213189,12 @@ | |
| 213189 | jsonEachCursorReset(p); |
| 213190 | if( idxNum==0 ) return SQLITE_OK; |
| 213191 | memset(&p->sParse, 0, sizeof(p->sParse)); |
| 213192 | p->sParse.nJPRef = 1; |
| 213193 | p->sParse.db = p->db; |
| 213194 | if( jsonArgIsJsonb(argv[0], &p->sParse) ){ |
| 213195 | /* We have JSONB */ |
| 213196 | }else{ |
| 213197 | p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); |
| 213198 | p->sParse.nJson = sqlite3_value_bytes(argv[0]); |
| 213199 | if( p->sParse.zJson==0 ){ |
| 213200 | p->i = p->iEnd = 0; |
| @@ -229245,10 +229307,12 @@ | |
| 229307 | int rc = SQLITE_OK; |
| 229308 | |
| 229309 | if( pTab->nCol==0 ){ |
| 229310 | u8 *abPK; |
| 229311 | assert( pTab->azCol==0 || pTab->abPK==0 ); |
| 229312 | sqlite3_free(pTab->azCol); |
| 229313 | pTab->abPK = 0; |
| 229314 | rc = sessionTableInfo(pSession, db, zDb, |
| 229315 | pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol, |
| 229316 | &pTab->azDflt, &pTab->aiIdx, &abPK, |
| 229317 | ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) |
| 229318 | ); |
| @@ -230265,21 +230329,47 @@ | |
| 230329 | |
| 230330 | /* Check the table schemas match */ |
| 230331 | if( rc==SQLITE_OK ){ |
| 230332 | int bHasPk = 0; |
| 230333 | int bMismatch = 0; |
| 230334 | int nCol = 0; /* Columns in zFrom.zTbl */ |
| 230335 | int bRowid = 0; |
| 230336 | u8 *abPK = 0; |
| 230337 | const char **azCol = 0; |
| 230338 | char *zDbExists = 0; |
| 230339 | |
| 230340 | /* Check that database zFrom is attached. */ |
| 230341 | zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom); |
| 230342 | if( zDbExists==0 ){ |
| 230343 | rc = SQLITE_NOMEM; |
| 230344 | }else{ |
| 230345 | sqlite3_stmt *pDbExists = 0; |
| 230346 | rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0); |
| 230347 | if( rc==SQLITE_ERROR ){ |
| 230348 | rc = SQLITE_OK; |
| 230349 | nCol = -1; |
| 230350 | } |
| 230351 | sqlite3_finalize(pDbExists); |
| 230352 | sqlite3_free(zDbExists); |
| 230353 | } |
| 230354 | |
| 230355 | if( rc==SQLITE_OK && nCol==0 ){ |
| 230356 | rc = sessionTableInfo(0, db, zFrom, zTbl, |
| 230357 | &nCol, 0, 0, &azCol, 0, 0, &abPK, |
| 230358 | pSession->bImplicitPK ? &bRowid : 0 |
| 230359 | ); |
| 230360 | } |
| 230361 | if( rc==SQLITE_OK ){ |
| 230362 | if( pTo->nCol!=nCol ){ |
| 230363 | if( nCol<=0 ){ |
| 230364 | rc = SQLITE_SCHEMA; |
| 230365 | if( pzErrMsg ){ |
| 230366 | *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl); |
| 230367 | } |
| 230368 | }else{ |
| 230369 | bMismatch = 1; |
| 230370 | } |
| 230371 | }else{ |
| 230372 | int i; |
| 230373 | for(i=0; i<nCol; i++){ |
| 230374 | if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1; |
| 230375 | if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1; |
| @@ -257173,11 +257263,11 @@ | |
| 257263 | int nArg, /* Number of args */ |
| 257264 | sqlite3_value **apUnused /* Function arguments */ |
| 257265 | ){ |
| 257266 | assert( nArg==0 ); |
| 257267 | UNUSED_PARAM2(nArg, apUnused); |
| 257268 | sqlite3_result_text(pCtx, "fts5: 2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849", -1, SQLITE_TRANSIENT); |
| 257269 | } |
| 257270 | |
| 257271 | /* |
| 257272 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 257273 | ** |
| 257274 |
+8
-6
| --- extsrc/sqlite3.h | ||
| +++ extsrc/sqlite3.h | ||
| @@ -146,11 +146,11 @@ | ||
| 146 | 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | 148 | */ |
| 149 | 149 | #define SQLITE_VERSION "3.50.0" |
| 150 | 150 | #define SQLITE_VERSION_NUMBER 3050000 |
| 151 | -#define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6" | |
| 151 | +#define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849" | |
| 152 | 152 | |
| 153 | 153 | /* |
| 154 | 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | 156 | ** |
| @@ -11553,13 +11553,14 @@ | ||
| 11553 | 11553 | ** This may appear to have some counter-intuitive effects if a single row |
| 11554 | 11554 | ** is written to more than once during a session. For example, if a row |
| 11555 | 11555 | ** is inserted while a session object is enabled, then later deleted while |
| 11556 | 11556 | ** the same session object is disabled, no INSERT record will appear in the |
| 11557 | 11557 | ** changeset, even though the delete took place while the session was disabled. |
| 11558 | -** Or, if one field of a row is updated while a session is disabled, and | |
| 11559 | -** another field of the same row is updated while the session is enabled, the | |
| 11560 | -** resulting changeset will contain an UPDATE change that updates both fields. | |
| 11558 | +** Or, if one field of a row is updated while a session is enabled, and | |
| 11559 | +** then another field of the same row is updated while the session is disabled, | |
| 11560 | +** the resulting changeset will contain an UPDATE change that updates both | |
| 11561 | +** fields. | |
| 11561 | 11562 | */ |
| 11562 | 11563 | SQLITE_API int sqlite3session_changeset( |
| 11563 | 11564 | sqlite3_session *pSession, /* Session object */ |
| 11564 | 11565 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11565 | 11566 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11627,12 +11628,13 @@ | ||
| 11627 | 11628 | ** To clarify, if this function is called and then a changeset constructed |
| 11628 | 11629 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11629 | 11630 | ** database zFrom the contents of the two compatible tables would be |
| 11630 | 11631 | ** identical. |
| 11631 | 11632 | ** |
| 11632 | -** It an error if database zFrom does not exist or does not contain the | |
| 11633 | -** required compatible table. | |
| 11633 | +** Unless the call to this function is a no-op as described above, it is an | |
| 11634 | +** error if database zFrom does not exist or does not contain the required | |
| 11635 | +** compatible table. | |
| 11634 | 11636 | ** |
| 11635 | 11637 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11636 | 11638 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11637 | 11639 | ** may be set to point to a buffer containing an English language error |
| 11638 | 11640 | ** message. It is the responsibility of the caller to free this buffer using |
| 11639 | 11641 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -146,11 +146,11 @@ | |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.50.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3050000 |
| 151 | #define SQLITE_SOURCE_ID "2025-04-10 10:18:07 20acd630b91609725794ce84f9eda01d5f3c898407f0948264830851d25ccaa6" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| @@ -11553,13 +11553,14 @@ | |
| 11553 | ** This may appear to have some counter-intuitive effects if a single row |
| 11554 | ** is written to more than once during a session. For example, if a row |
| 11555 | ** is inserted while a session object is enabled, then later deleted while |
| 11556 | ** the same session object is disabled, no INSERT record will appear in the |
| 11557 | ** changeset, even though the delete took place while the session was disabled. |
| 11558 | ** Or, if one field of a row is updated while a session is disabled, and |
| 11559 | ** another field of the same row is updated while the session is enabled, the |
| 11560 | ** resulting changeset will contain an UPDATE change that updates both fields. |
| 11561 | */ |
| 11562 | SQLITE_API int sqlite3session_changeset( |
| 11563 | sqlite3_session *pSession, /* Session object */ |
| 11564 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11565 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11627,12 +11628,13 @@ | |
| 11627 | ** To clarify, if this function is called and then a changeset constructed |
| 11628 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11629 | ** database zFrom the contents of the two compatible tables would be |
| 11630 | ** identical. |
| 11631 | ** |
| 11632 | ** It an error if database zFrom does not exist or does not contain the |
| 11633 | ** required compatible table. |
| 11634 | ** |
| 11635 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11636 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11637 | ** may be set to point to a buffer containing an English language error |
| 11638 | ** message. It is the responsibility of the caller to free this buffer using |
| 11639 |
| --- extsrc/sqlite3.h | |
| +++ extsrc/sqlite3.h | |
| @@ -146,11 +146,11 @@ | |
| 146 | ** [sqlite3_libversion_number()], [sqlite3_sourceid()], |
| 147 | ** [sqlite_version()] and [sqlite_source_id()]. |
| 148 | */ |
| 149 | #define SQLITE_VERSION "3.50.0" |
| 150 | #define SQLITE_VERSION_NUMBER 3050000 |
| 151 | #define SQLITE_SOURCE_ID "2025-04-30 14:37:00 20abf1ec107f942e4527901685d61283c9c2fe7bcefad63dbf5c6cbf050da849" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| @@ -11553,13 +11553,14 @@ | |
| 11553 | ** This may appear to have some counter-intuitive effects if a single row |
| 11554 | ** is written to more than once during a session. For example, if a row |
| 11555 | ** is inserted while a session object is enabled, then later deleted while |
| 11556 | ** the same session object is disabled, no INSERT record will appear in the |
| 11557 | ** changeset, even though the delete took place while the session was disabled. |
| 11558 | ** Or, if one field of a row is updated while a session is enabled, and |
| 11559 | ** then another field of the same row is updated while the session is disabled, |
| 11560 | ** the resulting changeset will contain an UPDATE change that updates both |
| 11561 | ** fields. |
| 11562 | */ |
| 11563 | SQLITE_API int sqlite3session_changeset( |
| 11564 | sqlite3_session *pSession, /* Session object */ |
| 11565 | int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ |
| 11566 | void **ppChangeset /* OUT: Buffer containing changeset */ |
| @@ -11627,12 +11628,13 @@ | |
| 11628 | ** To clarify, if this function is called and then a changeset constructed |
| 11629 | ** using [sqlite3session_changeset()], then after applying that changeset to |
| 11630 | ** database zFrom the contents of the two compatible tables would be |
| 11631 | ** identical. |
| 11632 | ** |
| 11633 | ** Unless the call to this function is a no-op as described above, it is an |
| 11634 | ** error if database zFrom does not exist or does not contain the required |
| 11635 | ** compatible table. |
| 11636 | ** |
| 11637 | ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite |
| 11638 | ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg |
| 11639 | ** may be set to point to a buffer containing an English language error |
| 11640 | ** message. It is the responsibility of the caller to free this buffer using |
| 11641 |
+1
-1
| --- skins/default/header.txt | ||
| +++ skins/default/header.txt | ||
| @@ -28,11 +28,11 @@ | ||
| 28 | 28 | return $logourl |
| 29 | 29 | } |
| 30 | 30 | set logourl [getLogoUrl $baseurl] |
| 31 | 31 | </th1> |
| 32 | 32 | <a href="$logourl"> |
| 33 | - <img src="$logo_image_url" border="0" alt="$project_name"> | |
| 33 | + <img src="$logo_image_url" border="0" alt="$<project_name>"> | |
| 34 | 34 | </a> |
| 35 | 35 | </div> |
| 36 | 36 | <div class="title"> |
| 37 | 37 | <h1>$<project_name></h1> |
| 38 | 38 | <span class="page-title">$<title></span> |
| 39 | 39 |
| --- skins/default/header.txt | |
| +++ skins/default/header.txt | |
| @@ -28,11 +28,11 @@ | |
| 28 | return $logourl |
| 29 | } |
| 30 | set logourl [getLogoUrl $baseurl] |
| 31 | </th1> |
| 32 | <a href="$logourl"> |
| 33 | <img src="$logo_image_url" border="0" alt="$project_name"> |
| 34 | </a> |
| 35 | </div> |
| 36 | <div class="title"> |
| 37 | <h1>$<project_name></h1> |
| 38 | <span class="page-title">$<title></span> |
| 39 |
| --- skins/default/header.txt | |
| +++ skins/default/header.txt | |
| @@ -28,11 +28,11 @@ | |
| 28 | return $logourl |
| 29 | } |
| 30 | set logourl [getLogoUrl $baseurl] |
| 31 | </th1> |
| 32 | <a href="$logourl"> |
| 33 | <img src="$logo_image_url" border="0" alt="$<project_name>"> |
| 34 | </a> |
| 35 | </div> |
| 36 | <div class="title"> |
| 37 | <h1>$<project_name></h1> |
| 38 | <span class="page-title">$<title></span> |
| 39 |
+1
-1
| --- skins/eagle/header.txt | ||
| +++ skins/eagle/header.txt | ||
| @@ -65,11 +65,11 @@ | ||
| 65 | 65 | # Link logo to the top of the current repo |
| 66 | 66 | set logourl $baseurl |
| 67 | 67 | } |
| 68 | 68 | </th1> |
| 69 | 69 | <a href="$logourl"> |
| 70 | - <img src="$logo_image_url" border="0" alt="$project_name"> | |
| 70 | + <img src="$logo_image_url" border="0" alt="$<project_name>"> | |
| 71 | 71 | </a> |
| 72 | 72 | </div> |
| 73 | 73 | <div class="title">$<title></div> |
| 74 | 74 | <div class="status"><nobr><th1> |
| 75 | 75 | if {[info exists login]} { |
| 76 | 76 |
| --- skins/eagle/header.txt | |
| +++ skins/eagle/header.txt | |
| @@ -65,11 +65,11 @@ | |
| 65 | # Link logo to the top of the current repo |
| 66 | set logourl $baseurl |
| 67 | } |
| 68 | </th1> |
| 69 | <a href="$logourl"> |
| 70 | <img src="$logo_image_url" border="0" alt="$project_name"> |
| 71 | </a> |
| 72 | </div> |
| 73 | <div class="title">$<title></div> |
| 74 | <div class="status"><nobr><th1> |
| 75 | if {[info exists login]} { |
| 76 |
| --- skins/eagle/header.txt | |
| +++ skins/eagle/header.txt | |
| @@ -65,11 +65,11 @@ | |
| 65 | # Link logo to the top of the current repo |
| 66 | set logourl $baseurl |
| 67 | } |
| 68 | </th1> |
| 69 | <a href="$logourl"> |
| 70 | <img src="$logo_image_url" border="0" alt="$<project_name>"> |
| 71 | </a> |
| 72 | </div> |
| 73 | <div class="title">$<title></div> |
| 74 | <div class="status"><nobr><th1> |
| 75 | if {[info exists login]} { |
| 76 |
+1
-1
| --- skins/original/header.txt | ||
| +++ skins/original/header.txt | ||
| @@ -59,11 +59,11 @@ | ||
| 59 | 59 | return $logourl |
| 60 | 60 | } |
| 61 | 61 | set logourl [getLogoUrl $baseurl] |
| 62 | 62 | </th1> |
| 63 | 63 | <a href="$logourl"> |
| 64 | - <img src="$logo_image_url" border="0" alt="$project_name"> | |
| 64 | + <img src="$logo_image_url" border="0" alt="$<project_name>"> | |
| 65 | 65 | </a> |
| 66 | 66 | </div> |
| 67 | 67 | <div class="title">$<title></div> |
| 68 | 68 | <div class="status"><nobr><th1> |
| 69 | 69 | if {[info exists login]} { |
| 70 | 70 |
| --- skins/original/header.txt | |
| +++ skins/original/header.txt | |
| @@ -59,11 +59,11 @@ | |
| 59 | return $logourl |
| 60 | } |
| 61 | set logourl [getLogoUrl $baseurl] |
| 62 | </th1> |
| 63 | <a href="$logourl"> |
| 64 | <img src="$logo_image_url" border="0" alt="$project_name"> |
| 65 | </a> |
| 66 | </div> |
| 67 | <div class="title">$<title></div> |
| 68 | <div class="status"><nobr><th1> |
| 69 | if {[info exists login]} { |
| 70 |
| --- skins/original/header.txt | |
| +++ skins/original/header.txt | |
| @@ -59,11 +59,11 @@ | |
| 59 | return $logourl |
| 60 | } |
| 61 | set logourl [getLogoUrl $baseurl] |
| 62 | </th1> |
| 63 | <a href="$logourl"> |
| 64 | <img src="$logo_image_url" border="0" alt="$<project_name>"> |
| 65 | </a> |
| 66 | </div> |
| 67 | <div class="title">$<title></div> |
| 68 | <div class="status"><nobr><th1> |
| 69 | if {[info exists login]} { |
| 70 |
+1
-1
| --- skins/xekri/header.txt | ||
| +++ skins/xekri/header.txt | ||
| @@ -65,11 +65,11 @@ | ||
| 65 | 65 | # Link logo to the top of the current repo |
| 66 | 66 | set logourl $baseurl |
| 67 | 67 | } |
| 68 | 68 | </th1> |
| 69 | 69 | <a href="$logourl"> |
| 70 | - <img src="$logo_image_url" border="0" alt="$project_name"> | |
| 70 | + <img src="$logo_image_url" border="0" alt="$<project_name>"> | |
| 71 | 71 | </a> |
| 72 | 72 | </div> |
| 73 | 73 | <div class="title">$<title></div> |
| 74 | 74 | <div class="status"><nobr> |
| 75 | 75 | <th1> |
| 76 | 76 |
| --- skins/xekri/header.txt | |
| +++ skins/xekri/header.txt | |
| @@ -65,11 +65,11 @@ | |
| 65 | # Link logo to the top of the current repo |
| 66 | set logourl $baseurl |
| 67 | } |
| 68 | </th1> |
| 69 | <a href="$logourl"> |
| 70 | <img src="$logo_image_url" border="0" alt="$project_name"> |
| 71 | </a> |
| 72 | </div> |
| 73 | <div class="title">$<title></div> |
| 74 | <div class="status"><nobr> |
| 75 | <th1> |
| 76 |
| --- skins/xekri/header.txt | |
| +++ skins/xekri/header.txt | |
| @@ -65,11 +65,11 @@ | |
| 65 | # Link logo to the top of the current repo |
| 66 | set logourl $baseurl |
| 67 | } |
| 68 | </th1> |
| 69 | <a href="$logourl"> |
| 70 | <img src="$logo_image_url" border="0" alt="$<project_name>"> |
| 71 | </a> |
| 72 | </div> |
| 73 | <div class="title">$<title></div> |
| 74 | <div class="status"><nobr> |
| 75 | <th1> |
| 76 |
+41
-20
| --- src/alerts.c | ||
| +++ src/alerts.c | ||
| @@ -655,11 +655,10 @@ | ||
| 655 | 655 | emailerError(p, "Could not start SMTP session: %s", |
| 656 | 656 | p->pSmtp ? p->pSmtp->zErr : "reason unknown"); |
| 657 | 657 | }else if( p->zDest[0]=='d' ){ |
| 658 | 658 | smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out); |
| 659 | 659 | } |
| 660 | - smtp_client_startup(p->pSmtp); | |
| 661 | 660 | } |
| 662 | 661 | } |
| 663 | 662 | return p; |
| 664 | 663 | } |
| 665 | 664 | |
| @@ -985,13 +984,10 @@ | ||
| 985 | 984 | blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); |
| 986 | 985 | }else{ |
| 987 | 986 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 988 | 987 | } |
| 989 | 988 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 990 | - if( p->zListId && p->zListId[0] ){ | |
| 991 | - blob_appendf(pOut, "List-Id: %s\r\n", p->zListId); | |
| 992 | - } | |
| 993 | 989 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 994 | 990 | /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is |
| 995 | 991 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 996 | 992 | ** and $(from) is the domain part of the email-self setting. */ |
| 997 | 993 | sqlite3_randomness(sizeof(r1), &r1); |
| @@ -1033,13 +1029,21 @@ | ||
| 1033 | 1029 | blob_write_to_file(&all, zFile); |
| 1034 | 1030 | fossil_free(zFile); |
| 1035 | 1031 | }else if( p->pSmtp ){ |
| 1036 | 1032 | char **azTo = 0; |
| 1037 | 1033 | int nTo = 0; |
| 1034 | + SmtpSession *pSmtp = p->pSmtp; | |
| 1038 | 1035 | email_header_to(pHdr, &nTo, &azTo); |
| 1039 | - if( nTo>0 ){ | |
| 1040 | - smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all)); | |
| 1036 | + if( nTo>0 && !pSmtp->bFatal ){ | |
| 1037 | + smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); | |
| 1038 | + if( pSmtp->zErr && !pSmtp->bFatal ){ | |
| 1039 | + smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); | |
| 1040 | + } | |
| 1041 | + if( pSmtp->zErr ){ | |
| 1042 | + fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry", | |
| 1043 | + pSmtp->zErr); | |
| 1044 | + } | |
| 1041 | 1045 | email_header_to_free(nTo, azTo); |
| 1042 | 1046 | } |
| 1043 | 1047 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 1044 | 1048 | char **azTo = 0; |
| 1045 | 1049 | int nTo = 0; |
| @@ -3208,18 +3212,21 @@ | ||
| 3208 | 3212 | Blob fhdr, fbody; |
| 3209 | 3213 | blob_init(&fhdr, 0, 0); |
| 3210 | 3214 | blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); |
| 3211 | 3215 | blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
| 3212 | 3216 | blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3213 | - blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", | |
| 3214 | - zUrl, zCode); | |
| 3215 | - blob_appendf(&fhdr, | |
| 3216 | - "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); | |
| 3217 | - blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", | |
| 3218 | - zUrl, zCode); | |
| 3219 | - /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n", | |
| 3220 | - ** zUrl, zCode); */ | |
| 3217 | + if( pSender->zListId && pSender->zListId[0] ){ | |
| 3218 | + blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId); | |
| 3219 | + blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", | |
| 3220 | + zUrl, zCode); | |
| 3221 | + blob_appendf(&fhdr, | |
| 3222 | + "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); | |
| 3223 | + blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", | |
| 3224 | + zUrl, zCode); | |
| 3225 | + /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n", | |
| 3226 | + ** zUrl, zCode); */ | |
| 3227 | + } | |
| 3221 | 3228 | alert_send(pSender,&fhdr,&fbody,p->zFromName); |
| 3222 | 3229 | nSent++; |
| 3223 | 3230 | blob_reset(&fhdr); |
| 3224 | 3231 | blob_reset(&fbody); |
| 3225 | 3232 | }else{ |
| @@ -3238,15 +3245,19 @@ | ||
| 3238 | 3245 | blob_append(&body, "\n", 1); |
| 3239 | 3246 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3240 | 3247 | } |
| 3241 | 3248 | } |
| 3242 | 3249 | if( nHit==0 ) continue; |
| 3243 | - blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", | |
| 3244 | - zUrl, zCode); | |
| 3245 | - blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); | |
| 3246 | - blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", | |
| 3247 | - zUrl, zCode); | |
| 3250 | + if( pSender->zListId && pSender->zListId[0] ){ | |
| 3251 | + blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); | |
| 3252 | + blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", | |
| 3253 | + zUrl, zCode); | |
| 3254 | + blob_appendf(&hdr, | |
| 3255 | + "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); | |
| 3256 | + blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", | |
| 3257 | + zUrl, zCode); | |
| 3258 | + } | |
| 3248 | 3259 | alert_send(pSender,&hdr,&body,0); |
| 3249 | 3260 | nSent++; |
| 3250 | 3261 | blob_truncate(&hdr, 0); |
| 3251 | 3262 | blob_truncate(&body, 0); |
| 3252 | 3263 | } |
| @@ -3288,18 +3299,28 @@ | ||
| 3288 | 3299 | " AND length(sdigest)>0", |
| 3289 | 3300 | iNewWarn, iOldWarn |
| 3290 | 3301 | ); |
| 3291 | 3302 | while( db_step(&q)==SQLITE_ROW ){ |
| 3292 | 3303 | Blob hdr, body; |
| 3304 | + const char *zCode = db_column_text(&q,0); | |
| 3293 | 3305 | blob_init(&hdr, 0, 0); |
| 3294 | 3306 | blob_init(&body, 0, 0); |
| 3295 | 3307 | alert_renewal_msg(&hdr, &body, |
| 3296 | - db_column_text(&q,0), | |
| 3308 | + zCode, | |
| 3297 | 3309 | db_column_int(&q,1), |
| 3298 | 3310 | db_column_text(&q,2), |
| 3299 | 3311 | db_column_text(&q,3), |
| 3300 | 3312 | zRepoName, zUrl); |
| 3313 | + if( pSender->zListId && pSender->zListId[0] ){ | |
| 3314 | + blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); | |
| 3315 | + blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", | |
| 3316 | + zUrl, zCode); | |
| 3317 | + blob_appendf(&hdr, | |
| 3318 | + "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); | |
| 3319 | + blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", | |
| 3320 | + zUrl, zCode); | |
| 3321 | + } | |
| 3301 | 3322 | alert_send(pSender,&hdr,&body,0); |
| 3302 | 3323 | blob_reset(&hdr); |
| 3303 | 3324 | blob_reset(&body); |
| 3304 | 3325 | } |
| 3305 | 3326 | db_finalize(&q); |
| 3306 | 3327 |
| --- src/alerts.c | |
| +++ src/alerts.c | |
| @@ -655,11 +655,10 @@ | |
| 655 | emailerError(p, "Could not start SMTP session: %s", |
| 656 | p->pSmtp ? p->pSmtp->zErr : "reason unknown"); |
| 657 | }else if( p->zDest[0]=='d' ){ |
| 658 | smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out); |
| 659 | } |
| 660 | smtp_client_startup(p->pSmtp); |
| 661 | } |
| 662 | } |
| 663 | return p; |
| 664 | } |
| 665 | |
| @@ -985,13 +984,10 @@ | |
| 985 | blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); |
| 986 | }else{ |
| 987 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 988 | } |
| 989 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 990 | if( p->zListId && p->zListId[0] ){ |
| 991 | blob_appendf(pOut, "List-Id: %s\r\n", p->zListId); |
| 992 | } |
| 993 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 994 | /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is |
| 995 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 996 | ** and $(from) is the domain part of the email-self setting. */ |
| 997 | sqlite3_randomness(sizeof(r1), &r1); |
| @@ -1033,13 +1029,21 @@ | |
| 1033 | blob_write_to_file(&all, zFile); |
| 1034 | fossil_free(zFile); |
| 1035 | }else if( p->pSmtp ){ |
| 1036 | char **azTo = 0; |
| 1037 | int nTo = 0; |
| 1038 | email_header_to(pHdr, &nTo, &azTo); |
| 1039 | if( nTo>0 ){ |
| 1040 | smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all)); |
| 1041 | email_header_to_free(nTo, azTo); |
| 1042 | } |
| 1043 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 1044 | char **azTo = 0; |
| 1045 | int nTo = 0; |
| @@ -3208,18 +3212,21 @@ | |
| 3208 | Blob fhdr, fbody; |
| 3209 | blob_init(&fhdr, 0, 0); |
| 3210 | blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); |
| 3211 | blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
| 3212 | blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3213 | blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
| 3214 | zUrl, zCode); |
| 3215 | blob_appendf(&fhdr, |
| 3216 | "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
| 3217 | blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", |
| 3218 | zUrl, zCode); |
| 3219 | /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n", |
| 3220 | ** zUrl, zCode); */ |
| 3221 | alert_send(pSender,&fhdr,&fbody,p->zFromName); |
| 3222 | nSent++; |
| 3223 | blob_reset(&fhdr); |
| 3224 | blob_reset(&fbody); |
| 3225 | }else{ |
| @@ -3238,15 +3245,19 @@ | |
| 3238 | blob_append(&body, "\n", 1); |
| 3239 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3240 | } |
| 3241 | } |
| 3242 | if( nHit==0 ) continue; |
| 3243 | blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
| 3244 | zUrl, zCode); |
| 3245 | blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
| 3246 | blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
| 3247 | zUrl, zCode); |
| 3248 | alert_send(pSender,&hdr,&body,0); |
| 3249 | nSent++; |
| 3250 | blob_truncate(&hdr, 0); |
| 3251 | blob_truncate(&body, 0); |
| 3252 | } |
| @@ -3288,18 +3299,28 @@ | |
| 3288 | " AND length(sdigest)>0", |
| 3289 | iNewWarn, iOldWarn |
| 3290 | ); |
| 3291 | while( db_step(&q)==SQLITE_ROW ){ |
| 3292 | Blob hdr, body; |
| 3293 | blob_init(&hdr, 0, 0); |
| 3294 | blob_init(&body, 0, 0); |
| 3295 | alert_renewal_msg(&hdr, &body, |
| 3296 | db_column_text(&q,0), |
| 3297 | db_column_int(&q,1), |
| 3298 | db_column_text(&q,2), |
| 3299 | db_column_text(&q,3), |
| 3300 | zRepoName, zUrl); |
| 3301 | alert_send(pSender,&hdr,&body,0); |
| 3302 | blob_reset(&hdr); |
| 3303 | blob_reset(&body); |
| 3304 | } |
| 3305 | db_finalize(&q); |
| 3306 |
| --- src/alerts.c | |
| +++ src/alerts.c | |
| @@ -655,11 +655,10 @@ | |
| 655 | emailerError(p, "Could not start SMTP session: %s", |
| 656 | p->pSmtp ? p->pSmtp->zErr : "reason unknown"); |
| 657 | }else if( p->zDest[0]=='d' ){ |
| 658 | smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out); |
| 659 | } |
| 660 | } |
| 661 | } |
| 662 | return p; |
| 663 | } |
| 664 | |
| @@ -985,13 +984,10 @@ | |
| 984 | blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); |
| 985 | }else{ |
| 986 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 987 | } |
| 988 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 989 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 990 | /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is |
| 991 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 992 | ** and $(from) is the domain part of the email-self setting. */ |
| 993 | sqlite3_randomness(sizeof(r1), &r1); |
| @@ -1033,13 +1029,21 @@ | |
| 1029 | blob_write_to_file(&all, zFile); |
| 1030 | fossil_free(zFile); |
| 1031 | }else if( p->pSmtp ){ |
| 1032 | char **azTo = 0; |
| 1033 | int nTo = 0; |
| 1034 | SmtpSession *pSmtp = p->pSmtp; |
| 1035 | email_header_to(pHdr, &nTo, &azTo); |
| 1036 | if( nTo>0 && !pSmtp->bFatal ){ |
| 1037 | smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); |
| 1038 | if( pSmtp->zErr && !pSmtp->bFatal ){ |
| 1039 | smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all)); |
| 1040 | } |
| 1041 | if( pSmtp->zErr ){ |
| 1042 | fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry", |
| 1043 | pSmtp->zErr); |
| 1044 | } |
| 1045 | email_header_to_free(nTo, azTo); |
| 1046 | } |
| 1047 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 1048 | char **azTo = 0; |
| 1049 | int nTo = 0; |
| @@ -3208,18 +3212,21 @@ | |
| 3212 | Blob fhdr, fbody; |
| 3213 | blob_init(&fhdr, 0, 0); |
| 3214 | blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); |
| 3215 | blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); |
| 3216 | blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3217 | if( pSender->zListId && pSender->zListId[0] ){ |
| 3218 | blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId); |
| 3219 | blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
| 3220 | zUrl, zCode); |
| 3221 | blob_appendf(&fhdr, |
| 3222 | "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
| 3223 | blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", |
| 3224 | zUrl, zCode); |
| 3225 | /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n", |
| 3226 | ** zUrl, zCode); */ |
| 3227 | } |
| 3228 | alert_send(pSender,&fhdr,&fbody,p->zFromName); |
| 3229 | nSent++; |
| 3230 | blob_reset(&fhdr); |
| 3231 | blob_reset(&fbody); |
| 3232 | }else{ |
| @@ -3238,15 +3245,19 @@ | |
| 3245 | blob_append(&body, "\n", 1); |
| 3246 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 3247 | } |
| 3248 | } |
| 3249 | if( nHit==0 ) continue; |
| 3250 | if( pSender->zListId && pSender->zListId[0] ){ |
| 3251 | blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); |
| 3252 | blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
| 3253 | zUrl, zCode); |
| 3254 | blob_appendf(&hdr, |
| 3255 | "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
| 3256 | blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
| 3257 | zUrl, zCode); |
| 3258 | } |
| 3259 | alert_send(pSender,&hdr,&body,0); |
| 3260 | nSent++; |
| 3261 | blob_truncate(&hdr, 0); |
| 3262 | blob_truncate(&body, 0); |
| 3263 | } |
| @@ -3288,18 +3299,28 @@ | |
| 3299 | " AND length(sdigest)>0", |
| 3300 | iNewWarn, iOldWarn |
| 3301 | ); |
| 3302 | while( db_step(&q)==SQLITE_ROW ){ |
| 3303 | Blob hdr, body; |
| 3304 | const char *zCode = db_column_text(&q,0); |
| 3305 | blob_init(&hdr, 0, 0); |
| 3306 | blob_init(&body, 0, 0); |
| 3307 | alert_renewal_msg(&hdr, &body, |
| 3308 | zCode, |
| 3309 | db_column_int(&q,1), |
| 3310 | db_column_text(&q,2), |
| 3311 | db_column_text(&q,3), |
| 3312 | zRepoName, zUrl); |
| 3313 | if( pSender->zListId && pSender->zListId[0] ){ |
| 3314 | blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId); |
| 3315 | blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n", |
| 3316 | zUrl, zCode); |
| 3317 | blob_appendf(&hdr, |
| 3318 | "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); |
| 3319 | blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n", |
| 3320 | zUrl, zCode); |
| 3321 | } |
| 3322 | alert_send(pSender,&hdr,&body,0); |
| 3323 | blob_reset(&hdr); |
| 3324 | blob_reset(&body); |
| 3325 | } |
| 3326 | db_finalize(&q); |
| 3327 |
+2
-2
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -205,11 +205,11 @@ | ||
| 205 | 205 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 206 | 206 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | 207 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | 208 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | 209 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | - Th_Store("current_checkin", zCI); | |
| 210 | + Th_StoreUnsafe("current_checkin", zCI); | |
| 211 | 211 | }else{ |
| 212 | 212 | zCI = 0; |
| 213 | 213 | } |
| 214 | 214 | } |
| 215 | 215 | |
| @@ -771,11 +771,11 @@ | ||
| 771 | 771 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | 772 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | 773 | " FROM event WHERE objid=%d", rid); |
| 774 | 774 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 775 | 775 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 776 | - Th_Store("current_checkin", zCI); | |
| 776 | + Th_StoreUnsafe("current_checkin", zCI); | |
| 777 | 777 | }else{ |
| 778 | 778 | zCI = 0; |
| 779 | 779 | } |
| 780 | 780 | } |
| 781 | 781 | if( zCI==0 ){ |
| 782 | 782 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -205,11 +205,11 @@ | |
| 205 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 206 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | Th_Store("current_checkin", zCI); |
| 211 | }else{ |
| 212 | zCI = 0; |
| 213 | } |
| 214 | } |
| 215 | |
| @@ -771,11 +771,11 @@ | |
| 771 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | " FROM event WHERE objid=%d", rid); |
| 774 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 775 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 776 | Th_Store("current_checkin", zCI); |
| 777 | }else{ |
| 778 | zCI = 0; |
| 779 | } |
| 780 | } |
| 781 | if( zCI==0 ){ |
| 782 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -205,11 +205,11 @@ | |
| 205 | linkTip = rid != symbolic_name_to_rid("tip", "ci"); |
| 206 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 207 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); |
| 208 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 209 | if( bDocDir ) zCI = mprintf("%S", zUuid); |
| 210 | Th_StoreUnsafe("current_checkin", zCI); |
| 211 | }else{ |
| 212 | zCI = 0; |
| 213 | } |
| 214 | } |
| 215 | |
| @@ -771,11 +771,11 @@ | |
| 771 | rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); |
| 772 | zNow = db_text("", "SELECT datetime(mtime,toLocal())" |
| 773 | " FROM event WHERE objid=%d", rid); |
| 774 | isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); |
| 775 | isBranchCI = branch_includes_uuid(zCI, zUuid); |
| 776 | Th_StoreUnsafe("current_checkin", zCI); |
| 777 | }else{ |
| 778 | zCI = 0; |
| 779 | } |
| 780 | } |
| 781 | if( zCI==0 ){ |
| 782 |
+252
-140
| --- src/cgi.c | ||
| +++ src/cgi.c | ||
| @@ -2498,11 +2498,10 @@ | ||
| 2498 | 2498 | fossil_free(zToFree); |
| 2499 | 2499 | fgetc(g.httpIn); /* Read past the "," separating header from content */ |
| 2500 | 2500 | cgi_init(); |
| 2501 | 2501 | } |
| 2502 | 2502 | |
| 2503 | - | |
| 2504 | 2503 | #if INTERFACE |
| 2505 | 2504 | /* |
| 2506 | 2505 | ** Bitmap values for the flags parameter to cgi_http_server(). |
| 2507 | 2506 | */ |
| 2508 | 2507 | #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ |
| @@ -2541,121 +2540,222 @@ | ||
| 2541 | 2540 | ){ |
| 2542 | 2541 | #if defined(_WIN32) |
| 2543 | 2542 | /* Use win32_http_server() instead */ |
| 2544 | 2543 | fossil_exit(1); |
| 2545 | 2544 | #else |
| 2546 | - int listener = -1; /* The server socket */ | |
| 2547 | - int connection; /* A socket for each individual connection */ | |
| 2545 | + int listen4 = -1; /* Main socket; IPv4 or unix-domain */ | |
| 2546 | + int listen6 = -1; /* Aux socket for corresponding IPv6 */ | |
| 2547 | + int mxListen = -1; /* Maximum of listen4 and listen6 */ | |
| 2548 | + int connection; /* An incoming connection */ | |
| 2548 | 2549 | int nRequest = 0; /* Number of requests handled so far */ |
| 2549 | 2550 | fd_set readfds; /* Set of file descriptors for select() */ |
| 2550 | 2551 | socklen_t lenaddr; /* Length of the inaddr structure */ |
| 2551 | 2552 | int child; /* PID of the child process */ |
| 2552 | 2553 | int nchildren = 0; /* Number of child processes */ |
| 2553 | 2554 | struct timeval delay; /* How long to wait inside select() */ |
| 2554 | - struct sockaddr_in6 inaddr; /* The socket address */ | |
| 2555 | + struct sockaddr_in6 inaddr6; /* Address for IPv6 */ | |
| 2556 | + struct sockaddr_in inaddr4; /* Address for IPv4 */ | |
| 2555 | 2557 | struct sockaddr_un uxaddr; /* The address for unix-domain sockets */ |
| 2556 | 2558 | int opt = 1; /* setsockopt flag */ |
| 2557 | 2559 | int rc; /* Result code from system calls */ |
| 2558 | 2560 | int iPort = mnPort; /* Port to try to use */ |
| 2559 | - | |
| 2560 | - while( iPort<=mxPort ){ | |
| 2561 | - if( flags & HTTP_SERVER_UNIXSOCKET ){ | |
| 2562 | - /* Initialize a Unix socket named g.zSockName */ | |
| 2563 | - assert( g.zSockName!=0 ); | |
| 2564 | - memset(&uxaddr, 0, sizeof(uxaddr)); | |
| 2565 | - if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){ | |
| 2566 | - fossil_fatal("name of unix socket too big: %s\nmax size: %d\n", | |
| 2567 | - g.zSockName, (int)sizeof(uxaddr.sun_path)); | |
| 2568 | - } | |
| 2569 | - if( file_isdir(g.zSockName, ExtFILE)!=0 ){ | |
| 2570 | - if( !file_issocket(g.zSockName) ){ | |
| 2571 | - fossil_fatal("cannot name socket \"%s\" because another object" | |
| 2572 | - " with that name already exists", g.zSockName); | |
| 2573 | - }else{ | |
| 2574 | - unlink(g.zSockName); | |
| 2575 | - } | |
| 2576 | - } | |
| 2577 | - uxaddr.sun_family = AF_UNIX; | |
| 2578 | - strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1); | |
| 2579 | - listener = socket(AF_UNIX, SOCK_STREAM, 0); | |
| 2580 | - if( listener<0 ){ | |
| 2581 | - fossil_fatal("unable to create a unix socket named %s", | |
| 2582 | - g.zSockName); | |
| 2583 | - } | |
| 2584 | - /* Set the access permission for the new socket. Default to 0660. | |
| 2585 | - ** But use an alternative specified by --socket-mode if available. | |
| 2586 | - ** Do this before bind() to avoid a race condition. */ | |
| 2587 | - if( g.zSockMode ){ | |
| 2588 | - file_set_mode(g.zSockName, listener, g.zSockMode, 0); | |
| 2589 | - }else{ | |
| 2590 | - file_set_mode(g.zSockName, listener, "0660", 1); | |
| 2591 | - } | |
| 2592 | - }else{ | |
| 2593 | - /* Initialize a TCP/IP socket on port iPort */ | |
| 2594 | - memset(&inaddr, 0, sizeof(inaddr)); | |
| 2595 | - inaddr.sin6_family = AF_INET6; | |
| 2596 | - if( zIpAddr ){ | |
| 2597 | - if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){ | |
| 2598 | - fossil_fatal("not a valid IP address: %s", zIpAddr); | |
| 2599 | - } | |
| 2600 | - }else if( flags & HTTP_SERVER_LOCALHOST ){ | |
| 2601 | - inaddr.sin6_addr = in6addr_loopback; | |
| 2602 | - }else{ | |
| 2603 | - inaddr.sin6_addr = in6addr_any; | |
| 2604 | - } | |
| 2605 | - inaddr.sin6_port = htons(iPort); | |
| 2606 | - listener = socket(AF_INET6, SOCK_STREAM, 0); | |
| 2607 | - if( listener<0 ){ | |
| 2608 | - iPort++; | |
| 2609 | - continue; | |
| 2610 | - } | |
| 2611 | - } | |
| 2612 | - | |
| 2613 | - /* if we can't terminate nicely, at least allow the socket to be reused */ | |
| 2614 | - setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); | |
| 2615 | - | |
| 2616 | - if( flags & HTTP_SERVER_UNIXSOCKET ){ | |
| 2617 | - rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr)); | |
| 2618 | - /* Set the owner of the socket if requested by --socket-owner. This | |
| 2619 | - ** must wait until after bind(), after the filesystem object has been | |
| 2620 | - ** created. See https://lkml.org/lkml/2004/11/1/84 and | |
| 2621 | - ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */ | |
| 2622 | - if( g.zSockOwner ){ | |
| 2623 | - file_set_owner(g.zSockName, listener, g.zSockOwner); | |
| 2624 | - } | |
| 2625 | - }else{ | |
| 2626 | - rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr)); | |
| 2627 | - } | |
| 2628 | - if( rc<0 ){ | |
| 2629 | - close(listener); | |
| 2630 | - iPort++; | |
| 2631 | - continue; | |
| 2632 | - } | |
| 2633 | - break; | |
| 2634 | - } | |
| 2635 | - if( iPort>mxPort ){ | |
| 2636 | - if( flags & HTTP_SERVER_UNIXSOCKET ){ | |
| 2637 | - fossil_fatal("unable to listen on unix socket %s", zIpAddr); | |
| 2638 | - }else if( mnPort==mxPort ){ | |
| 2639 | - fossil_fatal("unable to open listening socket on port %d", mnPort); | |
| 2640 | - }else{ | |
| 2641 | - fossil_fatal("unable to open listening socket on any" | |
| 2642 | - " port in the range %d..%d", mnPort, mxPort); | |
| 2643 | - } | |
| 2644 | - } | |
| 2645 | - if( iPort>mxPort ) return 1; | |
| 2646 | - listen(listener,10); | |
| 2647 | - if( flags & HTTP_SERVER_UNIXSOCKET ){ | |
| 2648 | - fossil_print("Listening for %s requests on unix socket %s\n", | |
| 2649 | - (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : | |
| 2650 | - g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName); | |
| 2651 | - }else{ | |
| 2652 | - fossil_print("Listening for %s requests on TCP port %d\n", | |
| 2653 | - (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : | |
| 2654 | - g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); | |
| 2655 | - } | |
| 2656 | - fflush(stdout); | |
| 2561 | + const char *zRequestType; /* Type of requests to listen for */ | |
| 2562 | + | |
| 2563 | + | |
| 2564 | + if( flags & HTTP_SERVER_SCGI ){ | |
| 2565 | + zRequestType = "SCGI"; | |
| 2566 | + }else if( g.httpUseSSL ){ | |
| 2567 | + zRequestType = "TLS-encrypted HTTPS"; | |
| 2568 | + }else{ | |
| 2569 | + zRequestType = "HTTP"; | |
| 2570 | + } | |
| 2571 | + | |
| 2572 | + if( flags & HTTP_SERVER_UNIXSOCKET ){ | |
| 2573 | + /* CASE 1: A unix socket named g.zSockName. After creation, set the | |
| 2574 | + ** permissions on the new socket to g.zSockMode and make the | |
| 2575 | + ** owner of the socket be g.zSockOwner. | |
| 2576 | + */ | |
| 2577 | + assert( g.zSockName!=0 ); | |
| 2578 | + memset(&uxaddr, 0, sizeof(uxaddr)); | |
| 2579 | + if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){ | |
| 2580 | + fossil_fatal("name of unix socket too big: %s\nmax size: %d\n", | |
| 2581 | + g.zSockName, (int)sizeof(uxaddr.sun_path)); | |
| 2582 | + } | |
| 2583 | + if( file_isdir(g.zSockName, ExtFILE)!=0 ){ | |
| 2584 | + if( !file_issocket(g.zSockName) ){ | |
| 2585 | + fossil_fatal("cannot name socket \"%s\" because another object" | |
| 2586 | + " with that name already exists", g.zSockName); | |
| 2587 | + }else{ | |
| 2588 | + unlink(g.zSockName); | |
| 2589 | + } | |
| 2590 | + } | |
| 2591 | + uxaddr.sun_family = AF_UNIX; | |
| 2592 | + strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1); | |
| 2593 | + listen4 = socket(AF_UNIX, SOCK_STREAM, 0); | |
| 2594 | + if( listen4<0 ){ | |
| 2595 | + fossil_fatal("unable to create a unix socket named %s", | |
| 2596 | + g.zSockName); | |
| 2597 | + } | |
| 2598 | + mxListen = listen4; | |
| 2599 | + listen6 = -1; | |
| 2600 | + | |
| 2601 | + /* Set the access permission for the new socket. Default to 0660. | |
| 2602 | + ** But use an alternative specified by --socket-mode if available. | |
| 2603 | + ** Do this before bind() to avoid a race condition. */ | |
| 2604 | + if( g.zSockMode ){ | |
| 2605 | + file_set_mode(g.zSockName, listen4, g.zSockMode, 0); | |
| 2606 | + }else{ | |
| 2607 | + file_set_mode(g.zSockName, listen4, "0660", 1); | |
| 2608 | + } | |
| 2609 | + rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr)); | |
| 2610 | + /* Set the owner of the socket if requested by --socket-owner. This | |
| 2611 | + ** must wait until after bind(), after the filesystem object has been | |
| 2612 | + ** created. See https://lkml.org/lkml/2004/11/1/84 and | |
| 2613 | + ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */ | |
| 2614 | + if( g.zSockOwner ){ | |
| 2615 | + file_set_owner(g.zSockName, listen4, g.zSockOwner); | |
| 2616 | + } | |
| 2617 | + fossil_print("Listening for %s requests on unix socket %s\n", | |
| 2618 | + zRequestType, g.zSockName); | |
| 2619 | + fflush(stdout); | |
| 2620 | + }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){ | |
| 2621 | + /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort. | |
| 2622 | + */ | |
| 2623 | + assert( mnPort==mxPort ); | |
| 2624 | + memset(&inaddr6, 0, sizeof(inaddr6)); | |
| 2625 | + inaddr6.sin6_family = AF_INET6; | |
| 2626 | + inaddr6.sin6_port = htons(iPort); | |
| 2627 | + if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){ | |
| 2628 | + fossil_fatal("not a valid IPv6 address: %s", zIpAddr); | |
| 2629 | + } | |
| 2630 | + listen6 = socket(AF_INET6, SOCK_STREAM, 0); | |
| 2631 | + if( listen6>0 ){ | |
| 2632 | + opt = 1; | |
| 2633 | + setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| 2634 | + rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); | |
| 2635 | + if( rc<0 ){ | |
| 2636 | + close(listen6); | |
| 2637 | + listen6 = -1; | |
| 2638 | + } | |
| 2639 | + } | |
| 2640 | + if( listen6<0 ){ | |
| 2641 | + fossil_fatal("cannot open a listening socket on [%s]:%d", | |
| 2642 | + zIpAddr, mnPort); | |
| 2643 | + } | |
| 2644 | + mxListen = listen6; | |
| 2645 | + listen4 = -1; | |
| 2646 | + fossil_print("Listening for %s requests on [%s]:%d\n", | |
| 2647 | + zRequestType, zIpAddr, iPort); | |
| 2648 | + fflush(stdout); | |
| 2649 | + }else if( zIpAddr && zIpAddr[0] ){ | |
| 2650 | + /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort. | |
| 2651 | + */ | |
| 2652 | + assert( mnPort==mxPort ); | |
| 2653 | + memset(&inaddr4, 0, sizeof(inaddr4)); | |
| 2654 | + inaddr4.sin_family = AF_INET; | |
| 2655 | + inaddr4.sin_port = htons(iPort); | |
| 2656 | + if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1"; | |
| 2657 | + inaddr4.sin_addr.s_addr = inet_addr(zIpAddr); | |
| 2658 | + if( inaddr4.sin_addr.s_addr == INADDR_NONE ){ | |
| 2659 | + fossil_fatal("not a valid IPv4 address: %s", zIpAddr); | |
| 2660 | + } | |
| 2661 | + listen4 = socket(AF_INET, SOCK_STREAM, 0); | |
| 2662 | + if( listen4>0 ){ | |
| 2663 | + setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| 2664 | + rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); | |
| 2665 | + if( rc<0 ){ | |
| 2666 | + close(listen4); | |
| 2667 | + listen4 = -1; | |
| 2668 | + } | |
| 2669 | + } | |
| 2670 | + if( listen4<0 ){ | |
| 2671 | + fossil_fatal("cannot open a listening socket on %s:%d", | |
| 2672 | + zIpAddr, mnPort); | |
| 2673 | + } | |
| 2674 | + mxListen = listen4; | |
| 2675 | + listen6 = -1; | |
| 2676 | + fossil_print("Listening for %s requests on TCP port %s:%d\n", | |
| 2677 | + zRequestType, zIpAddr, iPort); | |
| 2678 | + fflush(stdout); | |
| 2679 | + }else{ | |
| 2680 | + /* CASE 4: Listen on all available IP addresses, or on only loopback | |
| 2681 | + ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the | |
| 2682 | + ** first available in the range of mnPort..mxPort. Listen | |
| 2683 | + ** on both IPv4 and IPv6, if possible. The TCP port scan is done | |
| 2684 | + ** on IPv4. | |
| 2685 | + */ | |
| 2686 | + while( iPort<=mxPort ){ | |
| 2687 | + const char *zProto; | |
| 2688 | + memset(&inaddr4, 0, sizeof(inaddr4)); | |
| 2689 | + inaddr4.sin_family = AF_INET; | |
| 2690 | + inaddr4.sin_port = htons(iPort); | |
| 2691 | + if( flags & HTTP_SERVER_LOCALHOST ){ | |
| 2692 | + inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | |
| 2693 | + }else{ | |
| 2694 | + inaddr4.sin_addr.s_addr = htonl(INADDR_ANY); | |
| 2695 | + } | |
| 2696 | + listen4 = socket(AF_INET, SOCK_STREAM, 0); | |
| 2697 | + if( listen4>0 ){ | |
| 2698 | + setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| 2699 | + rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); | |
| 2700 | + if( rc<0 ){ | |
| 2701 | + close(listen4); | |
| 2702 | + listen4 = -1; | |
| 2703 | + } | |
| 2704 | + } | |
| 2705 | + if( listen4<0 ){ | |
| 2706 | + iPort++; | |
| 2707 | + continue; | |
| 2708 | + } | |
| 2709 | + mxListen = listen4; | |
| 2710 | + | |
| 2711 | + /* If we get here, that means we found an open TCP port at iPort for | |
| 2712 | + ** IPv4. Try to set up a corresponding IPv6 socket on the same port. | |
| 2713 | + */ | |
| 2714 | + memset(&inaddr6, 0, sizeof(inaddr6)); | |
| 2715 | + inaddr6.sin6_family = AF_INET6; | |
| 2716 | + inaddr6.sin6_port = htons(iPort); | |
| 2717 | + if( flags & HTTP_SERVER_LOCALHOST ){ | |
| 2718 | + inaddr6.sin6_addr = in6addr_loopback; | |
| 2719 | + }else{ | |
| 2720 | + inaddr6.sin6_addr = in6addr_any; | |
| 2721 | + } | |
| 2722 | + listen6 = socket(AF_INET6, SOCK_STREAM, 0); | |
| 2723 | + if( listen6>0 ){ | |
| 2724 | + setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| 2725 | + setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); | |
| 2726 | + rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); | |
| 2727 | + if( rc<0 ){ | |
| 2728 | + close(listen6); | |
| 2729 | + listen6 = -1; | |
| 2730 | + } | |
| 2731 | + } | |
| 2732 | + if( listen6<0 ){ | |
| 2733 | + zProto = "IPv4 only"; | |
| 2734 | + }else{ | |
| 2735 | + zProto = "IPv4 and IPv6"; | |
| 2736 | + if( listen6>listen4 ) mxListen = listen6; | |
| 2737 | + } | |
| 2738 | + | |
| 2739 | + fossil_print("Listening for %s requests on TCP port %s%d, %s\n", | |
| 2740 | + zRequestType, | |
| 2741 | + (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "", | |
| 2742 | + iPort, zProto); | |
| 2743 | + fflush(stdout); | |
| 2744 | + break; | |
| 2745 | + } | |
| 2746 | + if( iPort>mxPort ){ | |
| 2747 | + fossil_fatal("no available TCP ports in the range %d..%d", | |
| 2748 | + mnPort, mxPort); | |
| 2749 | + } | |
| 2750 | + } | |
| 2751 | + | |
| 2752 | + /* If we get to this point, that means there is at least one listening | |
| 2753 | + ** socket on either listen4 or listen6 and perhaps on both. */ | |
| 2754 | + assert( listen4>0 || listen6>0 ); | |
| 2755 | + if( listen4>0 ) listen(listen4,10); | |
| 2756 | + if( listen6>0 ) listen(listen6,10); | |
| 2657 | 2757 | if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){ |
| 2658 | 2758 | assert( strstr(zBrowser,"%d")!=0 ); |
| 2659 | 2759 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 2660 | 2760 | #if defined(__CYGWIN__) |
| 2661 | 2761 | /* On Cygwin, we can do better than "echo" */ |
| @@ -2669,57 +2769,69 @@ | ||
| 2669 | 2769 | #endif |
| 2670 | 2770 | if( fossil_system(zBrowser)<0 ){ |
| 2671 | 2771 | fossil_warning("cannot start browser: %s\n", zBrowser); |
| 2672 | 2772 | } |
| 2673 | 2773 | } |
| 2774 | + | |
| 2775 | + /* What for incomming requests. For each request, fork() a child process | |
| 2776 | + ** to deal with that request. The child process returns. The parent | |
| 2777 | + ** keeps on listening and never returns. | |
| 2778 | + */ | |
| 2674 | 2779 | while( 1 ){ |
| 2675 | 2780 | #if FOSSIL_MAX_CONNECTIONS>0 |
| 2676 | 2781 | while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ |
| 2677 | 2782 | if( wait(0)>=0 ) nchildren--; |
| 2678 | 2783 | } |
| 2679 | 2784 | #endif |
| 2680 | 2785 | delay.tv_sec = 0; |
| 2681 | 2786 | delay.tv_usec = 100000; |
| 2682 | 2787 | FD_ZERO(&readfds); |
| 2683 | - assert( listener>=0 ); | |
| 2684 | - FD_SET( listener, &readfds); | |
| 2685 | - select( listener+1, &readfds, 0, 0, &delay); | |
| 2686 | - if( FD_ISSET(listener, &readfds) ){ | |
| 2687 | - lenaddr = sizeof(inaddr); | |
| 2688 | - connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); | |
| 2689 | - if( connection>=0 ){ | |
| 2690 | - if( flags & HTTP_SERVER_NOFORK ){ | |
| 2691 | - child = 0; | |
| 2692 | - }else{ | |
| 2693 | - child = fork(); | |
| 2694 | - } | |
| 2695 | - if( child!=0 ){ | |
| 2696 | - if( child>0 ){ | |
| 2697 | - nchildren++; | |
| 2698 | - nRequest++; | |
| 2699 | - } | |
| 2700 | - close(connection); | |
| 2701 | - }else{ | |
| 2702 | - int nErr = 0, fd; | |
| 2703 | - g.zSockName = 0 /* avoid deleting the socket via atexit() */; | |
| 2704 | - close(0); | |
| 2705 | - fd = dup(connection); | |
| 2706 | - if( fd!=0 ) nErr++; | |
| 2707 | - close(1); | |
| 2708 | - fd = dup(connection); | |
| 2709 | - if( fd!=1 ) nErr++; | |
| 2710 | - if( 0 && !g.fAnyTrace ){ | |
| 2711 | - close(2); | |
| 2712 | - fd = dup(connection); | |
| 2713 | - if( fd!=2 ) nErr++; | |
| 2714 | - } | |
| 2715 | - close(connection); | |
| 2716 | - close(listener); | |
| 2717 | - g.nPendingRequest = nchildren+1; | |
| 2718 | - g.nRequest = nRequest+1; | |
| 2719 | - return nErr; | |
| 2720 | - } | |
| 2788 | + assert( listen4>0 || listen6>0 ); | |
| 2789 | + if( listen4>0 ) FD_SET( listen4, &readfds); | |
| 2790 | + if( listen6>0 ) FD_SET( listen6, &readfds); | |
| 2791 | + select( mxListen+1, &readfds, 0, 0, &delay); | |
| 2792 | + if( listen4>0 && FD_ISSET(listen4, &readfds) ){ | |
| 2793 | + lenaddr = sizeof(inaddr4); | |
| 2794 | + connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr); | |
| 2795 | + }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){ | |
| 2796 | + lenaddr = sizeof(inaddr6); | |
| 2797 | + connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr); | |
| 2798 | + }else{ | |
| 2799 | + connection = -1; | |
| 2800 | + } | |
| 2801 | + if( connection>=0 ){ | |
| 2802 | + if( flags & HTTP_SERVER_NOFORK ){ | |
| 2803 | + child = 0; | |
| 2804 | + }else{ | |
| 2805 | + child = fork(); | |
| 2806 | + } | |
| 2807 | + if( child!=0 ){ | |
| 2808 | + if( child>0 ){ | |
| 2809 | + nchildren++; | |
| 2810 | + nRequest++; | |
| 2811 | + } | |
| 2812 | + close(connection); | |
| 2813 | + }else{ | |
| 2814 | + int nErr = 0, fd; | |
| 2815 | + g.zSockName = 0 /* avoid deleting the socket via atexit() */; | |
| 2816 | + close(0); | |
| 2817 | + fd = dup(connection); | |
| 2818 | + if( fd!=0 ) nErr++; | |
| 2819 | + close(1); | |
| 2820 | + fd = dup(connection); | |
| 2821 | + if( fd!=1 ) nErr++; | |
| 2822 | + if( 0 && !g.fAnyTrace ){ | |
| 2823 | + close(2); | |
| 2824 | + fd = dup(connection); | |
| 2825 | + if( fd!=2 ) nErr++; | |
| 2826 | + } | |
| 2827 | + close(connection); | |
| 2828 | + if( listen4>0 ) close(listen4); | |
| 2829 | + if( listen6>0 ) close(listen6); | |
| 2830 | + g.nPendingRequest = nchildren+1; | |
| 2831 | + g.nRequest = nRequest+1; | |
| 2832 | + return nErr; | |
| 2721 | 2833 | } |
| 2722 | 2834 | } |
| 2723 | 2835 | /* Bury dead children */ |
| 2724 | 2836 | if( nchildren ){ |
| 2725 | 2837 | while(1){ |
| 2726 | 2838 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -2498,11 +2498,10 @@ | |
| 2498 | fossil_free(zToFree); |
| 2499 | fgetc(g.httpIn); /* Read past the "," separating header from content */ |
| 2500 | cgi_init(); |
| 2501 | } |
| 2502 | |
| 2503 | |
| 2504 | #if INTERFACE |
| 2505 | /* |
| 2506 | ** Bitmap values for the flags parameter to cgi_http_server(). |
| 2507 | */ |
| 2508 | #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ |
| @@ -2541,121 +2540,222 @@ | |
| 2541 | ){ |
| 2542 | #if defined(_WIN32) |
| 2543 | /* Use win32_http_server() instead */ |
| 2544 | fossil_exit(1); |
| 2545 | #else |
| 2546 | int listener = -1; /* The server socket */ |
| 2547 | int connection; /* A socket for each individual connection */ |
| 2548 | int nRequest = 0; /* Number of requests handled so far */ |
| 2549 | fd_set readfds; /* Set of file descriptors for select() */ |
| 2550 | socklen_t lenaddr; /* Length of the inaddr structure */ |
| 2551 | int child; /* PID of the child process */ |
| 2552 | int nchildren = 0; /* Number of child processes */ |
| 2553 | struct timeval delay; /* How long to wait inside select() */ |
| 2554 | struct sockaddr_in6 inaddr; /* The socket address */ |
| 2555 | struct sockaddr_un uxaddr; /* The address for unix-domain sockets */ |
| 2556 | int opt = 1; /* setsockopt flag */ |
| 2557 | int rc; /* Result code from system calls */ |
| 2558 | int iPort = mnPort; /* Port to try to use */ |
| 2559 | |
| 2560 | while( iPort<=mxPort ){ |
| 2561 | if( flags & HTTP_SERVER_UNIXSOCKET ){ |
| 2562 | /* Initialize a Unix socket named g.zSockName */ |
| 2563 | assert( g.zSockName!=0 ); |
| 2564 | memset(&uxaddr, 0, sizeof(uxaddr)); |
| 2565 | if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){ |
| 2566 | fossil_fatal("name of unix socket too big: %s\nmax size: %d\n", |
| 2567 | g.zSockName, (int)sizeof(uxaddr.sun_path)); |
| 2568 | } |
| 2569 | if( file_isdir(g.zSockName, ExtFILE)!=0 ){ |
| 2570 | if( !file_issocket(g.zSockName) ){ |
| 2571 | fossil_fatal("cannot name socket \"%s\" because another object" |
| 2572 | " with that name already exists", g.zSockName); |
| 2573 | }else{ |
| 2574 | unlink(g.zSockName); |
| 2575 | } |
| 2576 | } |
| 2577 | uxaddr.sun_family = AF_UNIX; |
| 2578 | strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1); |
| 2579 | listener = socket(AF_UNIX, SOCK_STREAM, 0); |
| 2580 | if( listener<0 ){ |
| 2581 | fossil_fatal("unable to create a unix socket named %s", |
| 2582 | g.zSockName); |
| 2583 | } |
| 2584 | /* Set the access permission for the new socket. Default to 0660. |
| 2585 | ** But use an alternative specified by --socket-mode if available. |
| 2586 | ** Do this before bind() to avoid a race condition. */ |
| 2587 | if( g.zSockMode ){ |
| 2588 | file_set_mode(g.zSockName, listener, g.zSockMode, 0); |
| 2589 | }else{ |
| 2590 | file_set_mode(g.zSockName, listener, "0660", 1); |
| 2591 | } |
| 2592 | }else{ |
| 2593 | /* Initialize a TCP/IP socket on port iPort */ |
| 2594 | memset(&inaddr, 0, sizeof(inaddr)); |
| 2595 | inaddr.sin6_family = AF_INET6; |
| 2596 | if( zIpAddr ){ |
| 2597 | if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){ |
| 2598 | fossil_fatal("not a valid IP address: %s", zIpAddr); |
| 2599 | } |
| 2600 | }else if( flags & HTTP_SERVER_LOCALHOST ){ |
| 2601 | inaddr.sin6_addr = in6addr_loopback; |
| 2602 | }else{ |
| 2603 | inaddr.sin6_addr = in6addr_any; |
| 2604 | } |
| 2605 | inaddr.sin6_port = htons(iPort); |
| 2606 | listener = socket(AF_INET6, SOCK_STREAM, 0); |
| 2607 | if( listener<0 ){ |
| 2608 | iPort++; |
| 2609 | continue; |
| 2610 | } |
| 2611 | } |
| 2612 | |
| 2613 | /* if we can't terminate nicely, at least allow the socket to be reused */ |
| 2614 | setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); |
| 2615 | |
| 2616 | if( flags & HTTP_SERVER_UNIXSOCKET ){ |
| 2617 | rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr)); |
| 2618 | /* Set the owner of the socket if requested by --socket-owner. This |
| 2619 | ** must wait until after bind(), after the filesystem object has been |
| 2620 | ** created. See https://lkml.org/lkml/2004/11/1/84 and |
| 2621 | ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */ |
| 2622 | if( g.zSockOwner ){ |
| 2623 | file_set_owner(g.zSockName, listener, g.zSockOwner); |
| 2624 | } |
| 2625 | }else{ |
| 2626 | rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr)); |
| 2627 | } |
| 2628 | if( rc<0 ){ |
| 2629 | close(listener); |
| 2630 | iPort++; |
| 2631 | continue; |
| 2632 | } |
| 2633 | break; |
| 2634 | } |
| 2635 | if( iPort>mxPort ){ |
| 2636 | if( flags & HTTP_SERVER_UNIXSOCKET ){ |
| 2637 | fossil_fatal("unable to listen on unix socket %s", zIpAddr); |
| 2638 | }else if( mnPort==mxPort ){ |
| 2639 | fossil_fatal("unable to open listening socket on port %d", mnPort); |
| 2640 | }else{ |
| 2641 | fossil_fatal("unable to open listening socket on any" |
| 2642 | " port in the range %d..%d", mnPort, mxPort); |
| 2643 | } |
| 2644 | } |
| 2645 | if( iPort>mxPort ) return 1; |
| 2646 | listen(listener,10); |
| 2647 | if( flags & HTTP_SERVER_UNIXSOCKET ){ |
| 2648 | fossil_print("Listening for %s requests on unix socket %s\n", |
| 2649 | (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : |
| 2650 | g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", g.zSockName); |
| 2651 | }else{ |
| 2652 | fossil_print("Listening for %s requests on TCP port %d\n", |
| 2653 | (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : |
| 2654 | g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); |
| 2655 | } |
| 2656 | fflush(stdout); |
| 2657 | if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){ |
| 2658 | assert( strstr(zBrowser,"%d")!=0 ); |
| 2659 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 2660 | #if defined(__CYGWIN__) |
| 2661 | /* On Cygwin, we can do better than "echo" */ |
| @@ -2669,57 +2769,69 @@ | |
| 2669 | #endif |
| 2670 | if( fossil_system(zBrowser)<0 ){ |
| 2671 | fossil_warning("cannot start browser: %s\n", zBrowser); |
| 2672 | } |
| 2673 | } |
| 2674 | while( 1 ){ |
| 2675 | #if FOSSIL_MAX_CONNECTIONS>0 |
| 2676 | while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ |
| 2677 | if( wait(0)>=0 ) nchildren--; |
| 2678 | } |
| 2679 | #endif |
| 2680 | delay.tv_sec = 0; |
| 2681 | delay.tv_usec = 100000; |
| 2682 | FD_ZERO(&readfds); |
| 2683 | assert( listener>=0 ); |
| 2684 | FD_SET( listener, &readfds); |
| 2685 | select( listener+1, &readfds, 0, 0, &delay); |
| 2686 | if( FD_ISSET(listener, &readfds) ){ |
| 2687 | lenaddr = sizeof(inaddr); |
| 2688 | connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); |
| 2689 | if( connection>=0 ){ |
| 2690 | if( flags & HTTP_SERVER_NOFORK ){ |
| 2691 | child = 0; |
| 2692 | }else{ |
| 2693 | child = fork(); |
| 2694 | } |
| 2695 | if( child!=0 ){ |
| 2696 | if( child>0 ){ |
| 2697 | nchildren++; |
| 2698 | nRequest++; |
| 2699 | } |
| 2700 | close(connection); |
| 2701 | }else{ |
| 2702 | int nErr = 0, fd; |
| 2703 | g.zSockName = 0 /* avoid deleting the socket via atexit() */; |
| 2704 | close(0); |
| 2705 | fd = dup(connection); |
| 2706 | if( fd!=0 ) nErr++; |
| 2707 | close(1); |
| 2708 | fd = dup(connection); |
| 2709 | if( fd!=1 ) nErr++; |
| 2710 | if( 0 && !g.fAnyTrace ){ |
| 2711 | close(2); |
| 2712 | fd = dup(connection); |
| 2713 | if( fd!=2 ) nErr++; |
| 2714 | } |
| 2715 | close(connection); |
| 2716 | close(listener); |
| 2717 | g.nPendingRequest = nchildren+1; |
| 2718 | g.nRequest = nRequest+1; |
| 2719 | return nErr; |
| 2720 | } |
| 2721 | } |
| 2722 | } |
| 2723 | /* Bury dead children */ |
| 2724 | if( nchildren ){ |
| 2725 | while(1){ |
| 2726 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -2498,11 +2498,10 @@ | |
| 2498 | fossil_free(zToFree); |
| 2499 | fgetc(g.httpIn); /* Read past the "," separating header from content */ |
| 2500 | cgi_init(); |
| 2501 | } |
| 2502 | |
| 2503 | #if INTERFACE |
| 2504 | /* |
| 2505 | ** Bitmap values for the flags parameter to cgi_http_server(). |
| 2506 | */ |
| 2507 | #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ |
| @@ -2541,121 +2540,222 @@ | |
| 2540 | ){ |
| 2541 | #if defined(_WIN32) |
| 2542 | /* Use win32_http_server() instead */ |
| 2543 | fossil_exit(1); |
| 2544 | #else |
| 2545 | int listen4 = -1; /* Main socket; IPv4 or unix-domain */ |
| 2546 | int listen6 = -1; /* Aux socket for corresponding IPv6 */ |
| 2547 | int mxListen = -1; /* Maximum of listen4 and listen6 */ |
| 2548 | int connection; /* An incoming connection */ |
| 2549 | int nRequest = 0; /* Number of requests handled so far */ |
| 2550 | fd_set readfds; /* Set of file descriptors for select() */ |
| 2551 | socklen_t lenaddr; /* Length of the inaddr structure */ |
| 2552 | int child; /* PID of the child process */ |
| 2553 | int nchildren = 0; /* Number of child processes */ |
| 2554 | struct timeval delay; /* How long to wait inside select() */ |
| 2555 | struct sockaddr_in6 inaddr6; /* Address for IPv6 */ |
| 2556 | struct sockaddr_in inaddr4; /* Address for IPv4 */ |
| 2557 | struct sockaddr_un uxaddr; /* The address for unix-domain sockets */ |
| 2558 | int opt = 1; /* setsockopt flag */ |
| 2559 | int rc; /* Result code from system calls */ |
| 2560 | int iPort = mnPort; /* Port to try to use */ |
| 2561 | const char *zRequestType; /* Type of requests to listen for */ |
| 2562 | |
| 2563 | |
| 2564 | if( flags & HTTP_SERVER_SCGI ){ |
| 2565 | zRequestType = "SCGI"; |
| 2566 | }else if( g.httpUseSSL ){ |
| 2567 | zRequestType = "TLS-encrypted HTTPS"; |
| 2568 | }else{ |
| 2569 | zRequestType = "HTTP"; |
| 2570 | } |
| 2571 | |
| 2572 | if( flags & HTTP_SERVER_UNIXSOCKET ){ |
| 2573 | /* CASE 1: A unix socket named g.zSockName. After creation, set the |
| 2574 | ** permissions on the new socket to g.zSockMode and make the |
| 2575 | ** owner of the socket be g.zSockOwner. |
| 2576 | */ |
| 2577 | assert( g.zSockName!=0 ); |
| 2578 | memset(&uxaddr, 0, sizeof(uxaddr)); |
| 2579 | if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){ |
| 2580 | fossil_fatal("name of unix socket too big: %s\nmax size: %d\n", |
| 2581 | g.zSockName, (int)sizeof(uxaddr.sun_path)); |
| 2582 | } |
| 2583 | if( file_isdir(g.zSockName, ExtFILE)!=0 ){ |
| 2584 | if( !file_issocket(g.zSockName) ){ |
| 2585 | fossil_fatal("cannot name socket \"%s\" because another object" |
| 2586 | " with that name already exists", g.zSockName); |
| 2587 | }else{ |
| 2588 | unlink(g.zSockName); |
| 2589 | } |
| 2590 | } |
| 2591 | uxaddr.sun_family = AF_UNIX; |
| 2592 | strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1); |
| 2593 | listen4 = socket(AF_UNIX, SOCK_STREAM, 0); |
| 2594 | if( listen4<0 ){ |
| 2595 | fossil_fatal("unable to create a unix socket named %s", |
| 2596 | g.zSockName); |
| 2597 | } |
| 2598 | mxListen = listen4; |
| 2599 | listen6 = -1; |
| 2600 | |
| 2601 | /* Set the access permission for the new socket. Default to 0660. |
| 2602 | ** But use an alternative specified by --socket-mode if available. |
| 2603 | ** Do this before bind() to avoid a race condition. */ |
| 2604 | if( g.zSockMode ){ |
| 2605 | file_set_mode(g.zSockName, listen4, g.zSockMode, 0); |
| 2606 | }else{ |
| 2607 | file_set_mode(g.zSockName, listen4, "0660", 1); |
| 2608 | } |
| 2609 | rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr)); |
| 2610 | /* Set the owner of the socket if requested by --socket-owner. This |
| 2611 | ** must wait until after bind(), after the filesystem object has been |
| 2612 | ** created. See https://lkml.org/lkml/2004/11/1/84 and |
| 2613 | ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */ |
| 2614 | if( g.zSockOwner ){ |
| 2615 | file_set_owner(g.zSockName, listen4, g.zSockOwner); |
| 2616 | } |
| 2617 | fossil_print("Listening for %s requests on unix socket %s\n", |
| 2618 | zRequestType, g.zSockName); |
| 2619 | fflush(stdout); |
| 2620 | }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){ |
| 2621 | /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort. |
| 2622 | */ |
| 2623 | assert( mnPort==mxPort ); |
| 2624 | memset(&inaddr6, 0, sizeof(inaddr6)); |
| 2625 | inaddr6.sin6_family = AF_INET6; |
| 2626 | inaddr6.sin6_port = htons(iPort); |
| 2627 | if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){ |
| 2628 | fossil_fatal("not a valid IPv6 address: %s", zIpAddr); |
| 2629 | } |
| 2630 | listen6 = socket(AF_INET6, SOCK_STREAM, 0); |
| 2631 | if( listen6>0 ){ |
| 2632 | opt = 1; |
| 2633 | setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| 2634 | rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); |
| 2635 | if( rc<0 ){ |
| 2636 | close(listen6); |
| 2637 | listen6 = -1; |
| 2638 | } |
| 2639 | } |
| 2640 | if( listen6<0 ){ |
| 2641 | fossil_fatal("cannot open a listening socket on [%s]:%d", |
| 2642 | zIpAddr, mnPort); |
| 2643 | } |
| 2644 | mxListen = listen6; |
| 2645 | listen4 = -1; |
| 2646 | fossil_print("Listening for %s requests on [%s]:%d\n", |
| 2647 | zRequestType, zIpAddr, iPort); |
| 2648 | fflush(stdout); |
| 2649 | }else if( zIpAddr && zIpAddr[0] ){ |
| 2650 | /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort. |
| 2651 | */ |
| 2652 | assert( mnPort==mxPort ); |
| 2653 | memset(&inaddr4, 0, sizeof(inaddr4)); |
| 2654 | inaddr4.sin_family = AF_INET; |
| 2655 | inaddr4.sin_port = htons(iPort); |
| 2656 | if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1"; |
| 2657 | inaddr4.sin_addr.s_addr = inet_addr(zIpAddr); |
| 2658 | if( inaddr4.sin_addr.s_addr == INADDR_NONE ){ |
| 2659 | fossil_fatal("not a valid IPv4 address: %s", zIpAddr); |
| 2660 | } |
| 2661 | listen4 = socket(AF_INET, SOCK_STREAM, 0); |
| 2662 | if( listen4>0 ){ |
| 2663 | setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| 2664 | rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); |
| 2665 | if( rc<0 ){ |
| 2666 | close(listen4); |
| 2667 | listen4 = -1; |
| 2668 | } |
| 2669 | } |
| 2670 | if( listen4<0 ){ |
| 2671 | fossil_fatal("cannot open a listening socket on %s:%d", |
| 2672 | zIpAddr, mnPort); |
| 2673 | } |
| 2674 | mxListen = listen4; |
| 2675 | listen6 = -1; |
| 2676 | fossil_print("Listening for %s requests on TCP port %s:%d\n", |
| 2677 | zRequestType, zIpAddr, iPort); |
| 2678 | fflush(stdout); |
| 2679 | }else{ |
| 2680 | /* CASE 4: Listen on all available IP addresses, or on only loopback |
| 2681 | ** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the |
| 2682 | ** first available in the range of mnPort..mxPort. Listen |
| 2683 | ** on both IPv4 and IPv6, if possible. The TCP port scan is done |
| 2684 | ** on IPv4. |
| 2685 | */ |
| 2686 | while( iPort<=mxPort ){ |
| 2687 | const char *zProto; |
| 2688 | memset(&inaddr4, 0, sizeof(inaddr4)); |
| 2689 | inaddr4.sin_family = AF_INET; |
| 2690 | inaddr4.sin_port = htons(iPort); |
| 2691 | if( flags & HTTP_SERVER_LOCALHOST ){ |
| 2692 | inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| 2693 | }else{ |
| 2694 | inaddr4.sin_addr.s_addr = htonl(INADDR_ANY); |
| 2695 | } |
| 2696 | listen4 = socket(AF_INET, SOCK_STREAM, 0); |
| 2697 | if( listen4>0 ){ |
| 2698 | setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| 2699 | rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); |
| 2700 | if( rc<0 ){ |
| 2701 | close(listen4); |
| 2702 | listen4 = -1; |
| 2703 | } |
| 2704 | } |
| 2705 | if( listen4<0 ){ |
| 2706 | iPort++; |
| 2707 | continue; |
| 2708 | } |
| 2709 | mxListen = listen4; |
| 2710 | |
| 2711 | /* If we get here, that means we found an open TCP port at iPort for |
| 2712 | ** IPv4. Try to set up a corresponding IPv6 socket on the same port. |
| 2713 | */ |
| 2714 | memset(&inaddr6, 0, sizeof(inaddr6)); |
| 2715 | inaddr6.sin6_family = AF_INET6; |
| 2716 | inaddr6.sin6_port = htons(iPort); |
| 2717 | if( flags & HTTP_SERVER_LOCALHOST ){ |
| 2718 | inaddr6.sin6_addr = in6addr_loopback; |
| 2719 | }else{ |
| 2720 | inaddr6.sin6_addr = in6addr_any; |
| 2721 | } |
| 2722 | listen6 = socket(AF_INET6, SOCK_STREAM, 0); |
| 2723 | if( listen6>0 ){ |
| 2724 | setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| 2725 | setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); |
| 2726 | rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); |
| 2727 | if( rc<0 ){ |
| 2728 | close(listen6); |
| 2729 | listen6 = -1; |
| 2730 | } |
| 2731 | } |
| 2732 | if( listen6<0 ){ |
| 2733 | zProto = "IPv4 only"; |
| 2734 | }else{ |
| 2735 | zProto = "IPv4 and IPv6"; |
| 2736 | if( listen6>listen4 ) mxListen = listen6; |
| 2737 | } |
| 2738 | |
| 2739 | fossil_print("Listening for %s requests on TCP port %s%d, %s\n", |
| 2740 | zRequestType, |
| 2741 | (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "", |
| 2742 | iPort, zProto); |
| 2743 | fflush(stdout); |
| 2744 | break; |
| 2745 | } |
| 2746 | if( iPort>mxPort ){ |
| 2747 | fossil_fatal("no available TCP ports in the range %d..%d", |
| 2748 | mnPort, mxPort); |
| 2749 | } |
| 2750 | } |
| 2751 | |
| 2752 | /* If we get to this point, that means there is at least one listening |
| 2753 | ** socket on either listen4 or listen6 and perhaps on both. */ |
| 2754 | assert( listen4>0 || listen6>0 ); |
| 2755 | if( listen4>0 ) listen(listen4,10); |
| 2756 | if( listen6>0 ) listen(listen6,10); |
| 2757 | if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){ |
| 2758 | assert( strstr(zBrowser,"%d")!=0 ); |
| 2759 | zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
| 2760 | #if defined(__CYGWIN__) |
| 2761 | /* On Cygwin, we can do better than "echo" */ |
| @@ -2669,57 +2769,69 @@ | |
| 2769 | #endif |
| 2770 | if( fossil_system(zBrowser)<0 ){ |
| 2771 | fossil_warning("cannot start browser: %s\n", zBrowser); |
| 2772 | } |
| 2773 | } |
| 2774 | |
| 2775 | /* What for incomming requests. For each request, fork() a child process |
| 2776 | ** to deal with that request. The child process returns. The parent |
| 2777 | ** keeps on listening and never returns. |
| 2778 | */ |
| 2779 | while( 1 ){ |
| 2780 | #if FOSSIL_MAX_CONNECTIONS>0 |
| 2781 | while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ |
| 2782 | if( wait(0)>=0 ) nchildren--; |
| 2783 | } |
| 2784 | #endif |
| 2785 | delay.tv_sec = 0; |
| 2786 | delay.tv_usec = 100000; |
| 2787 | FD_ZERO(&readfds); |
| 2788 | assert( listen4>0 || listen6>0 ); |
| 2789 | if( listen4>0 ) FD_SET( listen4, &readfds); |
| 2790 | if( listen6>0 ) FD_SET( listen6, &readfds); |
| 2791 | select( mxListen+1, &readfds, 0, 0, &delay); |
| 2792 | if( listen4>0 && FD_ISSET(listen4, &readfds) ){ |
| 2793 | lenaddr = sizeof(inaddr4); |
| 2794 | connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr); |
| 2795 | }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){ |
| 2796 | lenaddr = sizeof(inaddr6); |
| 2797 | connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr); |
| 2798 | }else{ |
| 2799 | connection = -1; |
| 2800 | } |
| 2801 | if( connection>=0 ){ |
| 2802 | if( flags & HTTP_SERVER_NOFORK ){ |
| 2803 | child = 0; |
| 2804 | }else{ |
| 2805 | child = fork(); |
| 2806 | } |
| 2807 | if( child!=0 ){ |
| 2808 | if( child>0 ){ |
| 2809 | nchildren++; |
| 2810 | nRequest++; |
| 2811 | } |
| 2812 | close(connection); |
| 2813 | }else{ |
| 2814 | int nErr = 0, fd; |
| 2815 | g.zSockName = 0 /* avoid deleting the socket via atexit() */; |
| 2816 | close(0); |
| 2817 | fd = dup(connection); |
| 2818 | if( fd!=0 ) nErr++; |
| 2819 | close(1); |
| 2820 | fd = dup(connection); |
| 2821 | if( fd!=1 ) nErr++; |
| 2822 | if( 0 && !g.fAnyTrace ){ |
| 2823 | close(2); |
| 2824 | fd = dup(connection); |
| 2825 | if( fd!=2 ) nErr++; |
| 2826 | } |
| 2827 | close(connection); |
| 2828 | if( listen4>0 ) close(listen4); |
| 2829 | if( listen6>0 ) close(listen6); |
| 2830 | g.nPendingRequest = nchildren+1; |
| 2831 | g.nRequest = nRequest+1; |
| 2832 | return nErr; |
| 2833 | } |
| 2834 | } |
| 2835 | /* Bury dead children */ |
| 2836 | if( nchildren ){ |
| 2837 | while(1){ |
| 2838 |
+2
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -2444,10 +2444,11 @@ | ||
| 2444 | 2444 | ** --close Close the branch being committed |
| 2445 | 2445 | ** --date-override DATETIME Make DATETIME the time of the check-in. |
| 2446 | 2446 | ** Useful when importing historical check-ins |
| 2447 | 2447 | ** from another version control system. |
| 2448 | 2448 | ** --delta Use a delta manifest in the commit process |
| 2449 | +** --editor NAME Text editor to use for check-in comment. | |
| 2449 | 2450 | ** --hash Verify file status using hashing rather |
| 2450 | 2451 | ** than relying on filesystem mtimes |
| 2451 | 2452 | ** --if-changes Make this command a silent no-op if there |
| 2452 | 2453 | ** are no changes |
| 2453 | 2454 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| @@ -2599,10 +2600,11 @@ | ||
| 2599 | 2600 | useCksum = db_get_boolean("repo-cksum", 1); |
| 2600 | 2601 | bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0; |
| 2601 | 2602 | outputManifest = db_get_manifest_setting(0); |
| 2602 | 2603 | mxSize = db_large_file_size(); |
| 2603 | 2604 | if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0; |
| 2605 | + (void)fossil_text_editor(); | |
| 2604 | 2606 | verify_all_options(); |
| 2605 | 2607 | |
| 2606 | 2608 | /* The --no-warnings flag and the --force flag each imply |
| 2607 | 2609 | ** the --no-verify-comment flag */ |
| 2608 | 2610 | if( noWarningFlag || forceFlag ){ |
| 2609 | 2611 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2444,10 +2444,11 @@ | |
| 2444 | ** --close Close the branch being committed |
| 2445 | ** --date-override DATETIME Make DATETIME the time of the check-in. |
| 2446 | ** Useful when importing historical check-ins |
| 2447 | ** from another version control system. |
| 2448 | ** --delta Use a delta manifest in the commit process |
| 2449 | ** --hash Verify file status using hashing rather |
| 2450 | ** than relying on filesystem mtimes |
| 2451 | ** --if-changes Make this command a silent no-op if there |
| 2452 | ** are no changes |
| 2453 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| @@ -2599,10 +2600,11 @@ | |
| 2599 | useCksum = db_get_boolean("repo-cksum", 1); |
| 2600 | bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0; |
| 2601 | outputManifest = db_get_manifest_setting(0); |
| 2602 | mxSize = db_large_file_size(); |
| 2603 | if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0; |
| 2604 | verify_all_options(); |
| 2605 | |
| 2606 | /* The --no-warnings flag and the --force flag each imply |
| 2607 | ** the --no-verify-comment flag */ |
| 2608 | if( noWarningFlag || forceFlag ){ |
| 2609 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -2444,10 +2444,11 @@ | |
| 2444 | ** --close Close the branch being committed |
| 2445 | ** --date-override DATETIME Make DATETIME the time of the check-in. |
| 2446 | ** Useful when importing historical check-ins |
| 2447 | ** from another version control system. |
| 2448 | ** --delta Use a delta manifest in the commit process |
| 2449 | ** --editor NAME Text editor to use for check-in comment. |
| 2450 | ** --hash Verify file status using hashing rather |
| 2451 | ** than relying on filesystem mtimes |
| 2452 | ** --if-changes Make this command a silent no-op if there |
| 2453 | ** are no changes |
| 2454 | ** --ignore-clock-skew If a clock skew is detected, ignore it and |
| @@ -2599,10 +2600,11 @@ | |
| 2600 | useCksum = db_get_boolean("repo-cksum", 1); |
| 2601 | bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0; |
| 2602 | outputManifest = db_get_manifest_setting(0); |
| 2603 | mxSize = db_large_file_size(); |
| 2604 | if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0; |
| 2605 | (void)fossil_text_editor(); |
| 2606 | verify_all_options(); |
| 2607 | |
| 2608 | /* The --no-warnings flag and the --force flag each imply |
| 2609 | ** the --no-verify-comment flag */ |
| 2610 | if( noWarningFlag || forceFlag ){ |
| 2611 |
+4
-2
| --- src/clone.c | ||
| +++ src/clone.c | ||
| @@ -430,12 +430,14 @@ | ||
| 430 | 430 | const char *zNm = db_get("short-project-name","download"); |
| 431 | 431 | char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); |
| 432 | 432 | @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> |
| 433 | 433 | zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); |
| 434 | 434 | @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> |
| 435 | - zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); | |
| 436 | - @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> | |
| 435 | + if( g.zLogin!=0 ){ | |
| 436 | + zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); | |
| 437 | + @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> | |
| 438 | + } | |
| 437 | 439 | } |
| 438 | 440 | if( !g.perm.Clone ){ |
| 439 | 441 | @ <p>You are not authorized to clone this repository. |
| 440 | 442 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 441 | 443 | @ Maybe you would be able to clone if you |
| 442 | 444 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -430,12 +430,14 @@ | |
| 430 | const char *zNm = db_get("short-project-name","download"); |
| 431 | char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); |
| 432 | @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> |
| 433 | zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); |
| 434 | @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> |
| 435 | zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); |
| 436 | @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> |
| 437 | } |
| 438 | if( !g.perm.Clone ){ |
| 439 | @ <p>You are not authorized to clone this repository. |
| 440 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 441 | @ Maybe you would be able to clone if you |
| 442 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -430,12 +430,14 @@ | |
| 430 | const char *zNm = db_get("short-project-name","download"); |
| 431 | char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); |
| 432 | @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> |
| 433 | zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); |
| 434 | @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> |
| 435 | if( g.zLogin!=0 ){ |
| 436 | zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); |
| 437 | @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> |
| 438 | } |
| 439 | } |
| 440 | if( !g.perm.Clone ){ |
| 441 | @ <p>You are not authorized to clone this repository. |
| 442 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 443 | @ Maybe you would be able to clone if you |
| 444 |
M
src/db.c
+2
-2
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -3369,11 +3369,11 @@ | ||
| 3369 | 3369 | if( zProjectName ) fossil_print("project-name: %s\n", zProjectName); |
| 3370 | 3370 | if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc); |
| 3371 | 3371 | fossil_print("project-id: %s\n", db_get("project-code", 0)); |
| 3372 | 3372 | fossil_print("server-id: %s\n", db_get("server-code", 0)); |
| 3373 | 3373 | zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); |
| 3374 | - fossil_print("admin-user: %s (initial password is \"%s\")\n", | |
| 3374 | + fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n", | |
| 3375 | 3375 | g.zLogin, zPassword); |
| 3376 | 3376 | hash_user_password(g.zLogin); |
| 3377 | 3377 | } |
| 3378 | 3378 | |
| 3379 | 3379 | /* |
| @@ -4758,11 +4758,11 @@ | ||
| 4758 | 4758 | ** SETTING: comment-format width=16 default=1 |
| 4759 | 4759 | ** Set the algorithm for printing timeline comments to the console. |
| 4760 | 4760 | ** |
| 4761 | 4761 | ** Possible values are: |
| 4762 | 4762 | ** 1 Use the original comment printing algorithm: |
| 4763 | -** * Leading and trialing whitespace is removed | |
| 4763 | +** * Leading and trailing whitespace is removed | |
| 4764 | 4764 | ** * Internal whitespace is converted into a single space (0x20) |
| 4765 | 4765 | ** * Line breaks occurs at whitespace or hyphens if possible |
| 4766 | 4766 | ** This is the recommended value and the default. |
| 4767 | 4767 | ** |
| 4768 | 4768 | ** Or a bitwise combination of the following flags: |
| 4769 | 4769 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3369,11 +3369,11 @@ | |
| 3369 | if( zProjectName ) fossil_print("project-name: %s\n", zProjectName); |
| 3370 | if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc); |
| 3371 | fossil_print("project-id: %s\n", db_get("project-code", 0)); |
| 3372 | fossil_print("server-id: %s\n", db_get("server-code", 0)); |
| 3373 | zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); |
| 3374 | fossil_print("admin-user: %s (initial password is \"%s\")\n", |
| 3375 | g.zLogin, zPassword); |
| 3376 | hash_user_password(g.zLogin); |
| 3377 | } |
| 3378 | |
| 3379 | /* |
| @@ -4758,11 +4758,11 @@ | |
| 4758 | ** SETTING: comment-format width=16 default=1 |
| 4759 | ** Set the algorithm for printing timeline comments to the console. |
| 4760 | ** |
| 4761 | ** Possible values are: |
| 4762 | ** 1 Use the original comment printing algorithm: |
| 4763 | ** * Leading and trialing whitespace is removed |
| 4764 | ** * Internal whitespace is converted into a single space (0x20) |
| 4765 | ** * Line breaks occurs at whitespace or hyphens if possible |
| 4766 | ** This is the recommended value and the default. |
| 4767 | ** |
| 4768 | ** Or a bitwise combination of the following flags: |
| 4769 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3369,11 +3369,11 @@ | |
| 3369 | if( zProjectName ) fossil_print("project-name: %s\n", zProjectName); |
| 3370 | if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc); |
| 3371 | fossil_print("project-id: %s\n", db_get("project-code", 0)); |
| 3372 | fossil_print("server-id: %s\n", db_get("server-code", 0)); |
| 3373 | zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); |
| 3374 | fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n", |
| 3375 | g.zLogin, zPassword); |
| 3376 | hash_user_password(g.zLogin); |
| 3377 | } |
| 3378 | |
| 3379 | /* |
| @@ -4758,11 +4758,11 @@ | |
| 4758 | ** SETTING: comment-format width=16 default=1 |
| 4759 | ** Set the algorithm for printing timeline comments to the console. |
| 4760 | ** |
| 4761 | ** Possible values are: |
| 4762 | ** 1 Use the original comment printing algorithm: |
| 4763 | ** * Leading and trailing whitespace is removed |
| 4764 | ** * Internal whitespace is converted into a single space (0x20) |
| 4765 | ** * Line breaks occurs at whitespace or hyphens if possible |
| 4766 | ** This is the recommended value and the default. |
| 4767 | ** |
| 4768 | ** Or a bitwise combination of the following flags: |
| 4769 |
+1
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -751,10 +751,11 @@ | ||
| 751 | 751 | border-left: 1px solid gold; |
| 752 | 752 | } |
| 753 | 753 | body.cpage-ckout .file-change-line, |
| 754 | 754 | body.cpage-info .file-change-line, |
| 755 | 755 | body.cpage-vinfo .file-change-line, |
| 756 | +body.cpage-ci .file-change-line, | |
| 756 | 757 | body.cpage-vdiff .file-change-line { |
| 757 | 758 | margin-top: 16px; |
| 758 | 759 | margin-bottom: 16px; |
| 759 | 760 | margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; |
| 760 | 761 | display: flex; |
| 761 | 762 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -751,10 +751,11 @@ | |
| 751 | border-left: 1px solid gold; |
| 752 | } |
| 753 | body.cpage-ckout .file-change-line, |
| 754 | body.cpage-info .file-change-line, |
| 755 | body.cpage-vinfo .file-change-line, |
| 756 | body.cpage-vdiff .file-change-line { |
| 757 | margin-top: 16px; |
| 758 | margin-bottom: 16px; |
| 759 | margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; |
| 760 | display: flex; |
| 761 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -751,10 +751,11 @@ | |
| 751 | border-left: 1px solid gold; |
| 752 | } |
| 753 | body.cpage-ckout .file-change-line, |
| 754 | body.cpage-info .file-change-line, |
| 755 | body.cpage-vinfo .file-change-line, |
| 756 | body.cpage-ci .file-change-line, |
| 757 | body.cpage-vdiff .file-change-line { |
| 758 | margin-top: 16px; |
| 759 | margin-bottom: 16px; |
| 760 | margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */; |
| 761 | display: flex; |
| 762 |
+1
-1
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -1052,11 +1052,11 @@ | ||
| 1052 | 1052 | */ |
| 1053 | 1053 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1054 | 1054 | if( zMime==0 ){ |
| 1055 | 1055 | zMime = mimetype_from_name(zName); |
| 1056 | 1056 | } |
| 1057 | - Th_Store("doc_name", zName); | |
| 1057 | + Th_StoreUnsafe("doc_name", zName); | |
| 1058 | 1058 | if( vid ){ |
| 1059 | 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1060 | 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1061 | 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1062 | 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1063 | 1063 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -1052,11 +1052,11 @@ | |
| 1052 | */ |
| 1053 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1054 | if( zMime==0 ){ |
| 1055 | zMime = mimetype_from_name(zName); |
| 1056 | } |
| 1057 | Th_Store("doc_name", zName); |
| 1058 | if( vid ){ |
| 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1063 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -1052,11 +1052,11 @@ | |
| 1052 | */ |
| 1053 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1054 | if( zMime==0 ){ |
| 1055 | zMime = mimetype_from_name(zName); |
| 1056 | } |
| 1057 | Th_StoreUnsafe("doc_name", zName); |
| 1058 | if( vid ){ |
| 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1063 |
+32
-5
| --- src/forum.c | ||
| +++ src/forum.c | ||
| @@ -59,10 +59,11 @@ | ||
| 59 | 59 | ForumPost *pFirst; /* First post in chronological order */ |
| 60 | 60 | ForumPost *pLast; /* Last post in chronological order */ |
| 61 | 61 | ForumPost *pDisplay; /* Entries in display order */ |
| 62 | 62 | ForumPost *pTail; /* Last on the display list */ |
| 63 | 63 | int mxIndent; /* Maximum indentation level */ |
| 64 | + int nArtifact; /* Number of forum artifacts in this thread */ | |
| 64 | 65 | }; |
| 65 | 66 | #endif /* INTERFACE */ |
| 66 | 67 | |
| 67 | 68 | /* |
| 68 | 69 | ** Return true if the forum post with the given rid has been |
| @@ -109,12 +110,17 @@ | ||
| 109 | 110 | ** the post. |
| 110 | 111 | ** |
| 111 | 112 | ** If bCheckIrt is true then p's thread in-response-to parents are |
| 112 | 113 | ** checked (recursively) for closure, else only p is checked. |
| 113 | 114 | */ |
| 114 | -static int forumpost_is_closed(ForumPost *p, int bCheckIrt){ | |
| 115 | - while(p){ | |
| 115 | +static int forumpost_is_closed( | |
| 116 | + ForumThread *pThread, /* Thread that the post is a member of */ | |
| 117 | + ForumPost *p, /* the forum post */ | |
| 118 | + int bCheckIrt /* True to check In-Reply-To posts */ | |
| 119 | +){ | |
| 120 | + int mx = pThread->nArtifact+1; | |
| 121 | + while( p && (mx--)>0 ){ | |
| 116 | 122 | if( p->pEditHead ) p = p->pEditHead; |
| 117 | 123 | if( p->iClosed || !bCheckIrt ) return p->iClosed; |
| 118 | 124 | p = p->pIrt; |
| 119 | 125 | } |
| 120 | 126 | return 0; |
| @@ -409,10 +415,11 @@ | ||
| 409 | 415 | pThread->pFirst = pPost; |
| 410 | 416 | }else{ |
| 411 | 417 | pThread->pLast->pNext = pPost; |
| 412 | 418 | } |
| 413 | 419 | pThread->pLast = pPost; |
| 420 | + pThread->nArtifact++; | |
| 414 | 421 | |
| 415 | 422 | /* Find the in-reply-to post. Default to the topic post if the replied-to |
| 416 | 423 | ** post cannot be found. */ |
| 417 | 424 | if( firt ){ |
| 418 | 425 | pPost->pIrt = pThread->pFirst; |
| @@ -520,10 +527,11 @@ | ||
| 520 | 527 | fossil_fatal("Not a forum post: \"%s\"", zName); |
| 521 | 528 | } |
| 522 | 529 | fossil_print("fpid = %d\n", fpid); |
| 523 | 530 | fossil_print("froot = %d\n", froot); |
| 524 | 531 | pThread = forumthread_create(froot, 1); |
| 532 | + fossil_print("count = %d\n", pThread->nArtifact); | |
| 525 | 533 | fossil_print("Chronological:\n"); |
| 526 | 534 | fossil_print( |
| 527 | 535 | /* 0 1 2 3 4 5 6 7 */ |
| 528 | 536 | /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */ |
| 529 | 537 | " sid rev closed fpid pIrt pEditPrev pEditTail hash\n"); |
| @@ -565,10 +573,11 @@ | ||
| 565 | 573 | int froot; |
| 566 | 574 | const char *zName = P("name"); |
| 567 | 575 | ForumThread *pThread; |
| 568 | 576 | ForumPost *p; |
| 569 | 577 | char *fuuid; |
| 578 | + Stmt q; | |
| 570 | 579 | |
| 571 | 580 | login_check_credentials(); |
| 572 | 581 | if( !g.perm.Admin ){ |
| 573 | 582 | return; |
| 574 | 583 | } |
| @@ -599,10 +608,27 @@ | ||
| 599 | 608 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 600 | 609 | @ %h(p->zUuid) |
| 601 | 610 | } |
| 602 | 611 | forumthread_delete(pThread); |
| 603 | 612 | @ </pre> |
| 613 | + @ <hr> | |
| 614 | + @ <h2>Related FORUMPOST Table Content</h2> | |
| 615 | + @ <table border="1" cellpadding="4" cellspacing="0"> | |
| 616 | + @ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime | |
| 617 | + db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)" | |
| 618 | + " FROM forumpost" | |
| 619 | + " WHERE froot=%d" | |
| 620 | + " ORDER BY fmtime", froot); | |
| 621 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 622 | + @ <tr><td>%d(db_column_int(&q,0))\ | |
| 623 | + @ <td>%d(db_column_int(&q,1))\ | |
| 624 | + @ <td>%d(db_column_int(&q,2))\ | |
| 625 | + @ <td>%d(db_column_int(&q,3))\ | |
| 626 | + @ <td>%h(db_column_text(&q,4))</tr> | |
| 627 | + } | |
| 628 | + @ </table> | |
| 629 | + db_finalize(&q); | |
| 604 | 630 | style_finish_page(); |
| 605 | 631 | } |
| 606 | 632 | |
| 607 | 633 | /* |
| 608 | 634 | ** Render a forum post for display |
| @@ -725,10 +751,11 @@ | ||
| 725 | 751 | |
| 726 | 752 | /* |
| 727 | 753 | ** Display a single post in a forum thread. |
| 728 | 754 | */ |
| 729 | 755 | static void forum_display_post( |
| 756 | + ForumThread *pThread, /* The thread that this post is a member of */ | |
| 730 | 757 | ForumPost *p, /* Forum post to display */ |
| 731 | 758 | int iIndentScale, /* Indent scale factor */ |
| 732 | 759 | int bRaw, /* True to omit the border */ |
| 733 | 760 | int bUnf, /* True to leave the post unformatted */ |
| 734 | 761 | int bHist, /* True if showing edit history */ |
| @@ -747,14 +774,14 @@ | ||
| 747 | 774 | const char *zMimetype;/* Formatting MIME type */ |
| 748 | 775 | |
| 749 | 776 | /* Get the manifest for the post. Abort if not found (e.g. shunned). */ |
| 750 | 777 | pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 751 | 778 | if( !pManifest ) return; |
| 752 | - iClosed = forumpost_is_closed(p, 1); | |
| 779 | + iClosed = forumpost_is_closed(pThread, p, 1); | |
| 753 | 780 | /* When not in raw mode, create the border around the post. */ |
| 754 | 781 | if( !bRaw ){ |
| 755 | - /* Open the <div> enclosing the post. Set the class string to mark the post | |
| 782 | + /* Open the <div> enclosing the post. Set the class string to mark the post | |
| 756 | 783 | ** as selected and/or obsolete. */ |
| 757 | 784 | iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; |
| 758 | 785 | @ <div id='forum%d(p->fpid)' class='forumTime\ |
| 759 | 786 | @ %s(bSelect ? " forumSel" : "")\ |
| 760 | 787 | @ %s(iClosed ? " forumClosed" : "")\ |
| @@ -1027,11 +1054,11 @@ | ||
| 1027 | 1054 | } |
| 1028 | 1055 | |
| 1029 | 1056 | /* Display the appropriate subset of posts in sequence. */ |
| 1030 | 1057 | while( p ){ |
| 1031 | 1058 | /* Display the post. */ |
| 1032 | - forum_display_post(p, iIndentScale, mode==FD_RAW, | |
| 1059 | + forum_display_post(pThread, p, iIndentScale, mode==FD_RAW, | |
| 1033 | 1060 | bUnf, bHist, p==pSelect, zQuery); |
| 1034 | 1061 | |
| 1035 | 1062 | /* Advance to the next post in the thread. */ |
| 1036 | 1063 | if( mode==FD_CHRONO ){ |
| 1037 | 1064 | /* Chronological mode: display posts (optionally including edits) in their |
| 1038 | 1065 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -59,10 +59,11 @@ | |
| 59 | ForumPost *pFirst; /* First post in chronological order */ |
| 60 | ForumPost *pLast; /* Last post in chronological order */ |
| 61 | ForumPost *pDisplay; /* Entries in display order */ |
| 62 | ForumPost *pTail; /* Last on the display list */ |
| 63 | int mxIndent; /* Maximum indentation level */ |
| 64 | }; |
| 65 | #endif /* INTERFACE */ |
| 66 | |
| 67 | /* |
| 68 | ** Return true if the forum post with the given rid has been |
| @@ -109,12 +110,17 @@ | |
| 109 | ** the post. |
| 110 | ** |
| 111 | ** If bCheckIrt is true then p's thread in-response-to parents are |
| 112 | ** checked (recursively) for closure, else only p is checked. |
| 113 | */ |
| 114 | static int forumpost_is_closed(ForumPost *p, int bCheckIrt){ |
| 115 | while(p){ |
| 116 | if( p->pEditHead ) p = p->pEditHead; |
| 117 | if( p->iClosed || !bCheckIrt ) return p->iClosed; |
| 118 | p = p->pIrt; |
| 119 | } |
| 120 | return 0; |
| @@ -409,10 +415,11 @@ | |
| 409 | pThread->pFirst = pPost; |
| 410 | }else{ |
| 411 | pThread->pLast->pNext = pPost; |
| 412 | } |
| 413 | pThread->pLast = pPost; |
| 414 | |
| 415 | /* Find the in-reply-to post. Default to the topic post if the replied-to |
| 416 | ** post cannot be found. */ |
| 417 | if( firt ){ |
| 418 | pPost->pIrt = pThread->pFirst; |
| @@ -520,10 +527,11 @@ | |
| 520 | fossil_fatal("Not a forum post: \"%s\"", zName); |
| 521 | } |
| 522 | fossil_print("fpid = %d\n", fpid); |
| 523 | fossil_print("froot = %d\n", froot); |
| 524 | pThread = forumthread_create(froot, 1); |
| 525 | fossil_print("Chronological:\n"); |
| 526 | fossil_print( |
| 527 | /* 0 1 2 3 4 5 6 7 */ |
| 528 | /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */ |
| 529 | " sid rev closed fpid pIrt pEditPrev pEditTail hash\n"); |
| @@ -565,10 +573,11 @@ | |
| 565 | int froot; |
| 566 | const char *zName = P("name"); |
| 567 | ForumThread *pThread; |
| 568 | ForumPost *p; |
| 569 | char *fuuid; |
| 570 | |
| 571 | login_check_credentials(); |
| 572 | if( !g.perm.Admin ){ |
| 573 | return; |
| 574 | } |
| @@ -599,10 +608,27 @@ | |
| 599 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 600 | @ %h(p->zUuid) |
| 601 | } |
| 602 | forumthread_delete(pThread); |
| 603 | @ </pre> |
| 604 | style_finish_page(); |
| 605 | } |
| 606 | |
| 607 | /* |
| 608 | ** Render a forum post for display |
| @@ -725,10 +751,11 @@ | |
| 725 | |
| 726 | /* |
| 727 | ** Display a single post in a forum thread. |
| 728 | */ |
| 729 | static void forum_display_post( |
| 730 | ForumPost *p, /* Forum post to display */ |
| 731 | int iIndentScale, /* Indent scale factor */ |
| 732 | int bRaw, /* True to omit the border */ |
| 733 | int bUnf, /* True to leave the post unformatted */ |
| 734 | int bHist, /* True if showing edit history */ |
| @@ -747,14 +774,14 @@ | |
| 747 | const char *zMimetype;/* Formatting MIME type */ |
| 748 | |
| 749 | /* Get the manifest for the post. Abort if not found (e.g. shunned). */ |
| 750 | pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 751 | if( !pManifest ) return; |
| 752 | iClosed = forumpost_is_closed(p, 1); |
| 753 | /* When not in raw mode, create the border around the post. */ |
| 754 | if( !bRaw ){ |
| 755 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 756 | ** as selected and/or obsolete. */ |
| 757 | iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; |
| 758 | @ <div id='forum%d(p->fpid)' class='forumTime\ |
| 759 | @ %s(bSelect ? " forumSel" : "")\ |
| 760 | @ %s(iClosed ? " forumClosed" : "")\ |
| @@ -1027,11 +1054,11 @@ | |
| 1027 | } |
| 1028 | |
| 1029 | /* Display the appropriate subset of posts in sequence. */ |
| 1030 | while( p ){ |
| 1031 | /* Display the post. */ |
| 1032 | forum_display_post(p, iIndentScale, mode==FD_RAW, |
| 1033 | bUnf, bHist, p==pSelect, zQuery); |
| 1034 | |
| 1035 | /* Advance to the next post in the thread. */ |
| 1036 | if( mode==FD_CHRONO ){ |
| 1037 | /* Chronological mode: display posts (optionally including edits) in their |
| 1038 |
| --- src/forum.c | |
| +++ src/forum.c | |
| @@ -59,10 +59,11 @@ | |
| 59 | ForumPost *pFirst; /* First post in chronological order */ |
| 60 | ForumPost *pLast; /* Last post in chronological order */ |
| 61 | ForumPost *pDisplay; /* Entries in display order */ |
| 62 | ForumPost *pTail; /* Last on the display list */ |
| 63 | int mxIndent; /* Maximum indentation level */ |
| 64 | int nArtifact; /* Number of forum artifacts in this thread */ |
| 65 | }; |
| 66 | #endif /* INTERFACE */ |
| 67 | |
| 68 | /* |
| 69 | ** Return true if the forum post with the given rid has been |
| @@ -109,12 +110,17 @@ | |
| 110 | ** the post. |
| 111 | ** |
| 112 | ** If bCheckIrt is true then p's thread in-response-to parents are |
| 113 | ** checked (recursively) for closure, else only p is checked. |
| 114 | */ |
| 115 | static int forumpost_is_closed( |
| 116 | ForumThread *pThread, /* Thread that the post is a member of */ |
| 117 | ForumPost *p, /* the forum post */ |
| 118 | int bCheckIrt /* True to check In-Reply-To posts */ |
| 119 | ){ |
| 120 | int mx = pThread->nArtifact+1; |
| 121 | while( p && (mx--)>0 ){ |
| 122 | if( p->pEditHead ) p = p->pEditHead; |
| 123 | if( p->iClosed || !bCheckIrt ) return p->iClosed; |
| 124 | p = p->pIrt; |
| 125 | } |
| 126 | return 0; |
| @@ -409,10 +415,11 @@ | |
| 415 | pThread->pFirst = pPost; |
| 416 | }else{ |
| 417 | pThread->pLast->pNext = pPost; |
| 418 | } |
| 419 | pThread->pLast = pPost; |
| 420 | pThread->nArtifact++; |
| 421 | |
| 422 | /* Find the in-reply-to post. Default to the topic post if the replied-to |
| 423 | ** post cannot be found. */ |
| 424 | if( firt ){ |
| 425 | pPost->pIrt = pThread->pFirst; |
| @@ -520,10 +527,11 @@ | |
| 527 | fossil_fatal("Not a forum post: \"%s\"", zName); |
| 528 | } |
| 529 | fossil_print("fpid = %d\n", fpid); |
| 530 | fossil_print("froot = %d\n", froot); |
| 531 | pThread = forumthread_create(froot, 1); |
| 532 | fossil_print("count = %d\n", pThread->nArtifact); |
| 533 | fossil_print("Chronological:\n"); |
| 534 | fossil_print( |
| 535 | /* 0 1 2 3 4 5 6 7 */ |
| 536 | /* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */ |
| 537 | " sid rev closed fpid pIrt pEditPrev pEditTail hash\n"); |
| @@ -565,10 +573,11 @@ | |
| 573 | int froot; |
| 574 | const char *zName = P("name"); |
| 575 | ForumThread *pThread; |
| 576 | ForumPost *p; |
| 577 | char *fuuid; |
| 578 | Stmt q; |
| 579 | |
| 580 | login_check_credentials(); |
| 581 | if( !g.perm.Admin ){ |
| 582 | return; |
| 583 | } |
| @@ -599,10 +608,27 @@ | |
| 608 | for(p=pThread->pFirst; p; p=p->pNext){ |
| 609 | @ %h(p->zUuid) |
| 610 | } |
| 611 | forumthread_delete(pThread); |
| 612 | @ </pre> |
| 613 | @ <hr> |
| 614 | @ <h2>Related FORUMPOST Table Content</h2> |
| 615 | @ <table border="1" cellpadding="4" cellspacing="0"> |
| 616 | @ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime |
| 617 | db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)" |
| 618 | " FROM forumpost" |
| 619 | " WHERE froot=%d" |
| 620 | " ORDER BY fmtime", froot); |
| 621 | while( db_step(&q)==SQLITE_ROW ){ |
| 622 | @ <tr><td>%d(db_column_int(&q,0))\ |
| 623 | @ <td>%d(db_column_int(&q,1))\ |
| 624 | @ <td>%d(db_column_int(&q,2))\ |
| 625 | @ <td>%d(db_column_int(&q,3))\ |
| 626 | @ <td>%h(db_column_text(&q,4))</tr> |
| 627 | } |
| 628 | @ </table> |
| 629 | db_finalize(&q); |
| 630 | style_finish_page(); |
| 631 | } |
| 632 | |
| 633 | /* |
| 634 | ** Render a forum post for display |
| @@ -725,10 +751,11 @@ | |
| 751 | |
| 752 | /* |
| 753 | ** Display a single post in a forum thread. |
| 754 | */ |
| 755 | static void forum_display_post( |
| 756 | ForumThread *pThread, /* The thread that this post is a member of */ |
| 757 | ForumPost *p, /* Forum post to display */ |
| 758 | int iIndentScale, /* Indent scale factor */ |
| 759 | int bRaw, /* True to omit the border */ |
| 760 | int bUnf, /* True to leave the post unformatted */ |
| 761 | int bHist, /* True if showing edit history */ |
| @@ -747,14 +774,14 @@ | |
| 774 | const char *zMimetype;/* Formatting MIME type */ |
| 775 | |
| 776 | /* Get the manifest for the post. Abort if not found (e.g. shunned). */ |
| 777 | pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); |
| 778 | if( !pManifest ) return; |
| 779 | iClosed = forumpost_is_closed(pThread, p, 1); |
| 780 | /* When not in raw mode, create the border around the post. */ |
| 781 | if( !bRaw ){ |
| 782 | /* Open the <div> enclosing the post. Set the class string to mark the post |
| 783 | ** as selected and/or obsolete. */ |
| 784 | iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; |
| 785 | @ <div id='forum%d(p->fpid)' class='forumTime\ |
| 786 | @ %s(bSelect ? " forumSel" : "")\ |
| 787 | @ %s(iClosed ? " forumClosed" : "")\ |
| @@ -1027,11 +1054,11 @@ | |
| 1054 | } |
| 1055 | |
| 1056 | /* Display the appropriate subset of posts in sequence. */ |
| 1057 | while( p ){ |
| 1058 | /* Display the post. */ |
| 1059 | forum_display_post(pThread, p, iIndentScale, mode==FD_RAW, |
| 1060 | bUnf, bHist, p==pSelect, zQuery); |
| 1061 | |
| 1062 | /* Advance to the next post in the thread. */ |
| 1063 | if( mode==FD_CHRONO ){ |
| 1064 | /* Chronological mode: display posts (optionally including edits) in their |
| 1065 |
+1
-1
| --- src/graph.c | ||
| +++ src/graph.c | ||
| @@ -121,11 +121,11 @@ | ||
| 121 | 121 | u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different |
| 122 | 122 | ** rail that the node */ |
| 123 | 123 | u8 bOverfull; /* Unable to allocate sufficient rails */ |
| 124 | 124 | u64 mergeRail; /* Rails used for merge lines */ |
| 125 | 125 | GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ |
| 126 | - u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */ | |
| 126 | + u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */ | |
| 127 | 127 | }; |
| 128 | 128 | |
| 129 | 129 | #endif |
| 130 | 130 | |
| 131 | 131 | /* The N-th bit */ |
| 132 | 132 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -121,11 +121,11 @@ | |
| 121 | u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different |
| 122 | ** rail that the node */ |
| 123 | u8 bOverfull; /* Unable to allocate sufficient rails */ |
| 124 | u64 mergeRail; /* Rails used for merge lines */ |
| 125 | GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ |
| 126 | u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */ |
| 127 | }; |
| 128 | |
| 129 | #endif |
| 130 | |
| 131 | /* The N-th bit */ |
| 132 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -121,11 +121,11 @@ | |
| 121 | u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different |
| 122 | ** rail that the node */ |
| 123 | u8 bOverfull; /* Unable to allocate sufficient rails */ |
| 124 | u64 mergeRail; /* Rails used for merge lines */ |
| 125 | GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ |
| 126 | u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */ |
| 127 | }; |
| 128 | |
| 129 | #endif |
| 130 | |
| 131 | /* The N-th bit */ |
| 132 |
+12
-12
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -319,11 +319,13 @@ | ||
| 319 | 319 | ** The following OpenSSL configuration options must not be used for this feature |
| 320 | 320 | ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not |
| 321 | 321 | ** currently set these options when building OpenSSL for Windows. */ |
| 322 | 322 | #if defined(_WIN32) |
| 323 | 323 | #if OPENSSL_VERSION_NUMBER >= 0x030200000 |
| 324 | - if( SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 ){ | |
| 324 | + if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */ | |
| 325 | + && SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 | |
| 326 | + ){ | |
| 325 | 327 | fossil_print("NOTICE: Failed to load the Windows root certificates.\n"); |
| 326 | 328 | } |
| 327 | 329 | #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
| 328 | 330 | #endif /* _WIN32 */ |
| 329 | 331 | |
| @@ -999,12 +1001,12 @@ | ||
| 999 | 1001 | fossil_print("\n" |
| 1000 | 1002 | " The OpenSSL library is not used by this build of Fossil\n\n" |
| 1001 | 1003 | ); |
| 1002 | 1004 | } |
| 1003 | 1005 | #else |
| 1004 | - fossil_print("OpenSSL-version: %s (0x%09x)\n", | |
| 1005 | - SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER); | |
| 1006 | + fossil_print("OpenSSL-version: %s (0x%09llx)\n", | |
| 1007 | + SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay()); | |
| 1006 | 1008 | if( verbose ){ |
| 1007 | 1009 | fossil_print("\n" |
| 1008 | 1010 | " The version of the OpenSSL library being used\n" |
| 1009 | 1011 | " by this instance of Fossil. Version 3.0.0 or\n" |
| 1010 | 1012 | " later is recommended.\n\n" |
| @@ -1061,20 +1063,18 @@ | ||
| 1061 | 1063 | " values are built into your OpenSSL library.\n\n" |
| 1062 | 1064 | ); |
| 1063 | 1065 | } |
| 1064 | 1066 | |
| 1065 | 1067 | #if defined(_WIN32) |
| 1066 | -#if OPENSSL_VERSION_NUMBER >= 0x030200000 | |
| 1067 | - fossil_print(" OpenSSL-winstore: Yes\n"); | |
| 1068 | -#else /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ | |
| 1069 | - fossil_print(" OpenSSL-winstore: No\n"); | |
| 1070 | -#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ | |
| 1068 | + fossil_print(" OpenSSL-winstore: %s\n", | |
| 1069 | + (SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No"); | |
| 1071 | 1070 | if( verbose ){ |
| 1072 | 1071 | fossil_print("\n" |
| 1073 | - " OpenSSL 3.2.0, or newer, use the root certificates managed by\n" | |
| 1074 | - " the Windows operating system. The installed root certificates\n" | |
| 1075 | - " are listed by the command:\n\n" | |
| 1072 | + " OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n" | |
| 1073 | + " are able to use the root certificates managed by the Windows\n" | |
| 1074 | + " operating system. The installed root certificates are listed\n" | |
| 1075 | + " by the command:\n\n" | |
| 1076 | 1076 | " certutil -store \"ROOT\"\n\n" |
| 1077 | 1077 | ); |
| 1078 | 1078 | } |
| 1079 | 1079 | #endif /* _WIN32 */ |
| 1080 | 1080 | |
| @@ -1232,10 +1232,10 @@ | ||
| 1232 | 1232 | ** freed by the caller. |
| 1233 | 1233 | */ |
| 1234 | 1234 | char *fossil_openssl_version(void){ |
| 1235 | 1235 | #if defined(FOSSIL_ENABLE_SSL) |
| 1236 | 1236 | return mprintf("%s (0x%09x)\n", |
| 1237 | - SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER); | |
| 1237 | + SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay()); | |
| 1238 | 1238 | #else |
| 1239 | 1239 | return mprintf("none"); |
| 1240 | 1240 | #endif |
| 1241 | 1241 | } |
| 1242 | 1242 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -319,11 +319,13 @@ | |
| 319 | ** The following OpenSSL configuration options must not be used for this feature |
| 320 | ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not |
| 321 | ** currently set these options when building OpenSSL for Windows. */ |
| 322 | #if defined(_WIN32) |
| 323 | #if OPENSSL_VERSION_NUMBER >= 0x030200000 |
| 324 | if( SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 ){ |
| 325 | fossil_print("NOTICE: Failed to load the Windows root certificates.\n"); |
| 326 | } |
| 327 | #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
| 328 | #endif /* _WIN32 */ |
| 329 | |
| @@ -999,12 +1001,12 @@ | |
| 999 | fossil_print("\n" |
| 1000 | " The OpenSSL library is not used by this build of Fossil\n\n" |
| 1001 | ); |
| 1002 | } |
| 1003 | #else |
| 1004 | fossil_print("OpenSSL-version: %s (0x%09x)\n", |
| 1005 | SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER); |
| 1006 | if( verbose ){ |
| 1007 | fossil_print("\n" |
| 1008 | " The version of the OpenSSL library being used\n" |
| 1009 | " by this instance of Fossil. Version 3.0.0 or\n" |
| 1010 | " later is recommended.\n\n" |
| @@ -1061,20 +1063,18 @@ | |
| 1061 | " values are built into your OpenSSL library.\n\n" |
| 1062 | ); |
| 1063 | } |
| 1064 | |
| 1065 | #if defined(_WIN32) |
| 1066 | #if OPENSSL_VERSION_NUMBER >= 0x030200000 |
| 1067 | fossil_print(" OpenSSL-winstore: Yes\n"); |
| 1068 | #else /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
| 1069 | fossil_print(" OpenSSL-winstore: No\n"); |
| 1070 | #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
| 1071 | if( verbose ){ |
| 1072 | fossil_print("\n" |
| 1073 | " OpenSSL 3.2.0, or newer, use the root certificates managed by\n" |
| 1074 | " the Windows operating system. The installed root certificates\n" |
| 1075 | " are listed by the command:\n\n" |
| 1076 | " certutil -store \"ROOT\"\n\n" |
| 1077 | ); |
| 1078 | } |
| 1079 | #endif /* _WIN32 */ |
| 1080 | |
| @@ -1232,10 +1232,10 @@ | |
| 1232 | ** freed by the caller. |
| 1233 | */ |
| 1234 | char *fossil_openssl_version(void){ |
| 1235 | #if defined(FOSSIL_ENABLE_SSL) |
| 1236 | return mprintf("%s (0x%09x)\n", |
| 1237 | SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER); |
| 1238 | #else |
| 1239 | return mprintf("none"); |
| 1240 | #endif |
| 1241 | } |
| 1242 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -319,11 +319,13 @@ | |
| 319 | ** The following OpenSSL configuration options must not be used for this feature |
| 320 | ** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not |
| 321 | ** currently set these options when building OpenSSL for Windows. */ |
| 322 | #if defined(_WIN32) |
| 323 | #if OPENSSL_VERSION_NUMBER >= 0x030200000 |
| 324 | if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */ |
| 325 | && SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 |
| 326 | ){ |
| 327 | fossil_print("NOTICE: Failed to load the Windows root certificates.\n"); |
| 328 | } |
| 329 | #endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
| 330 | #endif /* _WIN32 */ |
| 331 | |
| @@ -999,12 +1001,12 @@ | |
| 1001 | fossil_print("\n" |
| 1002 | " The OpenSSL library is not used by this build of Fossil\n\n" |
| 1003 | ); |
| 1004 | } |
| 1005 | #else |
| 1006 | fossil_print("OpenSSL-version: %s (0x%09llx)\n", |
| 1007 | SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay()); |
| 1008 | if( verbose ){ |
| 1009 | fossil_print("\n" |
| 1010 | " The version of the OpenSSL library being used\n" |
| 1011 | " by this instance of Fossil. Version 3.0.0 or\n" |
| 1012 | " later is recommended.\n\n" |
| @@ -1061,20 +1063,18 @@ | |
| 1063 | " values are built into your OpenSSL library.\n\n" |
| 1064 | ); |
| 1065 | } |
| 1066 | |
| 1067 | #if defined(_WIN32) |
| 1068 | fossil_print(" OpenSSL-winstore: %s\n", |
| 1069 | (SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No"); |
| 1070 | if( verbose ){ |
| 1071 | fossil_print("\n" |
| 1072 | " OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n" |
| 1073 | " are able to use the root certificates managed by the Windows\n" |
| 1074 | " operating system. The installed root certificates are listed\n" |
| 1075 | " by the command:\n\n" |
| 1076 | " certutil -store \"ROOT\"\n\n" |
| 1077 | ); |
| 1078 | } |
| 1079 | #endif /* _WIN32 */ |
| 1080 | |
| @@ -1232,10 +1232,10 @@ | |
| 1232 | ** freed by the caller. |
| 1233 | */ |
| 1234 | char *fossil_openssl_version(void){ |
| 1235 | #if defined(FOSSIL_ENABLE_SSL) |
| 1236 | return mprintf("%s (0x%09x)\n", |
| 1237 | SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay()); |
| 1238 | #else |
| 1239 | return mprintf("none"); |
| 1240 | #endif |
| 1241 | } |
| 1242 |
+7
-5
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -951,11 +951,11 @@ | ||
| 951 | 951 | const char *zOrigDate; |
| 952 | 952 | int okWiki = 0; |
| 953 | 953 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 954 | 954 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 955 | 955 | |
| 956 | - Th_Store("current_checkin", zName); | |
| 956 | + Th_StoreUnsafe("current_checkin", zName); | |
| 957 | 957 | style_header("Check-in [%S]", zUuid); |
| 958 | 958 | login_anonymous_available(); |
| 959 | 959 | zEUser = db_text(0, |
| 960 | 960 | "SELECT value FROM tagxref" |
| 961 | 961 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| @@ -993,12 +993,14 @@ | ||
| 993 | 993 | } |
| 994 | 994 | zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); |
| 995 | 995 | @ <tr><th>Downloads:</th><td> |
| 996 | 996 | @ %z(href("%s",zUrl))Tarball</a> |
| 997 | 997 | @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> |
| 998 | - @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ | |
| 999 | - @ SQL archive</a></td></tr> | |
| 998 | + if( g.zLogin!=0 ){ | |
| 999 | + @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ | |
| 1000 | + @ SQL archive</a></td></tr> | |
| 1001 | + } | |
| 1000 | 1002 | fossil_free(zUrl); |
| 1001 | 1003 | blob_reset(&projName); |
| 1002 | 1004 | } |
| 1003 | 1005 | |
| 1004 | 1006 | @ <tr><th>Timelines:</th><td> |
| @@ -1182,14 +1184,14 @@ | ||
| 1182 | 1184 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 1183 | 1185 | @ Side-by-Side Diff</a> |
| 1184 | 1186 | } |
| 1185 | 1187 | if( diffType!=0 ){ |
| 1186 | 1188 | if( *zW ){ |
| 1187 | - @ %z(chref("button","%R/%s/%T",zPage,zName)) | |
| 1189 | + @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType)) | |
| 1188 | 1190 | @ Show Whitespace Changes</a> |
| 1189 | 1191 | }else{ |
| 1190 | - @ %z(chref("button","%R/%s/%T?w",zPage,zName)) | |
| 1192 | + @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType)) | |
| 1191 | 1193 | @ Ignore Whitespace</a> |
| 1192 | 1194 | } |
| 1193 | 1195 | } |
| 1194 | 1196 | if( zParent ){ |
| 1195 | 1197 | @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid)) |
| 1196 | 1198 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -951,11 +951,11 @@ | |
| 951 | const char *zOrigDate; |
| 952 | int okWiki = 0; |
| 953 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 954 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 955 | |
| 956 | Th_Store("current_checkin", zName); |
| 957 | style_header("Check-in [%S]", zUuid); |
| 958 | login_anonymous_available(); |
| 959 | zEUser = db_text(0, |
| 960 | "SELECT value FROM tagxref" |
| 961 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| @@ -993,12 +993,14 @@ | |
| 993 | } |
| 994 | zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); |
| 995 | @ <tr><th>Downloads:</th><td> |
| 996 | @ %z(href("%s",zUrl))Tarball</a> |
| 997 | @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> |
| 998 | @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ |
| 999 | @ SQL archive</a></td></tr> |
| 1000 | fossil_free(zUrl); |
| 1001 | blob_reset(&projName); |
| 1002 | } |
| 1003 | |
| 1004 | @ <tr><th>Timelines:</th><td> |
| @@ -1182,14 +1184,14 @@ | |
| 1182 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 1183 | @ Side-by-Side Diff</a> |
| 1184 | } |
| 1185 | if( diffType!=0 ){ |
| 1186 | if( *zW ){ |
| 1187 | @ %z(chref("button","%R/%s/%T",zPage,zName)) |
| 1188 | @ Show Whitespace Changes</a> |
| 1189 | }else{ |
| 1190 | @ %z(chref("button","%R/%s/%T?w",zPage,zName)) |
| 1191 | @ Ignore Whitespace</a> |
| 1192 | } |
| 1193 | } |
| 1194 | if( zParent ){ |
| 1195 | @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid)) |
| 1196 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -951,11 +951,11 @@ | |
| 951 | const char *zOrigDate; |
| 952 | int okWiki = 0; |
| 953 | Blob wiki_read_links = BLOB_INITIALIZER; |
| 954 | Blob wiki_add_links = BLOB_INITIALIZER; |
| 955 | |
| 956 | Th_StoreUnsafe("current_checkin", zName); |
| 957 | style_header("Check-in [%S]", zUuid); |
| 958 | login_anonymous_available(); |
| 959 | zEUser = db_text(0, |
| 960 | "SELECT value FROM tagxref" |
| 961 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| @@ -993,12 +993,14 @@ | |
| 993 | } |
| 994 | zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); |
| 995 | @ <tr><th>Downloads:</th><td> |
| 996 | @ %z(href("%s",zUrl))Tarball</a> |
| 997 | @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> |
| 998 | if( g.zLogin!=0 ){ |
| 999 | @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ |
| 1000 | @ SQL archive</a></td></tr> |
| 1001 | } |
| 1002 | fossil_free(zUrl); |
| 1003 | blob_reset(&projName); |
| 1004 | } |
| 1005 | |
| 1006 | @ <tr><th>Timelines:</th><td> |
| @@ -1182,14 +1184,14 @@ | |
| 1184 | @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\ |
| 1185 | @ Side-by-Side Diff</a> |
| 1186 | } |
| 1187 | if( diffType!=0 ){ |
| 1188 | if( *zW ){ |
| 1189 | @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType)) |
| 1190 | @ Show Whitespace Changes</a> |
| 1191 | }else{ |
| 1192 | @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType)) |
| 1193 | @ Ignore Whitespace</a> |
| 1194 | } |
| 1195 | } |
| 1196 | if( zParent ){ |
| 1197 | @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid)) |
| 1198 |
+21
-8
| --- src/loadctrl.c | ||
| +++ src/loadctrl.c | ||
| @@ -43,10 +43,30 @@ | ||
| 43 | 43 | ** Print the load average on the host machine. |
| 44 | 44 | */ |
| 45 | 45 | void loadavg_test_cmd(void){ |
| 46 | 46 | fossil_print("load-average: %f\n", load_average()); |
| 47 | 47 | } |
| 48 | + | |
| 49 | +/* | |
| 50 | +** WEBPAGE: test-overload | |
| 51 | +** | |
| 52 | +** Generate the response that would normally be shown only when | |
| 53 | +** service is denied due to an overload condition. This is for | |
| 54 | +** testing of the overload warning page. | |
| 55 | +*/ | |
| 56 | +void overload_page(void){ | |
| 57 | + double mxLoad = atof(db_get("max-loadavg", "0.0")); | |
| 58 | + style_set_current_feature("test"); | |
| 59 | + style_header("Server Overload"); | |
| 60 | + @ <h2>The server load is currently too high. | |
| 61 | + @ Please try again later.</h2> | |
| 62 | + @ <p>Current load average: %f(load_average())<br> | |
| 63 | + @ Load average limit: %f(mxLoad)<br> | |
| 64 | + @ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br> | |
| 65 | + @ Timestamp: %h(db_text("","SELECT datetime()"))Z</p> | |
| 66 | + style_finish_page(); | |
| 67 | +} | |
| 48 | 68 | |
| 49 | 69 | /* |
| 50 | 70 | ** Abort the current page request if the load average of the host |
| 51 | 71 | ** computer is too high. Admin and Setup users are exempt from this |
| 52 | 72 | ** restriction. |
| @@ -60,17 +80,10 @@ | ||
| 60 | 80 | login_check_credentials(); |
| 61 | 81 | if(g.perm.Admin || g.perm.Setup){ |
| 62 | 82 | return; |
| 63 | 83 | } |
| 64 | 84 | #endif |
| 65 | - | |
| 66 | - style_set_current_feature("test"); | |
| 67 | - style_header("Server Overload"); | |
| 68 | - @ <h2>The server load is currently too high. | |
| 69 | - @ Please try again later.</h2> | |
| 70 | - @ <p>Current load average: %f(load_average()).<br> | |
| 71 | - @ Load average limit: %f(mxLoad)</p> | |
| 72 | - style_finish_page(); | |
| 85 | + overload_page(); | |
| 73 | 86 | cgi_set_status(503,"Server Overload"); |
| 74 | 87 | cgi_reply(); |
| 75 | 88 | exit(0); |
| 76 | 89 | } |
| 77 | 90 |
| --- src/loadctrl.c | |
| +++ src/loadctrl.c | |
| @@ -43,10 +43,30 @@ | |
| 43 | ** Print the load average on the host machine. |
| 44 | */ |
| 45 | void loadavg_test_cmd(void){ |
| 46 | fossil_print("load-average: %f\n", load_average()); |
| 47 | } |
| 48 | |
| 49 | /* |
| 50 | ** Abort the current page request if the load average of the host |
| 51 | ** computer is too high. Admin and Setup users are exempt from this |
| 52 | ** restriction. |
| @@ -60,17 +80,10 @@ | |
| 60 | login_check_credentials(); |
| 61 | if(g.perm.Admin || g.perm.Setup){ |
| 62 | return; |
| 63 | } |
| 64 | #endif |
| 65 | |
| 66 | style_set_current_feature("test"); |
| 67 | style_header("Server Overload"); |
| 68 | @ <h2>The server load is currently too high. |
| 69 | @ Please try again later.</h2> |
| 70 | @ <p>Current load average: %f(load_average()).<br> |
| 71 | @ Load average limit: %f(mxLoad)</p> |
| 72 | style_finish_page(); |
| 73 | cgi_set_status(503,"Server Overload"); |
| 74 | cgi_reply(); |
| 75 | exit(0); |
| 76 | } |
| 77 |
| --- src/loadctrl.c | |
| +++ src/loadctrl.c | |
| @@ -43,10 +43,30 @@ | |
| 43 | ** Print the load average on the host machine. |
| 44 | */ |
| 45 | void loadavg_test_cmd(void){ |
| 46 | fossil_print("load-average: %f\n", load_average()); |
| 47 | } |
| 48 | |
| 49 | /* |
| 50 | ** WEBPAGE: test-overload |
| 51 | ** |
| 52 | ** Generate the response that would normally be shown only when |
| 53 | ** service is denied due to an overload condition. This is for |
| 54 | ** testing of the overload warning page. |
| 55 | */ |
| 56 | void overload_page(void){ |
| 57 | double mxLoad = atof(db_get("max-loadavg", "0.0")); |
| 58 | style_set_current_feature("test"); |
| 59 | style_header("Server Overload"); |
| 60 | @ <h2>The server load is currently too high. |
| 61 | @ Please try again later.</h2> |
| 62 | @ <p>Current load average: %f(load_average())<br> |
| 63 | @ Load average limit: %f(mxLoad)<br> |
| 64 | @ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br> |
| 65 | @ Timestamp: %h(db_text("","SELECT datetime()"))Z</p> |
| 66 | style_finish_page(); |
| 67 | } |
| 68 | |
| 69 | /* |
| 70 | ** Abort the current page request if the load average of the host |
| 71 | ** computer is too high. Admin and Setup users are exempt from this |
| 72 | ** restriction. |
| @@ -60,17 +80,10 @@ | |
| 80 | login_check_credentials(); |
| 81 | if(g.perm.Admin || g.perm.Setup){ |
| 82 | return; |
| 83 | } |
| 84 | #endif |
| 85 | overload_page(); |
| 86 | cgi_set_status(503,"Server Overload"); |
| 87 | cgi_reply(); |
| 88 | exit(0); |
| 89 | } |
| 90 |
+1
-1
| --- src/login.c | ||
| +++ src/login.c | ||
| @@ -1416,11 +1416,11 @@ | ||
| 1416 | 1416 | */ |
| 1417 | 1417 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 1418 | 1418 | if( ( cgi_is_loopback(zIpAddr) |
| 1419 | 1419 | || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
| 1420 | 1420 | && g.useLocalauth |
| 1421 | - && db_get_int("localauth",0)==0 | |
| 1421 | + && db_get_boolean("localauth",0)==0 | |
| 1422 | 1422 | && P("HTTPS")==0 |
| 1423 | 1423 | ){ |
| 1424 | 1424 | char *zSeed; |
| 1425 | 1425 | if( g.localOpen ) zLogin = db_lget("default-user",0); |
| 1426 | 1426 | if( zLogin!=0 ){ |
| 1427 | 1427 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -1416,11 +1416,11 @@ | |
| 1416 | */ |
| 1417 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 1418 | if( ( cgi_is_loopback(zIpAddr) |
| 1419 | || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
| 1420 | && g.useLocalauth |
| 1421 | && db_get_int("localauth",0)==0 |
| 1422 | && P("HTTPS")==0 |
| 1423 | ){ |
| 1424 | char *zSeed; |
| 1425 | if( g.localOpen ) zLogin = db_lget("default-user",0); |
| 1426 | if( zLogin!=0 ){ |
| 1427 |
| --- src/login.c | |
| +++ src/login.c | |
| @@ -1416,11 +1416,11 @@ | |
| 1416 | */ |
| 1417 | zIpAddr = PD("REMOTE_ADDR","nil"); |
| 1418 | if( ( cgi_is_loopback(zIpAddr) |
| 1419 | || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) |
| 1420 | && g.useLocalauth |
| 1421 | && db_get_boolean("localauth",0)==0 |
| 1422 | && P("HTTPS")==0 |
| 1423 | ){ |
| 1424 | char *zSeed; |
| 1425 | if( g.localOpen ) zLogin = db_lget("default-user",0); |
| 1426 | if( zLogin!=0 ){ |
| 1427 |
+39
-11
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -2365,11 +2365,18 @@ | ||
| 2365 | 2365 | ** notfound: URL When in "directory:" mode, redirect to |
| 2366 | 2366 | ** URL if no suitable repository is found. |
| 2367 | 2367 | ** |
| 2368 | 2368 | ** repolist When in "directory:" mode, display a page |
| 2369 | 2369 | ** showing a list of available repositories if |
| 2370 | -** the URL is "/". | |
| 2370 | +** the URL is "/". Some control over the display | |
| 2371 | +** is accomplished using environment variables. | |
| 2372 | +** FOSSIL_REPOLIST_TITLE is the tital of the page. | |
| 2373 | +** FOSSIL_REPOLIST_SHOW cause the "Description" | |
| 2374 | +** column to display if it contains "description" as | |
| 2375 | +** as a substring, and causes the Login-Group column | |
| 2376 | +** to display if it contains the "login-group" | |
| 2377 | +** substring. | |
| 2371 | 2378 | ** |
| 2372 | 2379 | ** localauth Grant administrator privileges to connections |
| 2373 | 2380 | ** from 127.0.0.1 or ::1. |
| 2374 | 2381 | ** |
| 2375 | 2382 | ** nossl Signal that no SSL connections are available. |
| @@ -3523,11 +3530,12 @@ | ||
| 3523 | 3530 | } |
| 3524 | 3531 | blob_append_escaped_arg(&ssh, "fossil", 1); |
| 3525 | 3532 | }else{ |
| 3526 | 3533 | blob_appendf(&ssh, " %$", zFossilCmd); |
| 3527 | 3534 | } |
| 3528 | - blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort); | |
| 3535 | + blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d", | |
| 3536 | + iPort); | |
| 3529 | 3537 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3530 | 3538 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3531 | 3539 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3532 | 3540 | if( zExtPage ){ |
| 3533 | 3541 | if( !file_is_absolute_path(zExtPage) ){ |
| @@ -3710,10 +3718,13 @@ | ||
| 3710 | 3718 | ** case=3 Extra db_end_transaction() |
| 3711 | 3719 | ** case=4 Error during SQL processing |
| 3712 | 3720 | ** case=5 Call the segfault handler |
| 3713 | 3721 | ** case=6 Call webpage_assert() |
| 3714 | 3722 | ** case=7 Call webpage_error() |
| 3723 | +** case=8 Simulate a timeout | |
| 3724 | +** case=9 Simulate a TH1 XSS vulnerability | |
| 3725 | +** case=10 Simulate a TH1 SQL-injection vulnerability | |
| 3715 | 3726 | */ |
| 3716 | 3727 | void test_warning_page(void){ |
| 3717 | 3728 | int iCase = atoi(PD("case","0")); |
| 3718 | 3729 | int i; |
| 3719 | 3730 | login_check_credentials(); |
| @@ -3722,17 +3733,15 @@ | ||
| 3722 | 3733 | return; |
| 3723 | 3734 | } |
| 3724 | 3735 | style_set_current_feature("test"); |
| 3725 | 3736 | style_header("Warning Test Page"); |
| 3726 | 3737 | style_submenu_element("Error Log","%R/errorlog"); |
| 3727 | - if( iCase<1 || iCase>4 ){ | |
| 3728 | - @ <p>Generate a message to the <a href="%R/errorlog">error log</a> | |
| 3729 | - @ by clicking on one of the following cases: | |
| 3730 | - }else{ | |
| 3731 | - @ <p>This is the test page for case=%d(iCase). All possible cases: | |
| 3732 | - } | |
| 3733 | - for(i=1; i<=8; i++){ | |
| 3738 | + @ <p>This page will generate various kinds of errors to test Fossil's | |
| 3739 | + @ reaction. Depending on settings, a message might be written | |
| 3740 | + @ into the <a href="%R/errorlog">error log</a>. Click on | |
| 3741 | + @ one of the following hyperlinks to generate a simulated error: | |
| 3742 | + for(i=1; i<=10; i++){ | |
| 3734 | 3743 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3735 | 3744 | } |
| 3736 | 3745 | @ </p> |
| 3737 | 3746 | @ <p><ol> |
| 3738 | 3747 | @ <li value='1'> Call fossil_warning() |
| @@ -3761,20 +3770,39 @@ | ||
| 3761 | 3770 | } |
| 3762 | 3771 | @ <li value='6'> call webpage_assert(0) |
| 3763 | 3772 | if( iCase==6 ){ |
| 3764 | 3773 | webpage_assert( 5==7 ); |
| 3765 | 3774 | } |
| 3766 | - @ <li value='7'> call webpage_error()" | |
| 3775 | + @ <li value='7'> call webpage_error() | |
| 3767 | 3776 | if( iCase==7 ){ |
| 3768 | 3777 | cgi_reset_content(); |
| 3769 | 3778 | webpage_error("Case 7 from /test-warning"); |
| 3770 | 3779 | } |
| 3771 | - @ <li value='8'> simulated timeout" | |
| 3780 | + @ <li value='8'> simulated timeout | |
| 3772 | 3781 | if( iCase==8 ){ |
| 3773 | 3782 | fossil_set_timeout(1); |
| 3774 | 3783 | cgi_reset_content(); |
| 3775 | 3784 | sqlite3_sleep(1100); |
| 3785 | + } | |
| 3786 | + @ <li value='9'> simulated TH1 XSS vulnerability | |
| 3787 | + @ <li value='10'> simulated TH1 SQL-injection vulnerability | |
| 3788 | + if( iCase==9 || iCase==10 ){ | |
| 3789 | + const char *zR; | |
| 3790 | + int n, rc; | |
| 3791 | + static const char *zTH1[] = { | |
| 3792 | + /* case 9 */ "html [taint {<b>XSS</b>}]", | |
| 3793 | + /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n" | |
| 3794 | + " html \"<b>[htmlize $msg]</b>\"\n" | |
| 3795 | + "}" | |
| 3796 | + }; | |
| 3797 | + rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1); | |
| 3798 | + zR = Th_GetResult(g.interp, &n); | |
| 3799 | + if( rc==TH_OK ){ | |
| 3800 | + @ <pre class="th1result">%h(zR)</pre> | |
| 3801 | + }else{ | |
| 3802 | + @ <pre class="th1error">%h(zR)</pre> | |
| 3803 | + } | |
| 3776 | 3804 | } |
| 3777 | 3805 | @ </ol> |
| 3778 | 3806 | @ <p>End of test</p> |
| 3779 | 3807 | style_finish_page(); |
| 3780 | 3808 | } |
| 3781 | 3809 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2365,11 +2365,18 @@ | |
| 2365 | ** notfound: URL When in "directory:" mode, redirect to |
| 2366 | ** URL if no suitable repository is found. |
| 2367 | ** |
| 2368 | ** repolist When in "directory:" mode, display a page |
| 2369 | ** showing a list of available repositories if |
| 2370 | ** the URL is "/". |
| 2371 | ** |
| 2372 | ** localauth Grant administrator privileges to connections |
| 2373 | ** from 127.0.0.1 or ::1. |
| 2374 | ** |
| 2375 | ** nossl Signal that no SSL connections are available. |
| @@ -3523,11 +3530,12 @@ | |
| 3523 | } |
| 3524 | blob_append_escaped_arg(&ssh, "fossil", 1); |
| 3525 | }else{ |
| 3526 | blob_appendf(&ssh, " %$", zFossilCmd); |
| 3527 | } |
| 3528 | blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort); |
| 3529 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3530 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3531 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3532 | if( zExtPage ){ |
| 3533 | if( !file_is_absolute_path(zExtPage) ){ |
| @@ -3710,10 +3718,13 @@ | |
| 3710 | ** case=3 Extra db_end_transaction() |
| 3711 | ** case=4 Error during SQL processing |
| 3712 | ** case=5 Call the segfault handler |
| 3713 | ** case=6 Call webpage_assert() |
| 3714 | ** case=7 Call webpage_error() |
| 3715 | */ |
| 3716 | void test_warning_page(void){ |
| 3717 | int iCase = atoi(PD("case","0")); |
| 3718 | int i; |
| 3719 | login_check_credentials(); |
| @@ -3722,17 +3733,15 @@ | |
| 3722 | return; |
| 3723 | } |
| 3724 | style_set_current_feature("test"); |
| 3725 | style_header("Warning Test Page"); |
| 3726 | style_submenu_element("Error Log","%R/errorlog"); |
| 3727 | if( iCase<1 || iCase>4 ){ |
| 3728 | @ <p>Generate a message to the <a href="%R/errorlog">error log</a> |
| 3729 | @ by clicking on one of the following cases: |
| 3730 | }else{ |
| 3731 | @ <p>This is the test page for case=%d(iCase). All possible cases: |
| 3732 | } |
| 3733 | for(i=1; i<=8; i++){ |
| 3734 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3735 | } |
| 3736 | @ </p> |
| 3737 | @ <p><ol> |
| 3738 | @ <li value='1'> Call fossil_warning() |
| @@ -3761,20 +3770,39 @@ | |
| 3761 | } |
| 3762 | @ <li value='6'> call webpage_assert(0) |
| 3763 | if( iCase==6 ){ |
| 3764 | webpage_assert( 5==7 ); |
| 3765 | } |
| 3766 | @ <li value='7'> call webpage_error()" |
| 3767 | if( iCase==7 ){ |
| 3768 | cgi_reset_content(); |
| 3769 | webpage_error("Case 7 from /test-warning"); |
| 3770 | } |
| 3771 | @ <li value='8'> simulated timeout" |
| 3772 | if( iCase==8 ){ |
| 3773 | fossil_set_timeout(1); |
| 3774 | cgi_reset_content(); |
| 3775 | sqlite3_sleep(1100); |
| 3776 | } |
| 3777 | @ </ol> |
| 3778 | @ <p>End of test</p> |
| 3779 | style_finish_page(); |
| 3780 | } |
| 3781 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2365,11 +2365,18 @@ | |
| 2365 | ** notfound: URL When in "directory:" mode, redirect to |
| 2366 | ** URL if no suitable repository is found. |
| 2367 | ** |
| 2368 | ** repolist When in "directory:" mode, display a page |
| 2369 | ** showing a list of available repositories if |
| 2370 | ** the URL is "/". Some control over the display |
| 2371 | ** is accomplished using environment variables. |
| 2372 | ** FOSSIL_REPOLIST_TITLE is the tital of the page. |
| 2373 | ** FOSSIL_REPOLIST_SHOW cause the "Description" |
| 2374 | ** column to display if it contains "description" as |
| 2375 | ** as a substring, and causes the Login-Group column |
| 2376 | ** to display if it contains the "login-group" |
| 2377 | ** substring. |
| 2378 | ** |
| 2379 | ** localauth Grant administrator privileges to connections |
| 2380 | ** from 127.0.0.1 or ::1. |
| 2381 | ** |
| 2382 | ** nossl Signal that no SSL connections are available. |
| @@ -3523,11 +3530,12 @@ | |
| 3530 | } |
| 3531 | blob_append_escaped_arg(&ssh, "fossil", 1); |
| 3532 | }else{ |
| 3533 | blob_appendf(&ssh, " %$", zFossilCmd); |
| 3534 | } |
| 3535 | blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d", |
| 3536 | iPort); |
| 3537 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3538 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3539 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3540 | if( zExtPage ){ |
| 3541 | if( !file_is_absolute_path(zExtPage) ){ |
| @@ -3710,10 +3718,13 @@ | |
| 3718 | ** case=3 Extra db_end_transaction() |
| 3719 | ** case=4 Error during SQL processing |
| 3720 | ** case=5 Call the segfault handler |
| 3721 | ** case=6 Call webpage_assert() |
| 3722 | ** case=7 Call webpage_error() |
| 3723 | ** case=8 Simulate a timeout |
| 3724 | ** case=9 Simulate a TH1 XSS vulnerability |
| 3725 | ** case=10 Simulate a TH1 SQL-injection vulnerability |
| 3726 | */ |
| 3727 | void test_warning_page(void){ |
| 3728 | int iCase = atoi(PD("case","0")); |
| 3729 | int i; |
| 3730 | login_check_credentials(); |
| @@ -3722,17 +3733,15 @@ | |
| 3733 | return; |
| 3734 | } |
| 3735 | style_set_current_feature("test"); |
| 3736 | style_header("Warning Test Page"); |
| 3737 | style_submenu_element("Error Log","%R/errorlog"); |
| 3738 | @ <p>This page will generate various kinds of errors to test Fossil's |
| 3739 | @ reaction. Depending on settings, a message might be written |
| 3740 | @ into the <a href="%R/errorlog">error log</a>. Click on |
| 3741 | @ one of the following hyperlinks to generate a simulated error: |
| 3742 | for(i=1; i<=10; i++){ |
| 3743 | @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> |
| 3744 | } |
| 3745 | @ </p> |
| 3746 | @ <p><ol> |
| 3747 | @ <li value='1'> Call fossil_warning() |
| @@ -3761,20 +3770,39 @@ | |
| 3770 | } |
| 3771 | @ <li value='6'> call webpage_assert(0) |
| 3772 | if( iCase==6 ){ |
| 3773 | webpage_assert( 5==7 ); |
| 3774 | } |
| 3775 | @ <li value='7'> call webpage_error() |
| 3776 | if( iCase==7 ){ |
| 3777 | cgi_reset_content(); |
| 3778 | webpage_error("Case 7 from /test-warning"); |
| 3779 | } |
| 3780 | @ <li value='8'> simulated timeout |
| 3781 | if( iCase==8 ){ |
| 3782 | fossil_set_timeout(1); |
| 3783 | cgi_reset_content(); |
| 3784 | sqlite3_sleep(1100); |
| 3785 | } |
| 3786 | @ <li value='9'> simulated TH1 XSS vulnerability |
| 3787 | @ <li value='10'> simulated TH1 SQL-injection vulnerability |
| 3788 | if( iCase==9 || iCase==10 ){ |
| 3789 | const char *zR; |
| 3790 | int n, rc; |
| 3791 | static const char *zTH1[] = { |
| 3792 | /* case 9 */ "html [taint {<b>XSS</b>}]", |
| 3793 | /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n" |
| 3794 | " html \"<b>[htmlize $msg]</b>\"\n" |
| 3795 | "}" |
| 3796 | }; |
| 3797 | rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1); |
| 3798 | zR = Th_GetResult(g.interp, &n); |
| 3799 | if( rc==TH_OK ){ |
| 3800 | @ <pre class="th1result">%h(zR)</pre> |
| 3801 | }else{ |
| 3802 | @ <pre class="th1error">%h(zR)</pre> |
| 3803 | } |
| 3804 | } |
| 3805 | @ </ol> |
| 3806 | @ <p>End of test</p> |
| 3807 | style_finish_page(); |
| 3808 | } |
| 3809 |
+2
-2
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -3024,11 +3024,11 @@ | ||
| 3024 | 3024 | CARD_STR2(K, p->zTicketUuid); |
| 3025 | 3025 | CARD_STR2(L, p->zWikiTitle); |
| 3026 | 3026 | ISA( CFTYPE_CLUSTER ){ |
| 3027 | 3027 | CARD_LETTER(M); |
| 3028 | 3028 | blob_append_char(b, '['); |
| 3029 | - for( int i = 0; i < p->nCChild; ++i ){ | |
| 3029 | + for( i = 0; i < p->nCChild; ++i ){ | |
| 3030 | 3030 | if( i>0 ) blob_append_char(b, ','); |
| 3031 | 3031 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3032 | 3032 | } |
| 3033 | 3033 | blob_append_char(b, ']'); |
| 3034 | 3034 | } |
| @@ -3059,11 +3059,11 @@ | ||
| 3059 | 3059 | } |
| 3060 | 3060 | CARD_STR2(R, p->zRepoCksum); |
| 3061 | 3061 | if( p->nTag ){ |
| 3062 | 3062 | CARD_LETTER(T); |
| 3063 | 3063 | blob_append_char(b, '['); |
| 3064 | - for( int i = 0; i < p->nTag; ++i ){ | |
| 3064 | + for( i = 0; i < p->nTag; ++i ){ | |
| 3065 | 3065 | const char *zName = p->aTag[i].zName; |
| 3066 | 3066 | if( i>0 ) blob_append_char(b, ','); |
| 3067 | 3067 | blob_append_char(b, '{'); |
| 3068 | 3068 | blob_appendf(b, "\"type\":\"%c\"", *zName); |
| 3069 | 3069 | KVP_STR(1, name, &zName[1]); |
| 3070 | 3070 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -3024,11 +3024,11 @@ | |
| 3024 | CARD_STR2(K, p->zTicketUuid); |
| 3025 | CARD_STR2(L, p->zWikiTitle); |
| 3026 | ISA( CFTYPE_CLUSTER ){ |
| 3027 | CARD_LETTER(M); |
| 3028 | blob_append_char(b, '['); |
| 3029 | for( int i = 0; i < p->nCChild; ++i ){ |
| 3030 | if( i>0 ) blob_append_char(b, ','); |
| 3031 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3032 | } |
| 3033 | blob_append_char(b, ']'); |
| 3034 | } |
| @@ -3059,11 +3059,11 @@ | |
| 3059 | } |
| 3060 | CARD_STR2(R, p->zRepoCksum); |
| 3061 | if( p->nTag ){ |
| 3062 | CARD_LETTER(T); |
| 3063 | blob_append_char(b, '['); |
| 3064 | for( int i = 0; i < p->nTag; ++i ){ |
| 3065 | const char *zName = p->aTag[i].zName; |
| 3066 | if( i>0 ) blob_append_char(b, ','); |
| 3067 | blob_append_char(b, '{'); |
| 3068 | blob_appendf(b, "\"type\":\"%c\"", *zName); |
| 3069 | KVP_STR(1, name, &zName[1]); |
| 3070 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -3024,11 +3024,11 @@ | |
| 3024 | CARD_STR2(K, p->zTicketUuid); |
| 3025 | CARD_STR2(L, p->zWikiTitle); |
| 3026 | ISA( CFTYPE_CLUSTER ){ |
| 3027 | CARD_LETTER(M); |
| 3028 | blob_append_char(b, '['); |
| 3029 | for( i = 0; i < p->nCChild; ++i ){ |
| 3030 | if( i>0 ) blob_append_char(b, ','); |
| 3031 | blob_appendf(b, "%!j", p->azCChild[i]); |
| 3032 | } |
| 3033 | blob_append_char(b, ']'); |
| 3034 | } |
| @@ -3059,11 +3059,11 @@ | |
| 3059 | } |
| 3060 | CARD_STR2(R, p->zRepoCksum); |
| 3061 | if( p->nTag ){ |
| 3062 | CARD_LETTER(T); |
| 3063 | blob_append_char(b, '['); |
| 3064 | for( i = 0; i < p->nTag; ++i ){ |
| 3065 | const char *zName = p->aTag[i].zName; |
| 3066 | if( i>0 ) blob_append_char(b, ','); |
| 3067 | blob_append_char(b, '{'); |
| 3068 | blob_appendf(b, "\"type\":\"%c\"", *zName); |
| 3069 | KVP_STR(1, name, &zName[1]); |
| 3070 |
+2
-2
| --- src/markdown_html.c | ||
| +++ src/markdown_html.c | ||
| @@ -278,11 +278,11 @@ | ||
| 278 | 278 | struct Blob *head_row, |
| 279 | 279 | struct Blob *rows, |
| 280 | 280 | void *opaque |
| 281 | 281 | ){ |
| 282 | 282 | INTER_BLOCK(ob); |
| 283 | - blob_append_literal(ob, "<table>\n"); | |
| 283 | + blob_append_literal(ob, "<table class='md-table'>\n"); | |
| 284 | 284 | if( head_row && blob_size(head_row)>0 ){ |
| 285 | 285 | blob_append_literal(ob, "<thead>\n"); |
| 286 | 286 | blob_appendb(ob, head_row); |
| 287 | 287 | blob_append_literal(ob, "</thead>\n<tbody>\n"); |
| 288 | 288 | } |
| @@ -696,11 +696,11 @@ | ||
| 696 | 696 | ){ |
| 697 | 697 | blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); |
| 698 | 698 | } |
| 699 | 699 | blob_append(&bSrc, zSrc, nSrc) |
| 700 | 700 | /*have to dup input to ensure a NUL-terminated source string */; |
| 701 | - pikchr_process(blob_str(&bSrc), pikFlags, 0, ob); | |
| 701 | + pikchr_process(blob_str(&bSrc), pikFlags, ob); | |
| 702 | 702 | blob_reset(&bSrc); |
| 703 | 703 | } |
| 704 | 704 | |
| 705 | 705 | /* Invoked for `...` blocks where there are nSep grave accents in a |
| 706 | 706 | ** row that serve as the delimiter. According to CommonMark: |
| 707 | 707 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -278,11 +278,11 @@ | |
| 278 | struct Blob *head_row, |
| 279 | struct Blob *rows, |
| 280 | void *opaque |
| 281 | ){ |
| 282 | INTER_BLOCK(ob); |
| 283 | blob_append_literal(ob, "<table>\n"); |
| 284 | if( head_row && blob_size(head_row)>0 ){ |
| 285 | blob_append_literal(ob, "<thead>\n"); |
| 286 | blob_appendb(ob, head_row); |
| 287 | blob_append_literal(ob, "</thead>\n<tbody>\n"); |
| 288 | } |
| @@ -696,11 +696,11 @@ | |
| 696 | ){ |
| 697 | blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); |
| 698 | } |
| 699 | blob_append(&bSrc, zSrc, nSrc) |
| 700 | /*have to dup input to ensure a NUL-terminated source string */; |
| 701 | pikchr_process(blob_str(&bSrc), pikFlags, 0, ob); |
| 702 | blob_reset(&bSrc); |
| 703 | } |
| 704 | |
| 705 | /* Invoked for `...` blocks where there are nSep grave accents in a |
| 706 | ** row that serve as the delimiter. According to CommonMark: |
| 707 |
| --- src/markdown_html.c | |
| +++ src/markdown_html.c | |
| @@ -278,11 +278,11 @@ | |
| 278 | struct Blob *head_row, |
| 279 | struct Blob *rows, |
| 280 | void *opaque |
| 281 | ){ |
| 282 | INTER_BLOCK(ob); |
| 283 | blob_append_literal(ob, "<table class='md-table'>\n"); |
| 284 | if( head_row && blob_size(head_row)>0 ){ |
| 285 | blob_append_literal(ob, "<thead>\n"); |
| 286 | blob_appendb(ob, head_row); |
| 287 | blob_append_literal(ob, "</thead>\n<tbody>\n"); |
| 288 | } |
| @@ -696,11 +696,11 @@ | |
| 696 | ){ |
| 697 | blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); |
| 698 | } |
| 699 | blob_append(&bSrc, zSrc, nSrc) |
| 700 | /*have to dup input to ensure a NUL-terminated source string */; |
| 701 | pikchr_process(blob_str(&bSrc), pikFlags, ob); |
| 702 | blob_reset(&bSrc); |
| 703 | } |
| 704 | |
| 705 | /* Invoked for `...` blocks where there are nSep grave accents in a |
| 706 | ** row that serve as the delimiter. According to CommonMark: |
| 707 |
+65
-6
| --- src/name.c | ||
| +++ src/name.c | ||
| @@ -1816,11 +1816,11 @@ | ||
| 1816 | 1816 | } |
| 1817 | 1817 | if( bUnclst==0 ){ |
| 1818 | 1818 | style_submenu_element("Unclustered","bloblist?unclustered"); |
| 1819 | 1819 | } |
| 1820 | 1820 | if( g.perm.Admin ){ |
| 1821 | - style_submenu_element("Artifact Log", "rcvfromlist"); | |
| 1821 | + style_submenu_element("Xfer Log", "rcvfromlist"); | |
| 1822 | 1822 | } |
| 1823 | 1823 | if( !phantomOnly ){ |
| 1824 | 1824 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1825 | 1825 | } |
| 1826 | 1826 | style_submenu_element("Clusters","clusterlist"); |
| @@ -2005,11 +2005,11 @@ | ||
| 2005 | 2005 | void phantom_list_page(void){ |
| 2006 | 2006 | login_check_credentials(); |
| 2007 | 2007 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2008 | 2008 | style_header("Public Phantom Artifacts"); |
| 2009 | 2009 | if( g.perm.Admin ){ |
| 2010 | - style_submenu_element("Artifact Log", "rcvfromlist"); | |
| 2010 | + style_submenu_element("Xfer Log", "rcvfromlist"); | |
| 2011 | 2011 | style_submenu_element("Artifact List", "bloblist"); |
| 2012 | 2012 | } |
| 2013 | 2013 | if( g.perm.Write ){ |
| 2014 | 2014 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2015 | 2015 | } |
| @@ -2030,11 +2030,11 @@ | ||
| 2030 | 2030 | int n = atoi(PD("n","250")); |
| 2031 | 2031 | |
| 2032 | 2032 | login_check_credentials(); |
| 2033 | 2033 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2034 | 2034 | if( g.perm.Admin ){ |
| 2035 | - style_submenu_element("Artifact Log", "rcvfromlist"); | |
| 2035 | + style_submenu_element("Xfer Log", "rcvfromlist"); | |
| 2036 | 2036 | } |
| 2037 | 2037 | if( g.perm.Write ){ |
| 2038 | 2038 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2039 | 2039 | } |
| 2040 | 2040 | style_submenu_element("All Artifacts", "bloblist"); |
| @@ -2203,15 +2203,74 @@ | ||
| 2203 | 2203 | /* |
| 2204 | 2204 | ** COMMAND: test-phantoms |
| 2205 | 2205 | ** |
| 2206 | 2206 | ** Usage: %fossil test-phantoms |
| 2207 | 2207 | ** |
| 2208 | -** Show all phantom artifacts | |
| 2208 | +** Show all phantom artifacts. A phantom artifact is one for which there | |
| 2209 | +** is no content. Options: | |
| 2210 | +** | |
| 2211 | +** --count Show only a count of the number of phantoms. | |
| 2212 | +** --delta Show all delta-phantoms. A delta-phantom is a | |
| 2213 | +** artifact for which there is a delta but the delta | |
| 2214 | +** source is a phantom. | |
| 2215 | +** --list Just list the phantoms. Do not try to describe them. | |
| 2209 | 2216 | */ |
| 2210 | 2217 | void test_phatoms_cmd(void){ |
| 2218 | + int bDelta; | |
| 2219 | + int bList; | |
| 2220 | + int bCount; | |
| 2221 | + unsigned nPhantom = 0; | |
| 2222 | + unsigned nDeltaPhantom = 0; | |
| 2211 | 2223 | db_find_and_open_repository(0,0); |
| 2212 | - describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); | |
| 2224 | + bDelta = find_option("delta", 0, 0)!=0; | |
| 2225 | + bList = find_option("list", 0, 0)!=0; | |
| 2226 | + bCount = find_option("count", 0, 0)!=0; | |
| 2227 | + verify_all_options(); | |
| 2228 | + if( bList || bCount ){ | |
| 2229 | + Stmt q1, q2; | |
| 2230 | + db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0"); | |
| 2231 | + while( db_step(&q1)==SQLITE_ROW ){ | |
| 2232 | + int rid = db_column_int(&q1, 0); | |
| 2233 | + nPhantom++; | |
| 2234 | + if( !bCount ){ | |
| 2235 | + fossil_print("%S (%d)\n", db_column_text(&q1,1), rid); | |
| 2236 | + } | |
| 2237 | + db_prepare(&q2, | |
| 2238 | + "WITH RECURSIVE deltasof(rid) AS (" | |
| 2239 | + " SELECT rid FROM delta WHERE srcid=%d" | |
| 2240 | + " UNION" | |
| 2241 | + " SELECT delta.rid FROM deltasof, delta" | |
| 2242 | + " WHERE delta.srcid=deltasof.rid)" | |
| 2243 | + "SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob" | |
| 2244 | + " ON blob.rid=deltasof.rid", rid | |
| 2245 | + ); | |
| 2246 | + while( db_step(&q2)==SQLITE_ROW ){ | |
| 2247 | + nDeltaPhantom++; | |
| 2248 | + if( !bCount ){ | |
| 2249 | + fossil_print(" %S (%d)\n", db_column_text(&q2,1), | |
| 2250 | + db_column_int(&q2,0)); | |
| 2251 | + } | |
| 2252 | + } | |
| 2253 | + db_finalize(&q2); | |
| 2254 | + } | |
| 2255 | + db_finalize(&q1); | |
| 2256 | + if( nPhantom ){ | |
| 2257 | + fossil_print("Phantoms: %u Delta-phantoms: %u\n", | |
| 2258 | + nPhantom, nDeltaPhantom); | |
| 2259 | + } | |
| 2260 | + }else if( bDelta ){ | |
| 2261 | + describe_artifacts_to_stdout( | |
| 2262 | + "IN (WITH RECURSIVE delta_phantom(rid) AS (\n" | |
| 2263 | + " SELECT delta.rid FROM blob, delta\n" | |
| 2264 | + " WHERE blob.size<0 AND delta.srcid=blob.rid\n" | |
| 2265 | + " UNION\n" | |
| 2266 | + " SELECT delta.rid FROM delta_phantom, delta\n" | |
| 2267 | + " WHERE delta.srcid=delta_phantom.rid)\n" | |
| 2268 | + " SELECT rid FROM delta_phantom)", 0); | |
| 2269 | + }else{ | |
| 2270 | + describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); | |
| 2271 | + } | |
| 2213 | 2272 | } |
| 2214 | 2273 | |
| 2215 | 2274 | /* Maximum number of collision examples to remember */ |
| 2216 | 2275 | #define MAX_COLLIDE 25 |
| 2217 | 2276 | |
| @@ -2311,11 +2370,11 @@ | ||
| 2311 | 2370 | login_check_credentials(); |
| 2312 | 2371 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2313 | 2372 | style_header("All Cluster Artifacts"); |
| 2314 | 2373 | style_submenu_element("All Artifactst", "bloblist"); |
| 2315 | 2374 | if( g.perm.Admin ){ |
| 2316 | - style_submenu_element("Artifact Log", "rcvfromlist"); | |
| 2375 | + style_submenu_element("Xfer Log", "rcvfromlist"); | |
| 2317 | 2376 | } |
| 2318 | 2377 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 2319 | 2378 | if( g.perm.Write ){ |
| 2320 | 2379 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2321 | 2380 | } |
| 2322 | 2381 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -1816,11 +1816,11 @@ | |
| 1816 | } |
| 1817 | if( bUnclst==0 ){ |
| 1818 | style_submenu_element("Unclustered","bloblist?unclustered"); |
| 1819 | } |
| 1820 | if( g.perm.Admin ){ |
| 1821 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 1822 | } |
| 1823 | if( !phantomOnly ){ |
| 1824 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1825 | } |
| 1826 | style_submenu_element("Clusters","clusterlist"); |
| @@ -2005,11 +2005,11 @@ | |
| 2005 | void phantom_list_page(void){ |
| 2006 | login_check_credentials(); |
| 2007 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2008 | style_header("Public Phantom Artifacts"); |
| 2009 | if( g.perm.Admin ){ |
| 2010 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 2011 | style_submenu_element("Artifact List", "bloblist"); |
| 2012 | } |
| 2013 | if( g.perm.Write ){ |
| 2014 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2015 | } |
| @@ -2030,11 +2030,11 @@ | |
| 2030 | int n = atoi(PD("n","250")); |
| 2031 | |
| 2032 | login_check_credentials(); |
| 2033 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2034 | if( g.perm.Admin ){ |
| 2035 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 2036 | } |
| 2037 | if( g.perm.Write ){ |
| 2038 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2039 | } |
| 2040 | style_submenu_element("All Artifacts", "bloblist"); |
| @@ -2203,15 +2203,74 @@ | |
| 2203 | /* |
| 2204 | ** COMMAND: test-phantoms |
| 2205 | ** |
| 2206 | ** Usage: %fossil test-phantoms |
| 2207 | ** |
| 2208 | ** Show all phantom artifacts |
| 2209 | */ |
| 2210 | void test_phatoms_cmd(void){ |
| 2211 | db_find_and_open_repository(0,0); |
| 2212 | describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); |
| 2213 | } |
| 2214 | |
| 2215 | /* Maximum number of collision examples to remember */ |
| 2216 | #define MAX_COLLIDE 25 |
| 2217 | |
| @@ -2311,11 +2370,11 @@ | |
| 2311 | login_check_credentials(); |
| 2312 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2313 | style_header("All Cluster Artifacts"); |
| 2314 | style_submenu_element("All Artifactst", "bloblist"); |
| 2315 | if( g.perm.Admin ){ |
| 2316 | style_submenu_element("Artifact Log", "rcvfromlist"); |
| 2317 | } |
| 2318 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 2319 | if( g.perm.Write ){ |
| 2320 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2321 | } |
| 2322 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -1816,11 +1816,11 @@ | |
| 1816 | } |
| 1817 | if( bUnclst==0 ){ |
| 1818 | style_submenu_element("Unclustered","bloblist?unclustered"); |
| 1819 | } |
| 1820 | if( g.perm.Admin ){ |
| 1821 | style_submenu_element("Xfer Log", "rcvfromlist"); |
| 1822 | } |
| 1823 | if( !phantomOnly ){ |
| 1824 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 1825 | } |
| 1826 | style_submenu_element("Clusters","clusterlist"); |
| @@ -2005,11 +2005,11 @@ | |
| 2005 | void phantom_list_page(void){ |
| 2006 | login_check_credentials(); |
| 2007 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2008 | style_header("Public Phantom Artifacts"); |
| 2009 | if( g.perm.Admin ){ |
| 2010 | style_submenu_element("Xfer Log", "rcvfromlist"); |
| 2011 | style_submenu_element("Artifact List", "bloblist"); |
| 2012 | } |
| 2013 | if( g.perm.Write ){ |
| 2014 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2015 | } |
| @@ -2030,11 +2030,11 @@ | |
| 2030 | int n = atoi(PD("n","250")); |
| 2031 | |
| 2032 | login_check_credentials(); |
| 2033 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2034 | if( g.perm.Admin ){ |
| 2035 | style_submenu_element("Xfer Log", "rcvfromlist"); |
| 2036 | } |
| 2037 | if( g.perm.Write ){ |
| 2038 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2039 | } |
| 2040 | style_submenu_element("All Artifacts", "bloblist"); |
| @@ -2203,15 +2203,74 @@ | |
| 2203 | /* |
| 2204 | ** COMMAND: test-phantoms |
| 2205 | ** |
| 2206 | ** Usage: %fossil test-phantoms |
| 2207 | ** |
| 2208 | ** Show all phantom artifacts. A phantom artifact is one for which there |
| 2209 | ** is no content. Options: |
| 2210 | ** |
| 2211 | ** --count Show only a count of the number of phantoms. |
| 2212 | ** --delta Show all delta-phantoms. A delta-phantom is a |
| 2213 | ** artifact for which there is a delta but the delta |
| 2214 | ** source is a phantom. |
| 2215 | ** --list Just list the phantoms. Do not try to describe them. |
| 2216 | */ |
| 2217 | void test_phatoms_cmd(void){ |
| 2218 | int bDelta; |
| 2219 | int bList; |
| 2220 | int bCount; |
| 2221 | unsigned nPhantom = 0; |
| 2222 | unsigned nDeltaPhantom = 0; |
| 2223 | db_find_and_open_repository(0,0); |
| 2224 | bDelta = find_option("delta", 0, 0)!=0; |
| 2225 | bList = find_option("list", 0, 0)!=0; |
| 2226 | bCount = find_option("count", 0, 0)!=0; |
| 2227 | verify_all_options(); |
| 2228 | if( bList || bCount ){ |
| 2229 | Stmt q1, q2; |
| 2230 | db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0"); |
| 2231 | while( db_step(&q1)==SQLITE_ROW ){ |
| 2232 | int rid = db_column_int(&q1, 0); |
| 2233 | nPhantom++; |
| 2234 | if( !bCount ){ |
| 2235 | fossil_print("%S (%d)\n", db_column_text(&q1,1), rid); |
| 2236 | } |
| 2237 | db_prepare(&q2, |
| 2238 | "WITH RECURSIVE deltasof(rid) AS (" |
| 2239 | " SELECT rid FROM delta WHERE srcid=%d" |
| 2240 | " UNION" |
| 2241 | " SELECT delta.rid FROM deltasof, delta" |
| 2242 | " WHERE delta.srcid=deltasof.rid)" |
| 2243 | "SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob" |
| 2244 | " ON blob.rid=deltasof.rid", rid |
| 2245 | ); |
| 2246 | while( db_step(&q2)==SQLITE_ROW ){ |
| 2247 | nDeltaPhantom++; |
| 2248 | if( !bCount ){ |
| 2249 | fossil_print(" %S (%d)\n", db_column_text(&q2,1), |
| 2250 | db_column_int(&q2,0)); |
| 2251 | } |
| 2252 | } |
| 2253 | db_finalize(&q2); |
| 2254 | } |
| 2255 | db_finalize(&q1); |
| 2256 | if( nPhantom ){ |
| 2257 | fossil_print("Phantoms: %u Delta-phantoms: %u\n", |
| 2258 | nPhantom, nDeltaPhantom); |
| 2259 | } |
| 2260 | }else if( bDelta ){ |
| 2261 | describe_artifacts_to_stdout( |
| 2262 | "IN (WITH RECURSIVE delta_phantom(rid) AS (\n" |
| 2263 | " SELECT delta.rid FROM blob, delta\n" |
| 2264 | " WHERE blob.size<0 AND delta.srcid=blob.rid\n" |
| 2265 | " UNION\n" |
| 2266 | " SELECT delta.rid FROM delta_phantom, delta\n" |
| 2267 | " WHERE delta.srcid=delta_phantom.rid)\n" |
| 2268 | " SELECT rid FROM delta_phantom)", 0); |
| 2269 | }else{ |
| 2270 | describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); |
| 2271 | } |
| 2272 | } |
| 2273 | |
| 2274 | /* Maximum number of collision examples to remember */ |
| 2275 | #define MAX_COLLIDE 25 |
| 2276 | |
| @@ -2311,11 +2370,11 @@ | |
| 2370 | login_check_credentials(); |
| 2371 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2372 | style_header("All Cluster Artifacts"); |
| 2373 | style_submenu_element("All Artifactst", "bloblist"); |
| 2374 | if( g.perm.Admin ){ |
| 2375 | style_submenu_element("Xfer Log", "rcvfromlist"); |
| 2376 | } |
| 2377 | style_submenu_element("Phantoms", "bloblist?phan"); |
| 2378 | if( g.perm.Write ){ |
| 2379 | style_submenu_element("Artifact Stats", "artifact_stats"); |
| 2380 | } |
| 2381 |
+89
-182
| --- src/pikchrshow.c | ||
| +++ src/pikchrshow.c | ||
| @@ -27,12 +27,10 @@ | ||
| 27 | 27 | /* The first two must match the values from pikchr.c */ |
| 28 | 28 | #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001 |
| 29 | 29 | #define PIKCHR_PROCESS_DARK_MODE 0x0002 |
| 30 | 30 | /* end of flags supported directly by pikchr() */ |
| 31 | 31 | #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ |
| 32 | -#define PIKCHR_PROCESS_TH1 0x0004 | |
| 33 | -#define PIKCHR_PROCESS_TH1_NOSVG 0x0008 | |
| 34 | 32 | #define PIKCHR_PROCESS_NONCE 0x0010 |
| 35 | 33 | #define PIKCHR_PROCESS_ERR_PRE 0x0020 |
| 36 | 34 | #define PIKCHR_PROCESS_SRC 0x0040 |
| 37 | 35 | #define PIKCHR_PROCESS_DIV 0x0080 |
| 38 | 36 | #define PIKCHR_PROCESS_DIV_INDENT 0x0100 |
| @@ -43,36 +41,20 @@ | ||
| 43 | 41 | #define PIKCHR_PROCESS_DIV_SOURCE 0x2000 |
| 44 | 42 | #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 |
| 45 | 43 | #endif |
| 46 | 44 | |
| 47 | 45 | /* |
| 48 | -** Processes a pikchr script, optionally with embedded TH1, and | |
| 49 | -** produces HTML code for it. zIn is the NUL-terminated input | |
| 46 | +** Processes a pikchr script. zIn is the NUL-terminated input | |
| 50 | 47 | ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx |
| 51 | -** flags documented below. thFlags may be a bitmask of any of the | |
| 52 | -** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut, | |
| 53 | -** appending to it without modifying any prior contents. | |
| 48 | +** flags documented below. Output is sent to pOut, | |
| 54 | 49 | ** |
| 55 | -** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr | |
| 56 | -** processing failed. In either case, the error message (if any) from | |
| 57 | -** TH1 or pikchr will be appended to pOut. | |
| 50 | +** Returns 0 on success, or non-zero if pikchr processing failed. | |
| 51 | +** In either case, the error message (if any) from pikchr will be | |
| 52 | +** appended to pOut. | |
| 58 | 53 | ** |
| 59 | 54 | ** pikFlags flag descriptions: |
| 60 | 55 | ** |
| 61 | -** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1 | |
| 62 | -** init flags specified in the 3rd argument. If thFlags is non-0 then | |
| 63 | -** this flag is assumed even if it is not specified. | |
| 64 | -** | |
| 65 | -** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the | |
| 66 | -** TH1 eval step, thus the output will be (presumably) a | |
| 67 | -** TH1-generated/processed pikchr script (or whatever else the TH1 | |
| 68 | -** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even | |
| 69 | -** if it is not specified. | |
| 70 | -** | |
| 71 | -** All of the remaining flags listed below are ignored if | |
| 72 | -** PIKCHR_PROCESS_TH1_NOSVG is specified! | |
| 73 | -** | |
| 74 | 56 | ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV |
| 75 | 57 | ** element which specifies a max-width style value based on the SVG's |
| 76 | 58 | ** calculated size. This flag has multiple mutually exclusive forms: |
| 77 | 59 | ** |
| 78 | 60 | ** - PIKCHR_PROCESS_DIV uses default element alignment. |
| @@ -116,14 +98,14 @@ | ||
| 116 | 98 | ** |
| 117 | 99 | ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting |
| 118 | 100 | ** error report is wrapped in a PRE element, else it is retained |
| 119 | 101 | ** as-is (intended only for console output). |
| 120 | 102 | */ |
| 121 | -int pikchr_process(const char * zIn, int pikFlags, int thFlags, | |
| 122 | - Blob * pOut){ | |
| 123 | - Blob bIn = empty_blob; | |
| 103 | +int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){ | |
| 124 | 104 | int isErr = 0; |
| 105 | + int w = 0, h = 0; | |
| 106 | + char *zOut; | |
| 125 | 107 | const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) |
| 126 | 108 | ? safe_html_nonce(1) : 0; |
| 127 | 109 | |
| 128 | 110 | if(!(PIKCHR_PROCESS_DIV & pikFlags) |
| 129 | 111 | /* If any DIV_xxx flags are set, set DIV */ |
| @@ -135,115 +117,87 @@ | ||
| 135 | 117 | | PIKCHR_PROCESS_DIV_SOURCE_INLINE |
| 136 | 118 | | PIKCHR_PROCESS_DIV_TOGGLE |
| 137 | 119 | ) & pikFlags){ |
| 138 | 120 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 139 | 121 | } |
| 140 | - if(!(PIKCHR_PROCESS_TH1 & pikFlags) | |
| 141 | - /* If any TH1_xxx flags are set, set TH1 */ | |
| 142 | - && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){ | |
| 143 | - pikFlags |= PIKCHR_PROCESS_TH1; | |
| 144 | - } | |
| 145 | - if(zNonce){ | |
| 146 | - blob_appendf(pOut, "%s\n", zNonce); | |
| 147 | - } | |
| 148 | - if(PIKCHR_PROCESS_TH1 & pikFlags){ | |
| 149 | - Blob out = empty_blob; | |
| 150 | - isErr = Th_RenderToBlob(zIn, &out, thFlags) | |
| 151 | - ? 1 : 0; | |
| 152 | - if(isErr){ | |
| 153 | - blob_append(pOut, blob_str(&out), blob_size(&out)); | |
| 154 | - blob_reset(&out); | |
| 155 | - }else{ | |
| 156 | - bIn = out; | |
| 157 | - } | |
| 158 | - }else{ | |
| 159 | - blob_init(&bIn, zIn, -1); | |
| 160 | - } | |
| 161 | - if(!isErr){ | |
| 162 | - if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){ | |
| 163 | - blob_append(pOut, blob_str(&bIn), blob_size(&bIn)); | |
| 164 | - }else{ | |
| 165 | - int w = 0, h = 0; | |
| 166 | - const char * zContent = blob_str(&bIn); | |
| 167 | - char *zOut; | |
| 168 | - zOut = pikchr(zContent, "pikchr", | |
| 169 | - 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH), | |
| 170 | - &w, &h); | |
| 171 | - if( w>0 && h>0 ){ | |
| 172 | - const char * zClassToggle = ""; | |
| 173 | - const char * zClassSource = ""; | |
| 174 | - const char * zWrapperClass = ""; | |
| 175 | - if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 176 | - if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){ | |
| 177 | - zWrapperClass = " center"; | |
| 178 | - }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){ | |
| 179 | - zWrapperClass = " indent"; | |
| 180 | - }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){ | |
| 181 | - zWrapperClass = " float-left"; | |
| 182 | - }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){ | |
| 183 | - zWrapperClass = " float-right"; | |
| 184 | - } | |
| 185 | - if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){ | |
| 186 | - zClassToggle = " toggle"; | |
| 187 | - } | |
| 188 | - if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){ | |
| 189 | - if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ | |
| 190 | - zClassSource = " source source-inline"; | |
| 191 | - }else{ | |
| 192 | - zClassSource = " source-inline"; | |
| 193 | - } | |
| 194 | - pikFlags |= PIKCHR_PROCESS_SRC; | |
| 195 | - }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ | |
| 196 | - zClassSource = " source"; | |
| 197 | - pikFlags |= PIKCHR_PROCESS_SRC; | |
| 198 | - } | |
| 199 | - blob_appendf(pOut,"<div class='pikchr-wrapper" | |
| 200 | - "%s%s%s'>" | |
| 201 | - "<div class=\"pikchr-svg\" " | |
| 202 | - "style=\"max-width:%dpx\">\n", | |
| 203 | - zWrapperClass/*safe-for-%s*/, | |
| 204 | - zClassToggle/*safe-for-%s*/, | |
| 205 | - zClassSource/*safe-for-%s*/, w); | |
| 206 | - } | |
| 207 | - blob_append(pOut, zOut, -1); | |
| 208 | - if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 209 | - blob_append(pOut, "</div>\n", 7); | |
| 210 | - } | |
| 211 | - if(PIKCHR_PROCESS_SRC & pikFlags){ | |
| 212 | - static int counter = 0; | |
| 213 | - ++counter; | |
| 214 | - blob_appendf(pOut, "<div class='pikchr-src'>" | |
| 215 | - "<pre id='pikchr-src-%d'>%h</pre>" | |
| 216 | - "<span class='hidden'>" | |
| 217 | - "<a href='%R/pikchrshow?fromSession' " | |
| 218 | - "class='pikchr-src-pikchrshow' target='_new-%d' " | |
| 219 | - "data-pikchrid='pikchr-src-%d' " | |
| 220 | - "title='Open this pikchr in /pikchrshow'" | |
| 221 | - ">→ /pikchrshow</a></span>" | |
| 222 | - "</div>\n", | |
| 223 | - counter, blob_str(&bIn), counter, counter); | |
| 224 | - } | |
| 225 | - if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 226 | - blob_append(pOut, "</div>\n", 7); | |
| 227 | - } | |
| 228 | - }else{ | |
| 229 | - isErr = 2; | |
| 230 | - if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ | |
| 231 | - blob_append(pOut, "<pre class='error'>\n", 20); | |
| 232 | - } | |
| 233 | - blob_appendf(pOut, "%h", zOut); | |
| 234 | - if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ | |
| 235 | - blob_append(pOut, "\n</pre>\n", 8); | |
| 236 | - } | |
| 237 | - } | |
| 238 | - fossil_free(zOut); | |
| 239 | - } | |
| 240 | - } | |
| 241 | - if(zNonce){ | |
| 242 | - blob_appendf(pOut, "%s\n", zNonce); | |
| 243 | - } | |
| 244 | - blob_reset(&bIn); | |
| 122 | + if(zNonce){ | |
| 123 | + blob_appendf(pOut, "%s\n", zNonce); | |
| 124 | + } | |
| 125 | + zOut = pikchr(zIn, "pikchr", | |
| 126 | + 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH), | |
| 127 | + &w, &h); | |
| 128 | + if( w>0 && h>0 ){ | |
| 129 | + const char * zClassToggle = ""; | |
| 130 | + const char * zClassSource = ""; | |
| 131 | + const char * zWrapperClass = ""; | |
| 132 | + if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 133 | + if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){ | |
| 134 | + zWrapperClass = " center"; | |
| 135 | + }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){ | |
| 136 | + zWrapperClass = " indent"; | |
| 137 | + }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){ | |
| 138 | + zWrapperClass = " float-left"; | |
| 139 | + }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){ | |
| 140 | + zWrapperClass = " float-right"; | |
| 141 | + } | |
| 142 | + if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){ | |
| 143 | + zClassToggle = " toggle"; | |
| 144 | + } | |
| 145 | + if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){ | |
| 146 | + if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ | |
| 147 | + zClassSource = " source source-inline"; | |
| 148 | + }else{ | |
| 149 | + zClassSource = " source-inline"; | |
| 150 | + } | |
| 151 | + pikFlags |= PIKCHR_PROCESS_SRC; | |
| 152 | + }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ | |
| 153 | + zClassSource = " source"; | |
| 154 | + pikFlags |= PIKCHR_PROCESS_SRC; | |
| 155 | + } | |
| 156 | + blob_appendf(pOut,"<div class='pikchr-wrapper" | |
| 157 | + "%s%s%s'>" | |
| 158 | + "<div class=\"pikchr-svg\" " | |
| 159 | + "style=\"max-width:%dpx\">\n", | |
| 160 | + zWrapperClass/*safe-for-%s*/, | |
| 161 | + zClassToggle/*safe-for-%s*/, | |
| 162 | + zClassSource/*safe-for-%s*/, w); | |
| 163 | + } | |
| 164 | + blob_append(pOut, zOut, -1); | |
| 165 | + if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 166 | + blob_append(pOut, "</div>\n", 7); | |
| 167 | + } | |
| 168 | + if(PIKCHR_PROCESS_SRC & pikFlags){ | |
| 169 | + static int counter = 0; | |
| 170 | + ++counter; | |
| 171 | + blob_appendf(pOut, "<div class='pikchr-src'>" | |
| 172 | + "<pre id='pikchr-src-%d'>%h</pre>" | |
| 173 | + "<span class='hidden'>" | |
| 174 | + "<a href='%R/pikchrshow?fromSession' " | |
| 175 | + "class='pikchr-src-pikchrshow' target='_new-%d' " | |
| 176 | + "data-pikchrid='pikchr-src-%d' " | |
| 177 | + "title='Open this pikchr in /pikchrshow'" | |
| 178 | + ">→ /pikchrshow</a></span>" | |
| 179 | + "</div>\n", | |
| 180 | + counter, zIn, counter, counter); | |
| 181 | + } | |
| 182 | + if(PIKCHR_PROCESS_DIV & pikFlags){ | |
| 183 | + blob_append(pOut, "</div>\n", 7); | |
| 184 | + } | |
| 185 | + }else{ | |
| 186 | + isErr = 2; | |
| 187 | + if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ | |
| 188 | + blob_append(pOut, "<pre class='error'>\n", 20); | |
| 189 | + } | |
| 190 | + blob_appendf(pOut, "%h", zOut); | |
| 191 | + if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ | |
| 192 | + blob_append(pOut, "\n</pre>\n", 8); | |
| 193 | + } | |
| 194 | + } | |
| 195 | + fossil_free(zOut); | |
| 196 | + if(zNonce){ | |
| 197 | + blob_appendf(pOut, "%s\n", zNonce); | |
| 198 | + } | |
| 245 | 199 | return isErr; |
| 246 | 200 | } |
| 247 | 201 | |
| 248 | 202 | /* |
| 249 | 203 | ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to |
| @@ -279,11 +233,11 @@ | ||
| 279 | 233 | TODO: respond with JSON instead.*/ |
| 280 | 234 | cgi_set_content_type("text/html"); |
| 281 | 235 | if(zContent && *zContent){ |
| 282 | 236 | Blob out = empty_blob; |
| 283 | 237 | const int isErr = |
| 284 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 238 | + pikchr_process(zContent, pikFlags, &out); | |
| 285 | 239 | if(isErr){ |
| 286 | 240 | cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); |
| 287 | 241 | } |
| 288 | 242 | CX("%b", &out); |
| 289 | 243 | blob_reset(&out); |
| @@ -384,11 +338,11 @@ | ||
| 384 | 338 | /* Reminder: Firefox does not properly flexbox a LEGEND |
| 385 | 339 | element, always flowing it in column mode. */); |
| 386 | 340 | CX("<div id='pikchrshow-output'>"); |
| 387 | 341 | if(*zContent){ |
| 388 | 342 | Blob out = empty_blob; |
| 389 | - pikchr_process(zContent, pikFlags, 0, &out); | |
| 343 | + pikchr_process(zContent, pikFlags, &out); | |
| 390 | 344 | CX("%b", &out); |
| 391 | 345 | blob_reset(&out); |
| 392 | 346 | } CX("</div>"/*#pikchrshow-output*/); |
| 393 | 347 | } CX("</fieldset>"/*#pikchrshow-output-wrapper*/); |
| 394 | 348 | } CX("</div>"/*sbs-wrapper*/); |
| @@ -561,60 +515,23 @@ | ||
| 561 | 515 | ** |
| 562 | 516 | ** -src Store the input pikchr's source code in the output as |
| 563 | 517 | ** a separate element adjacent to the SVG one. Implied |
| 564 | 518 | ** by -div-source. |
| 565 | 519 | ** |
| 566 | -** | |
| 567 | -** -th Process the input using TH1 before passing it to pikchr | |
| 568 | -** | |
| 569 | -** -th-novar Disable $var and $<var> TH1 processing. Use this if the | |
| 570 | -** pikchr script uses '$' for its own purposes and that | |
| 571 | -** causes issues. This only affects parsing of '$' outside | |
| 572 | -** of TH1 script blocks. Code in such blocks is unaffected. | |
| 573 | -** | |
| 574 | -** -th-nosvg When using -th, output the post-TH1'd script | |
| 575 | -** instead of the pikchr-rendered output | |
| 576 | -** | |
| 577 | -** -th-trace Trace TH1 execution (for debugging purposes) | |
| 578 | -** | |
| 579 | 520 | ** -dark Change pikchr colors to assume a dark-mode theme. |
| 580 | 521 | ** |
| 581 | 522 | ** |
| 582 | 523 | ** The -div-indent/center/left/right flags may not be combined. |
| 583 | -** | |
| 584 | -** TH1-related Notes and Caveats: | |
| 585 | -** | |
| 586 | -** If the -th flag is used, this command must open a fossil database | |
| 587 | -** for certain functionality to work (via a check-out or the -R REPO | |
| 588 | -** flag). If opening a db fails, execution will continue but any TH1 | |
| 589 | -** commands which require a db will trigger a fatal error. | |
| 590 | -** | |
| 591 | -** In Fossil skins, TH1 variables in the form $varName are expanded | |
| 592 | -** as-is and those in the form $<varName> are htmlized in the | |
| 593 | -** resulting output. This processor disables the htmlizing step, so $x | |
| 594 | -** and $<x> are equivalent unless the TH1-processed pikchr script | |
| 595 | -** invokes the TH1 command [enable_htmlify 1] to enable it. Normally | |
| 596 | -** that option will interfere with pikchr output, however, e.g. by | |
| 597 | -** HTML-encoding double-quotes. | |
| 598 | -** | |
| 599 | -** Many of the fossil-installed TH1 functions simply do not make any | |
| 600 | -** sense for pikchr scripts. | |
| 601 | 524 | */ |
| 602 | 525 | void pikchr_cmd(void){ |
| 603 | 526 | Blob bIn = empty_blob; |
| 604 | 527 | Blob bOut = empty_blob; |
| 605 | 528 | const char * zInfile = "-"; |
| 606 | 529 | const char * zOutfile = "-"; |
| 607 | - const int fTh1 = find_option("th",0,0)!=0; | |
| 608 | - const int fNosvg = find_option("th-nosvg",0,0)!=0; | |
| 609 | 530 | int isErr = 0; |
| 610 | 531 | int pikFlags = find_option("src",0,0)!=0 |
| 611 | 532 | ? PIKCHR_PROCESS_SRC : 0; |
| 612 | - u32 fThFlags = TH_INIT_NO_ENCODE | |
| 613 | - | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0); | |
| 614 | - | |
| 615 | - Th_InitTraceLog()/*processes -th-trace flag*/; | |
| 616 | 533 | |
| 617 | 534 | if(find_option("div",0,0)!=0){ |
| 618 | 535 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 619 | 536 | }else if(find_option("div-indent",0,0)!=0){ |
| 620 | 537 | pikFlags |= PIKCHR_PROCESS_DIV_INDENT; |
| @@ -644,24 +561,14 @@ | ||
| 644 | 561 | } |
| 645 | 562 | if(g.argc>3){ |
| 646 | 563 | zOutfile = g.argv[3]; |
| 647 | 564 | } |
| 648 | 565 | blob_read_from_file(&bIn, zInfile, ExtFILE); |
| 649 | - if(fTh1){ | |
| 650 | - db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0) | |
| 651 | - /* ^^^ needed for certain TH1 functions to work */; | |
| 652 | - pikFlags |= PIKCHR_PROCESS_TH1; | |
| 653 | - if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG; | |
| 654 | - } | |
| 655 | - isErr = pikchr_process(blob_str(&bIn), pikFlags, | |
| 656 | - fTh1 ? fThFlags : 0, &bOut); | |
| 566 | + isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut); | |
| 657 | 567 | if(isErr){ |
| 658 | - fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr", | |
| 659 | - 1==isErr ? ' ' : '\n', | |
| 660 | - &bOut); | |
| 568 | + fossil_fatal("pikchr ERROR: %b", &bOut); | |
| 661 | 569 | }else{ |
| 662 | 570 | blob_write_to_file(&bOut, zOutfile); |
| 663 | 571 | } |
| 664 | - Th_PrintTraceLog(); | |
| 665 | 572 | blob_reset(&bIn); |
| 666 | 573 | blob_reset(&bOut); |
| 667 | 574 | } |
| 668 | 575 |
| --- src/pikchrshow.c | |
| +++ src/pikchrshow.c | |
| @@ -27,12 +27,10 @@ | |
| 27 | /* The first two must match the values from pikchr.c */ |
| 28 | #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001 |
| 29 | #define PIKCHR_PROCESS_DARK_MODE 0x0002 |
| 30 | /* end of flags supported directly by pikchr() */ |
| 31 | #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ |
| 32 | #define PIKCHR_PROCESS_TH1 0x0004 |
| 33 | #define PIKCHR_PROCESS_TH1_NOSVG 0x0008 |
| 34 | #define PIKCHR_PROCESS_NONCE 0x0010 |
| 35 | #define PIKCHR_PROCESS_ERR_PRE 0x0020 |
| 36 | #define PIKCHR_PROCESS_SRC 0x0040 |
| 37 | #define PIKCHR_PROCESS_DIV 0x0080 |
| 38 | #define PIKCHR_PROCESS_DIV_INDENT 0x0100 |
| @@ -43,36 +41,20 @@ | |
| 43 | #define PIKCHR_PROCESS_DIV_SOURCE 0x2000 |
| 44 | #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 |
| 45 | #endif |
| 46 | |
| 47 | /* |
| 48 | ** Processes a pikchr script, optionally with embedded TH1, and |
| 49 | ** produces HTML code for it. zIn is the NUL-terminated input |
| 50 | ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx |
| 51 | ** flags documented below. thFlags may be a bitmask of any of the |
| 52 | ** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut, |
| 53 | ** appending to it without modifying any prior contents. |
| 54 | ** |
| 55 | ** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr |
| 56 | ** processing failed. In either case, the error message (if any) from |
| 57 | ** TH1 or pikchr will be appended to pOut. |
| 58 | ** |
| 59 | ** pikFlags flag descriptions: |
| 60 | ** |
| 61 | ** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1 |
| 62 | ** init flags specified in the 3rd argument. If thFlags is non-0 then |
| 63 | ** this flag is assumed even if it is not specified. |
| 64 | ** |
| 65 | ** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the |
| 66 | ** TH1 eval step, thus the output will be (presumably) a |
| 67 | ** TH1-generated/processed pikchr script (or whatever else the TH1 |
| 68 | ** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even |
| 69 | ** if it is not specified. |
| 70 | ** |
| 71 | ** All of the remaining flags listed below are ignored if |
| 72 | ** PIKCHR_PROCESS_TH1_NOSVG is specified! |
| 73 | ** |
| 74 | ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV |
| 75 | ** element which specifies a max-width style value based on the SVG's |
| 76 | ** calculated size. This flag has multiple mutually exclusive forms: |
| 77 | ** |
| 78 | ** - PIKCHR_PROCESS_DIV uses default element alignment. |
| @@ -116,14 +98,14 @@ | |
| 116 | ** |
| 117 | ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting |
| 118 | ** error report is wrapped in a PRE element, else it is retained |
| 119 | ** as-is (intended only for console output). |
| 120 | */ |
| 121 | int pikchr_process(const char * zIn, int pikFlags, int thFlags, |
| 122 | Blob * pOut){ |
| 123 | Blob bIn = empty_blob; |
| 124 | int isErr = 0; |
| 125 | const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) |
| 126 | ? safe_html_nonce(1) : 0; |
| 127 | |
| 128 | if(!(PIKCHR_PROCESS_DIV & pikFlags) |
| 129 | /* If any DIV_xxx flags are set, set DIV */ |
| @@ -135,115 +117,87 @@ | |
| 135 | | PIKCHR_PROCESS_DIV_SOURCE_INLINE |
| 136 | | PIKCHR_PROCESS_DIV_TOGGLE |
| 137 | ) & pikFlags){ |
| 138 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 139 | } |
| 140 | if(!(PIKCHR_PROCESS_TH1 & pikFlags) |
| 141 | /* If any TH1_xxx flags are set, set TH1 */ |
| 142 | && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){ |
| 143 | pikFlags |= PIKCHR_PROCESS_TH1; |
| 144 | } |
| 145 | if(zNonce){ |
| 146 | blob_appendf(pOut, "%s\n", zNonce); |
| 147 | } |
| 148 | if(PIKCHR_PROCESS_TH1 & pikFlags){ |
| 149 | Blob out = empty_blob; |
| 150 | isErr = Th_RenderToBlob(zIn, &out, thFlags) |
| 151 | ? 1 : 0; |
| 152 | if(isErr){ |
| 153 | blob_append(pOut, blob_str(&out), blob_size(&out)); |
| 154 | blob_reset(&out); |
| 155 | }else{ |
| 156 | bIn = out; |
| 157 | } |
| 158 | }else{ |
| 159 | blob_init(&bIn, zIn, -1); |
| 160 | } |
| 161 | if(!isErr){ |
| 162 | if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){ |
| 163 | blob_append(pOut, blob_str(&bIn), blob_size(&bIn)); |
| 164 | }else{ |
| 165 | int w = 0, h = 0; |
| 166 | const char * zContent = blob_str(&bIn); |
| 167 | char *zOut; |
| 168 | zOut = pikchr(zContent, "pikchr", |
| 169 | 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH), |
| 170 | &w, &h); |
| 171 | if( w>0 && h>0 ){ |
| 172 | const char * zClassToggle = ""; |
| 173 | const char * zClassSource = ""; |
| 174 | const char * zWrapperClass = ""; |
| 175 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 176 | if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){ |
| 177 | zWrapperClass = " center"; |
| 178 | }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){ |
| 179 | zWrapperClass = " indent"; |
| 180 | }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){ |
| 181 | zWrapperClass = " float-left"; |
| 182 | }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){ |
| 183 | zWrapperClass = " float-right"; |
| 184 | } |
| 185 | if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){ |
| 186 | zClassToggle = " toggle"; |
| 187 | } |
| 188 | if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){ |
| 189 | if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ |
| 190 | zClassSource = " source source-inline"; |
| 191 | }else{ |
| 192 | zClassSource = " source-inline"; |
| 193 | } |
| 194 | pikFlags |= PIKCHR_PROCESS_SRC; |
| 195 | }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ |
| 196 | zClassSource = " source"; |
| 197 | pikFlags |= PIKCHR_PROCESS_SRC; |
| 198 | } |
| 199 | blob_appendf(pOut,"<div class='pikchr-wrapper" |
| 200 | "%s%s%s'>" |
| 201 | "<div class=\"pikchr-svg\" " |
| 202 | "style=\"max-width:%dpx\">\n", |
| 203 | zWrapperClass/*safe-for-%s*/, |
| 204 | zClassToggle/*safe-for-%s*/, |
| 205 | zClassSource/*safe-for-%s*/, w); |
| 206 | } |
| 207 | blob_append(pOut, zOut, -1); |
| 208 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 209 | blob_append(pOut, "</div>\n", 7); |
| 210 | } |
| 211 | if(PIKCHR_PROCESS_SRC & pikFlags){ |
| 212 | static int counter = 0; |
| 213 | ++counter; |
| 214 | blob_appendf(pOut, "<div class='pikchr-src'>" |
| 215 | "<pre id='pikchr-src-%d'>%h</pre>" |
| 216 | "<span class='hidden'>" |
| 217 | "<a href='%R/pikchrshow?fromSession' " |
| 218 | "class='pikchr-src-pikchrshow' target='_new-%d' " |
| 219 | "data-pikchrid='pikchr-src-%d' " |
| 220 | "title='Open this pikchr in /pikchrshow'" |
| 221 | ">→ /pikchrshow</a></span>" |
| 222 | "</div>\n", |
| 223 | counter, blob_str(&bIn), counter, counter); |
| 224 | } |
| 225 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 226 | blob_append(pOut, "</div>\n", 7); |
| 227 | } |
| 228 | }else{ |
| 229 | isErr = 2; |
| 230 | if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ |
| 231 | blob_append(pOut, "<pre class='error'>\n", 20); |
| 232 | } |
| 233 | blob_appendf(pOut, "%h", zOut); |
| 234 | if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ |
| 235 | blob_append(pOut, "\n</pre>\n", 8); |
| 236 | } |
| 237 | } |
| 238 | fossil_free(zOut); |
| 239 | } |
| 240 | } |
| 241 | if(zNonce){ |
| 242 | blob_appendf(pOut, "%s\n", zNonce); |
| 243 | } |
| 244 | blob_reset(&bIn); |
| 245 | return isErr; |
| 246 | } |
| 247 | |
| 248 | /* |
| 249 | ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to |
| @@ -279,11 +233,11 @@ | |
| 279 | TODO: respond with JSON instead.*/ |
| 280 | cgi_set_content_type("text/html"); |
| 281 | if(zContent && *zContent){ |
| 282 | Blob out = empty_blob; |
| 283 | const int isErr = |
| 284 | pikchr_process(zContent, pikFlags, 0, &out); |
| 285 | if(isErr){ |
| 286 | cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); |
| 287 | } |
| 288 | CX("%b", &out); |
| 289 | blob_reset(&out); |
| @@ -384,11 +338,11 @@ | |
| 384 | /* Reminder: Firefox does not properly flexbox a LEGEND |
| 385 | element, always flowing it in column mode. */); |
| 386 | CX("<div id='pikchrshow-output'>"); |
| 387 | if(*zContent){ |
| 388 | Blob out = empty_blob; |
| 389 | pikchr_process(zContent, pikFlags, 0, &out); |
| 390 | CX("%b", &out); |
| 391 | blob_reset(&out); |
| 392 | } CX("</div>"/*#pikchrshow-output*/); |
| 393 | } CX("</fieldset>"/*#pikchrshow-output-wrapper*/); |
| 394 | } CX("</div>"/*sbs-wrapper*/); |
| @@ -561,60 +515,23 @@ | |
| 561 | ** |
| 562 | ** -src Store the input pikchr's source code in the output as |
| 563 | ** a separate element adjacent to the SVG one. Implied |
| 564 | ** by -div-source. |
| 565 | ** |
| 566 | ** |
| 567 | ** -th Process the input using TH1 before passing it to pikchr |
| 568 | ** |
| 569 | ** -th-novar Disable $var and $<var> TH1 processing. Use this if the |
| 570 | ** pikchr script uses '$' for its own purposes and that |
| 571 | ** causes issues. This only affects parsing of '$' outside |
| 572 | ** of TH1 script blocks. Code in such blocks is unaffected. |
| 573 | ** |
| 574 | ** -th-nosvg When using -th, output the post-TH1'd script |
| 575 | ** instead of the pikchr-rendered output |
| 576 | ** |
| 577 | ** -th-trace Trace TH1 execution (for debugging purposes) |
| 578 | ** |
| 579 | ** -dark Change pikchr colors to assume a dark-mode theme. |
| 580 | ** |
| 581 | ** |
| 582 | ** The -div-indent/center/left/right flags may not be combined. |
| 583 | ** |
| 584 | ** TH1-related Notes and Caveats: |
| 585 | ** |
| 586 | ** If the -th flag is used, this command must open a fossil database |
| 587 | ** for certain functionality to work (via a check-out or the -R REPO |
| 588 | ** flag). If opening a db fails, execution will continue but any TH1 |
| 589 | ** commands which require a db will trigger a fatal error. |
| 590 | ** |
| 591 | ** In Fossil skins, TH1 variables in the form $varName are expanded |
| 592 | ** as-is and those in the form $<varName> are htmlized in the |
| 593 | ** resulting output. This processor disables the htmlizing step, so $x |
| 594 | ** and $<x> are equivalent unless the TH1-processed pikchr script |
| 595 | ** invokes the TH1 command [enable_htmlify 1] to enable it. Normally |
| 596 | ** that option will interfere with pikchr output, however, e.g. by |
| 597 | ** HTML-encoding double-quotes. |
| 598 | ** |
| 599 | ** Many of the fossil-installed TH1 functions simply do not make any |
| 600 | ** sense for pikchr scripts. |
| 601 | */ |
| 602 | void pikchr_cmd(void){ |
| 603 | Blob bIn = empty_blob; |
| 604 | Blob bOut = empty_blob; |
| 605 | const char * zInfile = "-"; |
| 606 | const char * zOutfile = "-"; |
| 607 | const int fTh1 = find_option("th",0,0)!=0; |
| 608 | const int fNosvg = find_option("th-nosvg",0,0)!=0; |
| 609 | int isErr = 0; |
| 610 | int pikFlags = find_option("src",0,0)!=0 |
| 611 | ? PIKCHR_PROCESS_SRC : 0; |
| 612 | u32 fThFlags = TH_INIT_NO_ENCODE |
| 613 | | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0); |
| 614 | |
| 615 | Th_InitTraceLog()/*processes -th-trace flag*/; |
| 616 | |
| 617 | if(find_option("div",0,0)!=0){ |
| 618 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 619 | }else if(find_option("div-indent",0,0)!=0){ |
| 620 | pikFlags |= PIKCHR_PROCESS_DIV_INDENT; |
| @@ -644,24 +561,14 @@ | |
| 644 | } |
| 645 | if(g.argc>3){ |
| 646 | zOutfile = g.argv[3]; |
| 647 | } |
| 648 | blob_read_from_file(&bIn, zInfile, ExtFILE); |
| 649 | if(fTh1){ |
| 650 | db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0) |
| 651 | /* ^^^ needed for certain TH1 functions to work */; |
| 652 | pikFlags |= PIKCHR_PROCESS_TH1; |
| 653 | if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG; |
| 654 | } |
| 655 | isErr = pikchr_process(blob_str(&bIn), pikFlags, |
| 656 | fTh1 ? fThFlags : 0, &bOut); |
| 657 | if(isErr){ |
| 658 | fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr", |
| 659 | 1==isErr ? ' ' : '\n', |
| 660 | &bOut); |
| 661 | }else{ |
| 662 | blob_write_to_file(&bOut, zOutfile); |
| 663 | } |
| 664 | Th_PrintTraceLog(); |
| 665 | blob_reset(&bIn); |
| 666 | blob_reset(&bOut); |
| 667 | } |
| 668 |
| --- src/pikchrshow.c | |
| +++ src/pikchrshow.c | |
| @@ -27,12 +27,10 @@ | |
| 27 | /* The first two must match the values from pikchr.c */ |
| 28 | #define PIKCHR_PROCESS_PLAINTEXT_ERRORS 0x0001 |
| 29 | #define PIKCHR_PROCESS_DARK_MODE 0x0002 |
| 30 | /* end of flags supported directly by pikchr() */ |
| 31 | #define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ |
| 32 | #define PIKCHR_PROCESS_NONCE 0x0010 |
| 33 | #define PIKCHR_PROCESS_ERR_PRE 0x0020 |
| 34 | #define PIKCHR_PROCESS_SRC 0x0040 |
| 35 | #define PIKCHR_PROCESS_DIV 0x0080 |
| 36 | #define PIKCHR_PROCESS_DIV_INDENT 0x0100 |
| @@ -43,36 +41,20 @@ | |
| 41 | #define PIKCHR_PROCESS_DIV_SOURCE 0x2000 |
| 42 | #define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 |
| 43 | #endif |
| 44 | |
| 45 | /* |
| 46 | ** Processes a pikchr script. zIn is the NUL-terminated input |
| 47 | ** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx |
| 48 | ** flags documented below. Output is sent to pOut, |
| 49 | ** |
| 50 | ** Returns 0 on success, or non-zero if pikchr processing failed. |
| 51 | ** In either case, the error message (if any) from pikchr will be |
| 52 | ** appended to pOut. |
| 53 | ** |
| 54 | ** pikFlags flag descriptions: |
| 55 | ** |
| 56 | ** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV |
| 57 | ** element which specifies a max-width style value based on the SVG's |
| 58 | ** calculated size. This flag has multiple mutually exclusive forms: |
| 59 | ** |
| 60 | ** - PIKCHR_PROCESS_DIV uses default element alignment. |
| @@ -116,14 +98,14 @@ | |
| 98 | ** |
| 99 | ** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting |
| 100 | ** error report is wrapped in a PRE element, else it is retained |
| 101 | ** as-is (intended only for console output). |
| 102 | */ |
| 103 | int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){ |
| 104 | int isErr = 0; |
| 105 | int w = 0, h = 0; |
| 106 | char *zOut; |
| 107 | const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) |
| 108 | ? safe_html_nonce(1) : 0; |
| 109 | |
| 110 | if(!(PIKCHR_PROCESS_DIV & pikFlags) |
| 111 | /* If any DIV_xxx flags are set, set DIV */ |
| @@ -135,115 +117,87 @@ | |
| 117 | | PIKCHR_PROCESS_DIV_SOURCE_INLINE |
| 118 | | PIKCHR_PROCESS_DIV_TOGGLE |
| 119 | ) & pikFlags){ |
| 120 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 121 | } |
| 122 | if(zNonce){ |
| 123 | blob_appendf(pOut, "%s\n", zNonce); |
| 124 | } |
| 125 | zOut = pikchr(zIn, "pikchr", |
| 126 | 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH), |
| 127 | &w, &h); |
| 128 | if( w>0 && h>0 ){ |
| 129 | const char * zClassToggle = ""; |
| 130 | const char * zClassSource = ""; |
| 131 | const char * zWrapperClass = ""; |
| 132 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 133 | if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){ |
| 134 | zWrapperClass = " center"; |
| 135 | }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){ |
| 136 | zWrapperClass = " indent"; |
| 137 | }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){ |
| 138 | zWrapperClass = " float-left"; |
| 139 | }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){ |
| 140 | zWrapperClass = " float-right"; |
| 141 | } |
| 142 | if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){ |
| 143 | zClassToggle = " toggle"; |
| 144 | } |
| 145 | if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){ |
| 146 | if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ |
| 147 | zClassSource = " source source-inline"; |
| 148 | }else{ |
| 149 | zClassSource = " source-inline"; |
| 150 | } |
| 151 | pikFlags |= PIKCHR_PROCESS_SRC; |
| 152 | }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ |
| 153 | zClassSource = " source"; |
| 154 | pikFlags |= PIKCHR_PROCESS_SRC; |
| 155 | } |
| 156 | blob_appendf(pOut,"<div class='pikchr-wrapper" |
| 157 | "%s%s%s'>" |
| 158 | "<div class=\"pikchr-svg\" " |
| 159 | "style=\"max-width:%dpx\">\n", |
| 160 | zWrapperClass/*safe-for-%s*/, |
| 161 | zClassToggle/*safe-for-%s*/, |
| 162 | zClassSource/*safe-for-%s*/, w); |
| 163 | } |
| 164 | blob_append(pOut, zOut, -1); |
| 165 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 166 | blob_append(pOut, "</div>\n", 7); |
| 167 | } |
| 168 | if(PIKCHR_PROCESS_SRC & pikFlags){ |
| 169 | static int counter = 0; |
| 170 | ++counter; |
| 171 | blob_appendf(pOut, "<div class='pikchr-src'>" |
| 172 | "<pre id='pikchr-src-%d'>%h</pre>" |
| 173 | "<span class='hidden'>" |
| 174 | "<a href='%R/pikchrshow?fromSession' " |
| 175 | "class='pikchr-src-pikchrshow' target='_new-%d' " |
| 176 | "data-pikchrid='pikchr-src-%d' " |
| 177 | "title='Open this pikchr in /pikchrshow'" |
| 178 | ">→ /pikchrshow</a></span>" |
| 179 | "</div>\n", |
| 180 | counter, zIn, counter, counter); |
| 181 | } |
| 182 | if(PIKCHR_PROCESS_DIV & pikFlags){ |
| 183 | blob_append(pOut, "</div>\n", 7); |
| 184 | } |
| 185 | }else{ |
| 186 | isErr = 2; |
| 187 | if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ |
| 188 | blob_append(pOut, "<pre class='error'>\n", 20); |
| 189 | } |
| 190 | blob_appendf(pOut, "%h", zOut); |
| 191 | if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ |
| 192 | blob_append(pOut, "\n</pre>\n", 8); |
| 193 | } |
| 194 | } |
| 195 | fossil_free(zOut); |
| 196 | if(zNonce){ |
| 197 | blob_appendf(pOut, "%s\n", zNonce); |
| 198 | } |
| 199 | return isErr; |
| 200 | } |
| 201 | |
| 202 | /* |
| 203 | ** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to |
| @@ -279,11 +233,11 @@ | |
| 233 | TODO: respond with JSON instead.*/ |
| 234 | cgi_set_content_type("text/html"); |
| 235 | if(zContent && *zContent){ |
| 236 | Blob out = empty_blob; |
| 237 | const int isErr = |
| 238 | pikchr_process(zContent, pikFlags, &out); |
| 239 | if(isErr){ |
| 240 | cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); |
| 241 | } |
| 242 | CX("%b", &out); |
| 243 | blob_reset(&out); |
| @@ -384,11 +338,11 @@ | |
| 338 | /* Reminder: Firefox does not properly flexbox a LEGEND |
| 339 | element, always flowing it in column mode. */); |
| 340 | CX("<div id='pikchrshow-output'>"); |
| 341 | if(*zContent){ |
| 342 | Blob out = empty_blob; |
| 343 | pikchr_process(zContent, pikFlags, &out); |
| 344 | CX("%b", &out); |
| 345 | blob_reset(&out); |
| 346 | } CX("</div>"/*#pikchrshow-output*/); |
| 347 | } CX("</fieldset>"/*#pikchrshow-output-wrapper*/); |
| 348 | } CX("</div>"/*sbs-wrapper*/); |
| @@ -561,60 +515,23 @@ | |
| 515 | ** |
| 516 | ** -src Store the input pikchr's source code in the output as |
| 517 | ** a separate element adjacent to the SVG one. Implied |
| 518 | ** by -div-source. |
| 519 | ** |
| 520 | ** -dark Change pikchr colors to assume a dark-mode theme. |
| 521 | ** |
| 522 | ** |
| 523 | ** The -div-indent/center/left/right flags may not be combined. |
| 524 | */ |
| 525 | void pikchr_cmd(void){ |
| 526 | Blob bIn = empty_blob; |
| 527 | Blob bOut = empty_blob; |
| 528 | const char * zInfile = "-"; |
| 529 | const char * zOutfile = "-"; |
| 530 | int isErr = 0; |
| 531 | int pikFlags = find_option("src",0,0)!=0 |
| 532 | ? PIKCHR_PROCESS_SRC : 0; |
| 533 | |
| 534 | if(find_option("div",0,0)!=0){ |
| 535 | pikFlags |= PIKCHR_PROCESS_DIV; |
| 536 | }else if(find_option("div-indent",0,0)!=0){ |
| 537 | pikFlags |= PIKCHR_PROCESS_DIV_INDENT; |
| @@ -644,24 +561,14 @@ | |
| 561 | } |
| 562 | if(g.argc>3){ |
| 563 | zOutfile = g.argv[3]; |
| 564 | } |
| 565 | blob_read_from_file(&bIn, zInfile, ExtFILE); |
| 566 | isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut); |
| 567 | if(isErr){ |
| 568 | fossil_fatal("pikchr ERROR: %b", &bOut); |
| 569 | }else{ |
| 570 | blob_write_to_file(&bOut, zOutfile); |
| 571 | } |
| 572 | blob_reset(&bIn); |
| 573 | blob_reset(&bOut); |
| 574 | } |
| 575 |
+7
-2
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -1077,10 +1077,11 @@ | ||
| 1077 | 1077 | time_t now; |
| 1078 | 1078 | FILE *out; |
| 1079 | 1079 | const char *z; |
| 1080 | 1080 | int i; |
| 1081 | 1081 | int bDetail = 0; |
| 1082 | + int bBrief = 0; | |
| 1082 | 1083 | va_list ap; |
| 1083 | 1084 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER", |
| 1084 | 1085 | "HTTP_USER_AGENT", |
| 1085 | 1086 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 1086 | 1087 | "REQUEST_URI", "SCRIPT_NAME" }; |
| @@ -1098,16 +1099,20 @@ | ||
| 1098 | 1099 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1099 | 1100 | va_start(ap, zFormat); |
| 1100 | 1101 | if( zFormat[0]=='X' ){ |
| 1101 | 1102 | bDetail = 1; |
| 1102 | 1103 | zFormat++; |
| 1104 | + }else if( strncmp(zFormat,"SMTP:",5)==0 ){ | |
| 1105 | + bBrief = 1; | |
| 1103 | 1106 | } |
| 1104 | 1107 | vfprintf(out, zFormat, ap); |
| 1105 | 1108 | fprintf(out, " (pid %d)\n", (int)getpid()); |
| 1106 | 1109 | va_end(ap); |
| 1107 | 1110 | if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase); |
| 1108 | - if( bDetail ){ | |
| 1111 | + if( bBrief ){ | |
| 1112 | + /* Say nothing more */ | |
| 1113 | + }else if( bDetail ){ | |
| 1109 | 1114 | cgi_print_all(1,3,out); |
| 1110 | 1115 | }else{ |
| 1111 | 1116 | for(i=0; i<count(azEnv); i++){ |
| 1112 | 1117 | char *p; |
| 1113 | 1118 | if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){ |
| @@ -1116,11 +1121,11 @@ | ||
| 1116 | 1121 | }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){ |
| 1117 | 1122 | fprintf(out, "%s=%s\n", azEnv[i], z); |
| 1118 | 1123 | } |
| 1119 | 1124 | } |
| 1120 | 1125 | } |
| 1121 | - fclose(out); | |
| 1126 | + if( out!=stderr ) fclose(out); | |
| 1122 | 1127 | } |
| 1123 | 1128 | |
| 1124 | 1129 | /* |
| 1125 | 1130 | ** The following variable becomes true while processing a fatal error |
| 1126 | 1131 | ** or a panic. If additional "recursive-fatal" errors occur while |
| 1127 | 1132 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -1077,10 +1077,11 @@ | |
| 1077 | time_t now; |
| 1078 | FILE *out; |
| 1079 | const char *z; |
| 1080 | int i; |
| 1081 | int bDetail = 0; |
| 1082 | va_list ap; |
| 1083 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER", |
| 1084 | "HTTP_USER_AGENT", |
| 1085 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 1086 | "REQUEST_URI", "SCRIPT_NAME" }; |
| @@ -1098,16 +1099,20 @@ | |
| 1098 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1099 | va_start(ap, zFormat); |
| 1100 | if( zFormat[0]=='X' ){ |
| 1101 | bDetail = 1; |
| 1102 | zFormat++; |
| 1103 | } |
| 1104 | vfprintf(out, zFormat, ap); |
| 1105 | fprintf(out, " (pid %d)\n", (int)getpid()); |
| 1106 | va_end(ap); |
| 1107 | if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase); |
| 1108 | if( bDetail ){ |
| 1109 | cgi_print_all(1,3,out); |
| 1110 | }else{ |
| 1111 | for(i=0; i<count(azEnv); i++){ |
| 1112 | char *p; |
| 1113 | if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){ |
| @@ -1116,11 +1121,11 @@ | |
| 1116 | }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){ |
| 1117 | fprintf(out, "%s=%s\n", azEnv[i], z); |
| 1118 | } |
| 1119 | } |
| 1120 | } |
| 1121 | fclose(out); |
| 1122 | } |
| 1123 | |
| 1124 | /* |
| 1125 | ** The following variable becomes true while processing a fatal error |
| 1126 | ** or a panic. If additional "recursive-fatal" errors occur while |
| 1127 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -1077,10 +1077,11 @@ | |
| 1077 | time_t now; |
| 1078 | FILE *out; |
| 1079 | const char *z; |
| 1080 | int i; |
| 1081 | int bDetail = 0; |
| 1082 | int bBrief = 0; |
| 1083 | va_list ap; |
| 1084 | static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER", |
| 1085 | "HTTP_USER_AGENT", |
| 1086 | "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", |
| 1087 | "REQUEST_URI", "SCRIPT_NAME" }; |
| @@ -1098,16 +1099,20 @@ | |
| 1099 | pNow->tm_hour, pNow->tm_min, pNow->tm_sec); |
| 1100 | va_start(ap, zFormat); |
| 1101 | if( zFormat[0]=='X' ){ |
| 1102 | bDetail = 1; |
| 1103 | zFormat++; |
| 1104 | }else if( strncmp(zFormat,"SMTP:",5)==0 ){ |
| 1105 | bBrief = 1; |
| 1106 | } |
| 1107 | vfprintf(out, zFormat, ap); |
| 1108 | fprintf(out, " (pid %d)\n", (int)getpid()); |
| 1109 | va_end(ap); |
| 1110 | if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase); |
| 1111 | if( bBrief ){ |
| 1112 | /* Say nothing more */ |
| 1113 | }else if( bDetail ){ |
| 1114 | cgi_print_all(1,3,out); |
| 1115 | }else{ |
| 1116 | for(i=0; i<count(azEnv); i++){ |
| 1117 | char *p; |
| 1118 | if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){ |
| @@ -1116,11 +1121,11 @@ | |
| 1121 | }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){ |
| 1122 | fprintf(out, "%s=%s\n", azEnv[i], z); |
| 1123 | } |
| 1124 | } |
| 1125 | } |
| 1126 | if( out!=stderr ) fclose(out); |
| 1127 | } |
| 1128 | |
| 1129 | /* |
| 1130 | ** The following variable becomes true while processing a fatal error |
| 1131 | ** or a panic. If additional "recursive-fatal" errors occur while |
| 1132 |
+73
-28
| --- src/repolist.c | ||
| +++ src/repolist.c | ||
| @@ -126,12 +126,20 @@ | ||
| 126 | 126 | int allRepo; /* True if running "fossil ui all". |
| 127 | 127 | ** False if a directory scan of base for repos */ |
| 128 | 128 | Blob html; /* Html for the body of the repository list */ |
| 129 | 129 | char *zSkinRepo = 0; /* Name of the repository database used for skins */ |
| 130 | 130 | char *zSkinUrl = 0; /* URL for the skin database */ |
| 131 | + const char *zShow; /* Value of FOSSIL_REPOLIST_SHOW environment variable */ | |
| 132 | + int bShowDesc = 0; /* True to show the description column */ | |
| 133 | + int bShowLg = 0; /* True to show the login-group column */ | |
| 131 | 134 | |
| 132 | 135 | assert( g.db==0 ); |
| 136 | + zShow = P("FOSSIL_REPOLIST_SHOW"); | |
| 137 | + if( zShow ){ | |
| 138 | + bShowDesc = strstr(zShow,"description")!=0; | |
| 139 | + bShowLg = strstr(zShow,"login-group")!=0; | |
| 140 | + } | |
| 133 | 141 | blob_init(&html, 0, 0); |
| 134 | 142 | if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ |
| 135 | 143 | /* For the special case of the "repository directory" being "/", |
| 136 | 144 | ** show all of the repositories named in the ~/.fossil database. |
| 137 | 145 | ** |
| @@ -150,10 +158,12 @@ | ||
| 150 | 158 | }else{ |
| 151 | 159 | /* The default case: All repositories under the g.zRepositoryName |
| 152 | 160 | ** directory. |
| 153 | 161 | */ |
| 154 | 162 | blob_init(&base, g.zRepositoryName, -1); |
| 163 | + db_close(0); | |
| 164 | + assert( g.db==0 ); | |
| 155 | 165 | sqlite3_open(":memory:", &g.db); |
| 156 | 166 | db_multi_exec("CREATE TABLE sfile(pathname TEXT);"); |
| 157 | 167 | db_multi_exec("CREATE TABLE vfile(pathname);"); |
| 158 | 168 | vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE); |
| 159 | 169 | db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'" |
| @@ -171,19 +181,50 @@ | ||
| 171 | 181 | g.localOpen = 0; |
| 172 | 182 | return 0; |
| 173 | 183 | }else{ |
| 174 | 184 | Stmt q; |
| 175 | 185 | double rNow; |
| 176 | - blob_append_sql(&html, | |
| 186 | + char zType[16]; /* Column type letters for class "sortable" */ | |
| 187 | + int nType; | |
| 188 | + zType[0] = 't'; /* Repo name */ | |
| 189 | + zType[1] = 'x'; /* Space between repo-name and project-name */ | |
| 190 | + zType[2] = 't'; /* Project name */ | |
| 191 | + nType = 3; | |
| 192 | + if( bShowDesc ){ | |
| 193 | + zType[nType++] = 'x'; /* Space between name and description */ | |
| 194 | + zType[nType++] = 't'; /* Project description */ | |
| 195 | + } | |
| 196 | + zType[nType++] = 'x'; /* space before age */ | |
| 197 | + zType[nType++] = 'k'; /* Project age */ | |
| 198 | + if( bShowLg ){ | |
| 199 | + zType[nType++] = 'x'; /* space before login-group */ | |
| 200 | + zType[nType++] = 't'; /* Login Group */ | |
| 201 | + } | |
| 202 | + zType[nType] = 0; | |
| 203 | + blob_appendf(&html, | |
| 177 | 204 | "<table border='0' class='sortable' data-init-sort='1'" |
| 178 | - " data-column-types='txtxtxkxt' cellspacing='0' cellpadding='0'><thead>\n" | |
| 179 | - "<tr><th>Filename<th width='7'>" | |
| 180 | - "<th width='25%%'>Project Name<th width='10'>" | |
| 181 | - "<th width='25%%'>Project Description<th width='5'>" | |
| 182 | - "<th><nobr>Last Modified</nobr><th width='1'>" | |
| 183 | - "<th><nobr>Login Group</nobr></tr>\n" | |
| 184 | - "</thead><tbody>\n"); | |
| 205 | + " data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n" | |
| 206 | + "<tr><th>Filename</th><th> </th>\n" | |
| 207 | + "<th%s><nobr>Project Name</nobr></th>\n", | |
| 208 | + zType, (bShowDesc ? " width='25%'" : "")); | |
| 209 | + if( bShowDesc ){ | |
| 210 | + blob_appendf(&html, | |
| 211 | + "<th> </th>\n" | |
| 212 | + "<th width='25%%'><nobr>Project Description</nobr></th>\n" | |
| 213 | + ); | |
| 214 | + } | |
| 215 | + blob_appendf(&html, | |
| 216 | + "<th> </th>" | |
| 217 | + "<th><nobr>Last Modified</nobr></th>\n" | |
| 218 | + ); | |
| 219 | + if( bShowLg ){ | |
| 220 | + blob_appendf(&html, | |
| 221 | + "<th> </th>" | |
| 222 | + "<th><nobr>Login Group</nobr></th></tr>\n" | |
| 223 | + ); | |
| 224 | + } | |
| 225 | + blob_appendf(&html,"</thead><tbody>\n"); | |
| 185 | 226 | db_prepare(&q, "SELECT pathname" |
| 186 | 227 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 187 | 228 | rNow = db_double(0, "SELECT julianday('now')"); |
| 188 | 229 | while( db_step(&q)==SQLITE_ROW ){ |
| 189 | 230 | const char *zName = db_column_text(&q, 0); |
| @@ -242,21 +283,21 @@ | ||
| 242 | 283 | if( x.rMTime==0.0 ){ |
| 243 | 284 | /* This repository has no entry in the "event" table. |
| 244 | 285 | ** Its age will still be maximum, so data-sortkey will work. */ |
| 245 | 286 | zAge = mprintf("unknown"); |
| 246 | 287 | } |
| 247 | - blob_append_sql(&html, "<tr><td valign='top'><nobr>"); | |
| 288 | + blob_appendf(&html, "<tr><td valign='top'><nobr>"); | |
| 248 | 289 | if( !file_ends_with_repository_extension(zName,0) ){ |
| 249 | 290 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 250 | 291 | ** do not work for repositories whose names do not end in ".fossil". |
| 251 | 292 | ** So do not hyperlink those cases. */ |
| 252 | - blob_append_sql(&html,"%h",zName); | |
| 293 | + blob_appendf(&html,"%h",zName); | |
| 253 | 294 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 254 | 295 | /* Do not show hyperlinks for hidden repos */ |
| 255 | - blob_append_sql(&html, "%h (hidden)", zName); | |
| 296 | + blob_appendf(&html, "%h (hidden)", zName); | |
| 256 | 297 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 257 | - blob_append_sql(&html, | |
| 298 | + blob_appendf(&html, | |
| 258 | 299 | "<a href='%R/%T/home' target='_blank'>/%h</a>\n", |
| 259 | 300 | zUrl, zName); |
| 260 | 301 | }else if( file_ends_with_repository_extension(zName,1) ){ |
| 261 | 302 | /* As described in |
| 262 | 303 | ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if |
| @@ -273,56 +314,60 @@ | ||
| 273 | 314 | , zDirPart |
| 274 | 315 | #if USE_SEE |
| 275 | 316 | , zDirPart |
| 276 | 317 | #endif |
| 277 | 318 | ) ){ |
| 278 | - blob_append_sql(&html, | |
| 319 | + blob_appendf(&html, | |
| 279 | 320 | "<s>%h</s> (directory/repo name collision)\n", |
| 280 | 321 | zName); |
| 281 | 322 | }else{ |
| 282 | - blob_append_sql(&html, | |
| 323 | + blob_appendf(&html, | |
| 283 | 324 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 284 | 325 | zUrl, zName); |
| 285 | 326 | } |
| 286 | 327 | fossil_free(zDirPart); |
| 287 | 328 | }else{ |
| 288 | - blob_append_sql(&html, | |
| 329 | + blob_appendf(&html, | |
| 289 | 330 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 290 | 331 | zUrl, zName); |
| 291 | 332 | } |
| 292 | - blob_append_sql(&html,"</nobr>"); | |
| 333 | + blob_appendf(&html,"</nobr></td>\n"); | |
| 293 | 334 | if( x.zProjName ){ |
| 294 | - blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n", | |
| 295 | - x.zProjName); | |
| 335 | + blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n", | |
| 336 | + x.zProjName); | |
| 296 | 337 | fossil_free(x.zProjName); |
| 297 | 338 | }else{ |
| 298 | - blob_append_sql(&html, "<td></td><td></td>\n"); | |
| 339 | + blob_appendf(&html, "<td> </td><td></td>\n"); | |
| 299 | 340 | } |
| 300 | - if( x.zProjDesc ){ | |
| 301 | - blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n", | |
| 341 | + if( !bShowDesc ){ | |
| 342 | + /* Do nothing */ | |
| 343 | + }else if( x.zProjDesc ){ | |
| 344 | + blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n", | |
| 302 | 345 | x.zProjDesc); |
| 303 | 346 | fossil_free(x.zProjDesc); |
| 304 | 347 | }else{ |
| 305 | - blob_append_sql(&html, "<td></td><td></td>\n"); | |
| 348 | + blob_appendf(&html, "<td> </td><td></td>\n"); | |
| 306 | 349 | } |
| 307 | - blob_append_sql(&html, | |
| 308 | - "<td></td><td data-sortkey='%08x' align='center' valign='top'>" | |
| 350 | + blob_appendf(&html, | |
| 351 | + "<td> </td><td data-sortkey='%08x' align='center' valign='top'>" | |
| 309 | 352 | "<nobr>%h</nobr></td>\n", |
| 310 | 353 | (int)iAge, zAge); |
| 311 | 354 | fossil_free(zAge); |
| 312 | - if( x.zLoginGroup ){ | |
| 313 | - blob_append_sql(&html, "<td></td><td valign='top'>" | |
| 355 | + if( !bShowLg ){ | |
| 356 | + blob_appendf(&html, "</tr>\n"); | |
| 357 | + }else if( x.zLoginGroup ){ | |
| 358 | + blob_appendf(&html, "<td> </td><td valign='top'>" | |
| 314 | 359 | "<nobr>%h</nobr></td></tr>\n", |
| 315 | 360 | x.zLoginGroup); |
| 316 | 361 | fossil_free(x.zLoginGroup); |
| 317 | 362 | }else{ |
| 318 | - blob_append_sql(&html, "<td></td><td></td></tr>\n"); | |
| 363 | + blob_appendf(&html, "<td> </td><td></td></tr>\n"); | |
| 319 | 364 | } |
| 320 | 365 | sqlite3_free(zUrl); |
| 321 | 366 | } |
| 322 | 367 | db_finalize(&q); |
| 323 | - blob_append_sql(&html,"</tbody></table>\n"); | |
| 368 | + blob_appendf(&html,"</tbody></table>\n"); | |
| 324 | 369 | } |
| 325 | 370 | if( zSkinRepo ){ |
| 326 | 371 | char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl); |
| 327 | 372 | g.zBaseURL = 0; |
| 328 | 373 | set_base_url(zNewBase); |
| 329 | 374 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -126,12 +126,20 @@ | |
| 126 | int allRepo; /* True if running "fossil ui all". |
| 127 | ** False if a directory scan of base for repos */ |
| 128 | Blob html; /* Html for the body of the repository list */ |
| 129 | char *zSkinRepo = 0; /* Name of the repository database used for skins */ |
| 130 | char *zSkinUrl = 0; /* URL for the skin database */ |
| 131 | |
| 132 | assert( g.db==0 ); |
| 133 | blob_init(&html, 0, 0); |
| 134 | if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ |
| 135 | /* For the special case of the "repository directory" being "/", |
| 136 | ** show all of the repositories named in the ~/.fossil database. |
| 137 | ** |
| @@ -150,10 +158,12 @@ | |
| 150 | }else{ |
| 151 | /* The default case: All repositories under the g.zRepositoryName |
| 152 | ** directory. |
| 153 | */ |
| 154 | blob_init(&base, g.zRepositoryName, -1); |
| 155 | sqlite3_open(":memory:", &g.db); |
| 156 | db_multi_exec("CREATE TABLE sfile(pathname TEXT);"); |
| 157 | db_multi_exec("CREATE TABLE vfile(pathname);"); |
| 158 | vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE); |
| 159 | db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'" |
| @@ -171,19 +181,50 @@ | |
| 171 | g.localOpen = 0; |
| 172 | return 0; |
| 173 | }else{ |
| 174 | Stmt q; |
| 175 | double rNow; |
| 176 | blob_append_sql(&html, |
| 177 | "<table border='0' class='sortable' data-init-sort='1'" |
| 178 | " data-column-types='txtxtxkxt' cellspacing='0' cellpadding='0'><thead>\n" |
| 179 | "<tr><th>Filename<th width='7'>" |
| 180 | "<th width='25%%'>Project Name<th width='10'>" |
| 181 | "<th width='25%%'>Project Description<th width='5'>" |
| 182 | "<th><nobr>Last Modified</nobr><th width='1'>" |
| 183 | "<th><nobr>Login Group</nobr></tr>\n" |
| 184 | "</thead><tbody>\n"); |
| 185 | db_prepare(&q, "SELECT pathname" |
| 186 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 187 | rNow = db_double(0, "SELECT julianday('now')"); |
| 188 | while( db_step(&q)==SQLITE_ROW ){ |
| 189 | const char *zName = db_column_text(&q, 0); |
| @@ -242,21 +283,21 @@ | |
| 242 | if( x.rMTime==0.0 ){ |
| 243 | /* This repository has no entry in the "event" table. |
| 244 | ** Its age will still be maximum, so data-sortkey will work. */ |
| 245 | zAge = mprintf("unknown"); |
| 246 | } |
| 247 | blob_append_sql(&html, "<tr><td valign='top'><nobr>"); |
| 248 | if( !file_ends_with_repository_extension(zName,0) ){ |
| 249 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 250 | ** do not work for repositories whose names do not end in ".fossil". |
| 251 | ** So do not hyperlink those cases. */ |
| 252 | blob_append_sql(&html,"%h",zName); |
| 253 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 254 | /* Do not show hyperlinks for hidden repos */ |
| 255 | blob_append_sql(&html, "%h (hidden)", zName); |
| 256 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 257 | blob_append_sql(&html, |
| 258 | "<a href='%R/%T/home' target='_blank'>/%h</a>\n", |
| 259 | zUrl, zName); |
| 260 | }else if( file_ends_with_repository_extension(zName,1) ){ |
| 261 | /* As described in |
| 262 | ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if |
| @@ -273,56 +314,60 @@ | |
| 273 | , zDirPart |
| 274 | #if USE_SEE |
| 275 | , zDirPart |
| 276 | #endif |
| 277 | ) ){ |
| 278 | blob_append_sql(&html, |
| 279 | "<s>%h</s> (directory/repo name collision)\n", |
| 280 | zName); |
| 281 | }else{ |
| 282 | blob_append_sql(&html, |
| 283 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 284 | zUrl, zName); |
| 285 | } |
| 286 | fossil_free(zDirPart); |
| 287 | }else{ |
| 288 | blob_append_sql(&html, |
| 289 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 290 | zUrl, zName); |
| 291 | } |
| 292 | blob_append_sql(&html,"</nobr>"); |
| 293 | if( x.zProjName ){ |
| 294 | blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n", |
| 295 | x.zProjName); |
| 296 | fossil_free(x.zProjName); |
| 297 | }else{ |
| 298 | blob_append_sql(&html, "<td></td><td></td>\n"); |
| 299 | } |
| 300 | if( x.zProjDesc ){ |
| 301 | blob_append_sql(&html, "<td></td><td valign='top'>%h</td>\n", |
| 302 | x.zProjDesc); |
| 303 | fossil_free(x.zProjDesc); |
| 304 | }else{ |
| 305 | blob_append_sql(&html, "<td></td><td></td>\n"); |
| 306 | } |
| 307 | blob_append_sql(&html, |
| 308 | "<td></td><td data-sortkey='%08x' align='center' valign='top'>" |
| 309 | "<nobr>%h</nobr></td>\n", |
| 310 | (int)iAge, zAge); |
| 311 | fossil_free(zAge); |
| 312 | if( x.zLoginGroup ){ |
| 313 | blob_append_sql(&html, "<td></td><td valign='top'>" |
| 314 | "<nobr>%h</nobr></td></tr>\n", |
| 315 | x.zLoginGroup); |
| 316 | fossil_free(x.zLoginGroup); |
| 317 | }else{ |
| 318 | blob_append_sql(&html, "<td></td><td></td></tr>\n"); |
| 319 | } |
| 320 | sqlite3_free(zUrl); |
| 321 | } |
| 322 | db_finalize(&q); |
| 323 | blob_append_sql(&html,"</tbody></table>\n"); |
| 324 | } |
| 325 | if( zSkinRepo ){ |
| 326 | char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl); |
| 327 | g.zBaseURL = 0; |
| 328 | set_base_url(zNewBase); |
| 329 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -126,12 +126,20 @@ | |
| 126 | int allRepo; /* True if running "fossil ui all". |
| 127 | ** False if a directory scan of base for repos */ |
| 128 | Blob html; /* Html for the body of the repository list */ |
| 129 | char *zSkinRepo = 0; /* Name of the repository database used for skins */ |
| 130 | char *zSkinUrl = 0; /* URL for the skin database */ |
| 131 | const char *zShow; /* Value of FOSSIL_REPOLIST_SHOW environment variable */ |
| 132 | int bShowDesc = 0; /* True to show the description column */ |
| 133 | int bShowLg = 0; /* True to show the login-group column */ |
| 134 | |
| 135 | assert( g.db==0 ); |
| 136 | zShow = P("FOSSIL_REPOLIST_SHOW"); |
| 137 | if( zShow ){ |
| 138 | bShowDesc = strstr(zShow,"description")!=0; |
| 139 | bShowLg = strstr(zShow,"login-group")!=0; |
| 140 | } |
| 141 | blob_init(&html, 0, 0); |
| 142 | if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ |
| 143 | /* For the special case of the "repository directory" being "/", |
| 144 | ** show all of the repositories named in the ~/.fossil database. |
| 145 | ** |
| @@ -150,10 +158,12 @@ | |
| 158 | }else{ |
| 159 | /* The default case: All repositories under the g.zRepositoryName |
| 160 | ** directory. |
| 161 | */ |
| 162 | blob_init(&base, g.zRepositoryName, -1); |
| 163 | db_close(0); |
| 164 | assert( g.db==0 ); |
| 165 | sqlite3_open(":memory:", &g.db); |
| 166 | db_multi_exec("CREATE TABLE sfile(pathname TEXT);"); |
| 167 | db_multi_exec("CREATE TABLE vfile(pathname);"); |
| 168 | vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE); |
| 169 | db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'" |
| @@ -171,19 +181,50 @@ | |
| 181 | g.localOpen = 0; |
| 182 | return 0; |
| 183 | }else{ |
| 184 | Stmt q; |
| 185 | double rNow; |
| 186 | char zType[16]; /* Column type letters for class "sortable" */ |
| 187 | int nType; |
| 188 | zType[0] = 't'; /* Repo name */ |
| 189 | zType[1] = 'x'; /* Space between repo-name and project-name */ |
| 190 | zType[2] = 't'; /* Project name */ |
| 191 | nType = 3; |
| 192 | if( bShowDesc ){ |
| 193 | zType[nType++] = 'x'; /* Space between name and description */ |
| 194 | zType[nType++] = 't'; /* Project description */ |
| 195 | } |
| 196 | zType[nType++] = 'x'; /* space before age */ |
| 197 | zType[nType++] = 'k'; /* Project age */ |
| 198 | if( bShowLg ){ |
| 199 | zType[nType++] = 'x'; /* space before login-group */ |
| 200 | zType[nType++] = 't'; /* Login Group */ |
| 201 | } |
| 202 | zType[nType] = 0; |
| 203 | blob_appendf(&html, |
| 204 | "<table border='0' class='sortable' data-init-sort='1'" |
| 205 | " data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n" |
| 206 | "<tr><th>Filename</th><th> </th>\n" |
| 207 | "<th%s><nobr>Project Name</nobr></th>\n", |
| 208 | zType, (bShowDesc ? " width='25%'" : "")); |
| 209 | if( bShowDesc ){ |
| 210 | blob_appendf(&html, |
| 211 | "<th> </th>\n" |
| 212 | "<th width='25%%'><nobr>Project Description</nobr></th>\n" |
| 213 | ); |
| 214 | } |
| 215 | blob_appendf(&html, |
| 216 | "<th> </th>" |
| 217 | "<th><nobr>Last Modified</nobr></th>\n" |
| 218 | ); |
| 219 | if( bShowLg ){ |
| 220 | blob_appendf(&html, |
| 221 | "<th> </th>" |
| 222 | "<th><nobr>Login Group</nobr></th></tr>\n" |
| 223 | ); |
| 224 | } |
| 225 | blob_appendf(&html,"</thead><tbody>\n"); |
| 226 | db_prepare(&q, "SELECT pathname" |
| 227 | " FROM sfile ORDER BY pathname COLLATE nocase;"); |
| 228 | rNow = db_double(0, "SELECT julianday('now')"); |
| 229 | while( db_step(&q)==SQLITE_ROW ){ |
| 230 | const char *zName = db_column_text(&q, 0); |
| @@ -242,21 +283,21 @@ | |
| 283 | if( x.rMTime==0.0 ){ |
| 284 | /* This repository has no entry in the "event" table. |
| 285 | ** Its age will still be maximum, so data-sortkey will work. */ |
| 286 | zAge = mprintf("unknown"); |
| 287 | } |
| 288 | blob_appendf(&html, "<tr><td valign='top'><nobr>"); |
| 289 | if( !file_ends_with_repository_extension(zName,0) ){ |
| 290 | /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands |
| 291 | ** do not work for repositories whose names do not end in ".fossil". |
| 292 | ** So do not hyperlink those cases. */ |
| 293 | blob_appendf(&html,"%h",zName); |
| 294 | } else if( sqlite3_strglob("*/.*", zName)==0 ){ |
| 295 | /* Do not show hyperlinks for hidden repos */ |
| 296 | blob_appendf(&html, "%h (hidden)", zName); |
| 297 | } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ |
| 298 | blob_appendf(&html, |
| 299 | "<a href='%R/%T/home' target='_blank'>/%h</a>\n", |
| 300 | zUrl, zName); |
| 301 | }else if( file_ends_with_repository_extension(zName,1) ){ |
| 302 | /* As described in |
| 303 | ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if |
| @@ -273,56 +314,60 @@ | |
| 314 | , zDirPart |
| 315 | #if USE_SEE |
| 316 | , zDirPart |
| 317 | #endif |
| 318 | ) ){ |
| 319 | blob_appendf(&html, |
| 320 | "<s>%h</s> (directory/repo name collision)\n", |
| 321 | zName); |
| 322 | }else{ |
| 323 | blob_appendf(&html, |
| 324 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 325 | zUrl, zName); |
| 326 | } |
| 327 | fossil_free(zDirPart); |
| 328 | }else{ |
| 329 | blob_appendf(&html, |
| 330 | "<a href='%R/%T/home' target='_blank'>%h</a>\n", |
| 331 | zUrl, zName); |
| 332 | } |
| 333 | blob_appendf(&html,"</nobr></td>\n"); |
| 334 | if( x.zProjName ){ |
| 335 | blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n", |
| 336 | x.zProjName); |
| 337 | fossil_free(x.zProjName); |
| 338 | }else{ |
| 339 | blob_appendf(&html, "<td> </td><td></td>\n"); |
| 340 | } |
| 341 | if( !bShowDesc ){ |
| 342 | /* Do nothing */ |
| 343 | }else if( x.zProjDesc ){ |
| 344 | blob_appendf(&html, "<td> </td><td valign='top'>%h</td>\n", |
| 345 | x.zProjDesc); |
| 346 | fossil_free(x.zProjDesc); |
| 347 | }else{ |
| 348 | blob_appendf(&html, "<td> </td><td></td>\n"); |
| 349 | } |
| 350 | blob_appendf(&html, |
| 351 | "<td> </td><td data-sortkey='%08x' align='center' valign='top'>" |
| 352 | "<nobr>%h</nobr></td>\n", |
| 353 | (int)iAge, zAge); |
| 354 | fossil_free(zAge); |
| 355 | if( !bShowLg ){ |
| 356 | blob_appendf(&html, "</tr>\n"); |
| 357 | }else if( x.zLoginGroup ){ |
| 358 | blob_appendf(&html, "<td> </td><td valign='top'>" |
| 359 | "<nobr>%h</nobr></td></tr>\n", |
| 360 | x.zLoginGroup); |
| 361 | fossil_free(x.zLoginGroup); |
| 362 | }else{ |
| 363 | blob_appendf(&html, "<td> </td><td></td></tr>\n"); |
| 364 | } |
| 365 | sqlite3_free(zUrl); |
| 366 | } |
| 367 | db_finalize(&q); |
| 368 | blob_appendf(&html,"</tbody></table>\n"); |
| 369 | } |
| 370 | if( zSkinRepo ){ |
| 371 | char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl); |
| 372 | g.zBaseURL = 0; |
| 373 | set_base_url(zNewBase); |
| 374 |
+2
-4
| --- src/report.c | ||
| +++ src/report.c | ||
| @@ -583,13 +583,10 @@ | ||
| 583 | 583 | zOwner = g.zLogin; |
| 584 | 584 | } |
| 585 | 585 | } |
| 586 | 586 | if( zOwner==0 ) zOwner = g.zLogin; |
| 587 | 587 | style_submenu_element("Cancel", "%R/reportlist"); |
| 588 | - if( rn>0 ){ | |
| 589 | - style_submenu_element("Delete", "%R/rptedit/%d?del1=1", rn); | |
| 590 | - } | |
| 591 | 588 | style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format"); |
| 592 | 589 | if( zErr ){ |
| 593 | 590 | @ <blockquote class="reportError">%h(zErr)</blockquote> |
| 594 | 591 | } |
| 595 | 592 | @ <form action="rptedit" method="post"><div> |
| @@ -897,11 +894,12 @@ | ||
| 897 | 894 | |
| 898 | 895 | /* Output the separator above each entry in a table which has multiple lines |
| 899 | 896 | ** per database entry. |
| 900 | 897 | */ |
| 901 | 898 | if( pState->iNewRow>=0 ){ |
| 902 | - @ <tr><td colspan=%d(pState->nCol)><font size=1> </font></td></tr> | |
| 899 | + @ <tr><td colspan="%d(pState->nCol)" style="padding:0px"> | |
| 900 | + @ <hr style="margin:0px"></td></tr> | |
| 903 | 901 | } |
| 904 | 902 | |
| 905 | 903 | /* Output the data for this entry from the database |
| 906 | 904 | */ |
| 907 | 905 | zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; |
| 908 | 906 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -583,13 +583,10 @@ | |
| 583 | zOwner = g.zLogin; |
| 584 | } |
| 585 | } |
| 586 | if( zOwner==0 ) zOwner = g.zLogin; |
| 587 | style_submenu_element("Cancel", "%R/reportlist"); |
| 588 | if( rn>0 ){ |
| 589 | style_submenu_element("Delete", "%R/rptedit/%d?del1=1", rn); |
| 590 | } |
| 591 | style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format"); |
| 592 | if( zErr ){ |
| 593 | @ <blockquote class="reportError">%h(zErr)</blockquote> |
| 594 | } |
| 595 | @ <form action="rptedit" method="post"><div> |
| @@ -897,11 +894,12 @@ | |
| 897 | |
| 898 | /* Output the separator above each entry in a table which has multiple lines |
| 899 | ** per database entry. |
| 900 | */ |
| 901 | if( pState->iNewRow>=0 ){ |
| 902 | @ <tr><td colspan=%d(pState->nCol)><font size=1> </font></td></tr> |
| 903 | } |
| 904 | |
| 905 | /* Output the data for this entry from the database |
| 906 | */ |
| 907 | zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; |
| 908 |
| --- src/report.c | |
| +++ src/report.c | |
| @@ -583,13 +583,10 @@ | |
| 583 | zOwner = g.zLogin; |
| 584 | } |
| 585 | } |
| 586 | if( zOwner==0 ) zOwner = g.zLogin; |
| 587 | style_submenu_element("Cancel", "%R/reportlist"); |
| 588 | style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format"); |
| 589 | if( zErr ){ |
| 590 | @ <blockquote class="reportError">%h(zErr)</blockquote> |
| 591 | } |
| 592 | @ <form action="rptedit" method="post"><div> |
| @@ -897,11 +894,12 @@ | |
| 894 | |
| 895 | /* Output the separator above each entry in a table which has multiple lines |
| 896 | ** per database entry. |
| 897 | */ |
| 898 | if( pState->iNewRow>=0 ){ |
| 899 | @ <tr><td colspan="%d(pState->nCol)" style="padding:0px"> |
| 900 | @ <hr style="margin:0px"></td></tr> |
| 901 | } |
| 902 | |
| 903 | /* Output the data for this entry from the database |
| 904 | */ |
| 905 | zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; |
| 906 |
+50
-11
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -100,10 +100,11 @@ | ||
| 100 | 100 | const char *zReadCap; /* Capabilities of user group "reader" */ |
| 101 | 101 | const char *zPubPages; /* GLOB pattern for public pages */ |
| 102 | 102 | const char *zSelfCap; /* Capabilities of self-registered users */ |
| 103 | 103 | int hasSelfReg = 0; /* True if able to self-register */ |
| 104 | 104 | const char *zPublicUrl; /* Canonical access URL */ |
| 105 | + const char *zVulnReport; /* The vuln-report setting */ | |
| 105 | 106 | Blob cmd; |
| 106 | 107 | char *z; |
| 107 | 108 | int n, i; |
| 108 | 109 | CapabilityString *pCap; |
| 109 | 110 | char **azCSP; /* Parsed content security policy */ |
| @@ -362,10 +363,22 @@ | ||
| 362 | 363 | @ <li><p><b>WARNING:</b> |
| 363 | 364 | @ The "strict-manifest-syntax" flag is off. This is a security |
| 364 | 365 | @ risk. Turn this setting on (its default) to protect the users |
| 365 | 366 | @ of this repository. |
| 366 | 367 | } |
| 368 | + | |
| 369 | + zVulnReport = db_get("vuln-report","log"); | |
| 370 | + if( fossil_strcmp(zVulnReport,"block")!=0 | |
| 371 | + && fossil_strcmp(zVulnReport,"fatal")!=0 | |
| 372 | + ){ | |
| 373 | + @ <li><p><b>WARNING:</b> | |
| 374 | + @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a> | |
| 375 | + @ has a value of "%h(zVulnReport)". This disables defenses against | |
| 376 | + @ XSS or SQL-injection vulnerabilities caused by coding errors in | |
| 377 | + @ custom TH1 scripts. For the best security, change | |
| 378 | + @ the value of the vuln-report setting to "block" or "fatal". | |
| 379 | + } | |
| 367 | 380 | |
| 368 | 381 | /* Obsolete: */ |
| 369 | 382 | if( hasAnyCap(zAnonCap, "d") || |
| 370 | 383 | hasAnyCap(zDevCap, "d") || |
| 371 | 384 | hasAnyCap(zReadCap, "d") ){ |
| @@ -810,35 +823,39 @@ | ||
| 810 | 823 | ** WEBPAGE: errorlog |
| 811 | 824 | ** |
| 812 | 825 | ** Show the content of the error log. Only the administrator can view |
| 813 | 826 | ** this page. |
| 814 | 827 | ** |
| 815 | -** y=0x01 Show only hack attempts | |
| 816 | -** y=0x02 Show only panics and assertion faults | |
| 817 | -** y=0x04 Show hung backoffice processes | |
| 818 | -** y=0x08 Show POST requests from a different origin | |
| 819 | -** y=0x10 Show SQLITE_AUTH and similar | |
| 820 | -** y=0x40 Show other uncategorized messages | |
| 828 | +** y=0x001 Show only hack attempts | |
| 829 | +** y=0x002 Show only panics and assertion faults | |
| 830 | +** y=0x004 Show hung backoffice processes | |
| 831 | +** y=0x008 Show POST requests from a different origin | |
| 832 | +** y=0x010 Show SQLITE_AUTH and similar | |
| 833 | +** y=0x020 Show SMTP error reports | |
| 834 | +** y=0x040 Show TH1 vulnerability reports | |
| 835 | +** y=0x800 Show other uncategorized messages | |
| 821 | 836 | ** |
| 822 | 837 | ** If y is omitted or is zero, a count of the various message types is |
| 823 | 838 | ** shown. |
| 824 | 839 | */ |
| 825 | 840 | void errorlog_page(void){ |
| 826 | 841 | i64 szFile; |
| 827 | 842 | FILE *in; |
| 828 | 843 | char *zLog; |
| 829 | 844 | const char *zType = P("y"); |
| 830 | - static const int eAllTypes = 0x5f; | |
| 845 | + static const int eAllTypes = 0x87f; | |
| 831 | 846 | long eType = 0; |
| 832 | 847 | int bOutput = 0; |
| 833 | 848 | int prevWasTime = 0; |
| 834 | 849 | int nHack = 0; |
| 835 | 850 | int nPanic = 0; |
| 836 | 851 | int nOther = 0; |
| 837 | 852 | int nHang = 0; |
| 838 | 853 | int nXPost = 0; |
| 839 | 854 | int nAuth = 0; |
| 855 | + int nSmtp = 0; | |
| 856 | + int nVuln = 0; | |
| 840 | 857 | char z[10000]; |
| 841 | 858 | char zTime[10000]; |
| 842 | 859 | |
| 843 | 860 | login_check_credentials(); |
| 844 | 861 | if( !g.perm.Admin ){ |
| @@ -911,11 +928,17 @@ | ||
| 911 | 928 | @ <li>POST requests from different origin |
| 912 | 929 | } |
| 913 | 930 | if( eType & 0x10 ){ |
| 914 | 931 | @ <li>SQLITE_AUTH and similar errors |
| 915 | 932 | } |
| 933 | + if( eType & 0x20 ){ | |
| 934 | + @ <li>SMTP malfunctions | |
| 935 | + } | |
| 916 | 936 | if( eType & 0x40 ){ |
| 937 | + @ <li>TH1 vulnerabilities | |
| 938 | + } | |
| 939 | + if( eType & 0x800 ){ | |
| 917 | 940 | @ <li>Other uncategorized messages |
| 918 | 941 | } |
| 919 | 942 | @ </ul> |
| 920 | 943 | } |
| 921 | 944 | @ <hr> |
| @@ -929,10 +952,14 @@ | ||
| 929 | 952 | nHack++; |
| 930 | 953 | }else |
| 931 | 954 | if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){ |
| 932 | 955 | bOutput = (eType & 0x02)!=0; |
| 933 | 956 | nPanic++; |
| 957 | + }else | |
| 958 | + if( strncmp(z,"SMTP:", 5)==0 ){ | |
| 959 | + bOutput = (eType & 0x20)!=0; | |
| 960 | + nSmtp++; | |
| 934 | 961 | }else |
| 935 | 962 | if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){ |
| 936 | 963 | bOutput = (eType & 0x04)!=0; |
| 937 | 964 | nHang++; |
| 938 | 965 | }else |
| @@ -944,12 +971,16 @@ | ||
| 944 | 971 | || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 |
| 945 | 972 | ){ |
| 946 | 973 | bOutput = (eType & 0x10)!=0; |
| 947 | 974 | nAuth++; |
| 948 | 975 | }else |
| 949 | - { | |
| 976 | + if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){ | |
| 950 | 977 | bOutput = (eType & 0x40)!=0; |
| 978 | + nVuln++; | |
| 979 | + }else | |
| 980 | + { | |
| 981 | + bOutput = (eType & 0x800)!=0; | |
| 951 | 982 | nOther++; |
| 952 | 983 | } |
| 953 | 984 | if( bOutput ){ |
| 954 | 985 | @ %h(zTime)\ |
| 955 | 986 | } |
| @@ -969,17 +1000,21 @@ | ||
| 969 | 1000 | fclose(in); |
| 970 | 1001 | if( eType ){ |
| 971 | 1002 | @ </pre> |
| 972 | 1003 | } |
| 973 | 1004 | if( eType==0 ){ |
| 974 | - int nNonHack = nPanic + nHang + nAuth + nOther; | |
| 1005 | + int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther; | |
| 975 | 1006 | int nTotal = nNonHack + nHack + nXPost; |
| 976 | 1007 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 977 | 1008 | if( nPanic>0 ){ |
| 978 | 1009 | @ <tr><td align="right">%d(nPanic)</td> |
| 979 | 1010 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| 980 | 1011 | } |
| 1012 | + if( nVuln>0 ){ | |
| 1013 | + @ <tr><td align="right">%d(nVuln)</td> | |
| 1014 | + @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td> | |
| 1015 | + } | |
| 981 | 1016 | if( nHack>0 ){ |
| 982 | 1017 | @ <tr><td align="right">%d(nHack)</td> |
| 983 | 1018 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 984 | 1019 | } |
| 985 | 1020 | if( nHang>0 ){ |
| @@ -992,19 +1027,23 @@ | ||
| 992 | 1027 | } |
| 993 | 1028 | if( nAuth>0 ){ |
| 994 | 1029 | @ <tr><td align="right">%d(nAuth)</td> |
| 995 | 1030 | @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td> |
| 996 | 1031 | } |
| 1032 | + if( nSmtp>0 ){ | |
| 1033 | + @ <tr><td align="right">%d(nSmtp)</td> | |
| 1034 | + @ <td><a href="./errorlog?y=32">SMTP faults</a></td> | |
| 1035 | + } | |
| 997 | 1036 | if( nOther>0 ){ |
| 998 | 1037 | @ <tr><td align="right">%d(nOther)</td> |
| 999 | - @ <td><a href="./errorlog?y=64">Other</a></td> | |
| 1038 | + @ <td><a href="./errorlog?y=2048">Other</a></td> | |
| 1000 | 1039 | } |
| 1001 | 1040 | @ <tr><td align="right">%d(nTotal)</td> |
| 1002 | 1041 | if( nTotal>0 ){ |
| 1003 | - @ <td><a href="./errorlog?y=255">All Messages</a></td> | |
| 1042 | + @ <td><a href="./errorlog?y=4095">All Messages</a></td> | |
| 1004 | 1043 | }else{ |
| 1005 | 1044 | @ <td>All Messages</td> |
| 1006 | 1045 | } |
| 1007 | 1046 | @ </table> |
| 1008 | 1047 | } |
| 1009 | 1048 | style_finish_page(); |
| 1010 | 1049 | } |
| 1011 | 1050 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -100,10 +100,11 @@ | |
| 100 | const char *zReadCap; /* Capabilities of user group "reader" */ |
| 101 | const char *zPubPages; /* GLOB pattern for public pages */ |
| 102 | const char *zSelfCap; /* Capabilities of self-registered users */ |
| 103 | int hasSelfReg = 0; /* True if able to self-register */ |
| 104 | const char *zPublicUrl; /* Canonical access URL */ |
| 105 | Blob cmd; |
| 106 | char *z; |
| 107 | int n, i; |
| 108 | CapabilityString *pCap; |
| 109 | char **azCSP; /* Parsed content security policy */ |
| @@ -362,10 +363,22 @@ | |
| 362 | @ <li><p><b>WARNING:</b> |
| 363 | @ The "strict-manifest-syntax" flag is off. This is a security |
| 364 | @ risk. Turn this setting on (its default) to protect the users |
| 365 | @ of this repository. |
| 366 | } |
| 367 | |
| 368 | /* Obsolete: */ |
| 369 | if( hasAnyCap(zAnonCap, "d") || |
| 370 | hasAnyCap(zDevCap, "d") || |
| 371 | hasAnyCap(zReadCap, "d") ){ |
| @@ -810,35 +823,39 @@ | |
| 810 | ** WEBPAGE: errorlog |
| 811 | ** |
| 812 | ** Show the content of the error log. Only the administrator can view |
| 813 | ** this page. |
| 814 | ** |
| 815 | ** y=0x01 Show only hack attempts |
| 816 | ** y=0x02 Show only panics and assertion faults |
| 817 | ** y=0x04 Show hung backoffice processes |
| 818 | ** y=0x08 Show POST requests from a different origin |
| 819 | ** y=0x10 Show SQLITE_AUTH and similar |
| 820 | ** y=0x40 Show other uncategorized messages |
| 821 | ** |
| 822 | ** If y is omitted or is zero, a count of the various message types is |
| 823 | ** shown. |
| 824 | */ |
| 825 | void errorlog_page(void){ |
| 826 | i64 szFile; |
| 827 | FILE *in; |
| 828 | char *zLog; |
| 829 | const char *zType = P("y"); |
| 830 | static const int eAllTypes = 0x5f; |
| 831 | long eType = 0; |
| 832 | int bOutput = 0; |
| 833 | int prevWasTime = 0; |
| 834 | int nHack = 0; |
| 835 | int nPanic = 0; |
| 836 | int nOther = 0; |
| 837 | int nHang = 0; |
| 838 | int nXPost = 0; |
| 839 | int nAuth = 0; |
| 840 | char z[10000]; |
| 841 | char zTime[10000]; |
| 842 | |
| 843 | login_check_credentials(); |
| 844 | if( !g.perm.Admin ){ |
| @@ -911,11 +928,17 @@ | |
| 911 | @ <li>POST requests from different origin |
| 912 | } |
| 913 | if( eType & 0x10 ){ |
| 914 | @ <li>SQLITE_AUTH and similar errors |
| 915 | } |
| 916 | if( eType & 0x40 ){ |
| 917 | @ <li>Other uncategorized messages |
| 918 | } |
| 919 | @ </ul> |
| 920 | } |
| 921 | @ <hr> |
| @@ -929,10 +952,14 @@ | |
| 929 | nHack++; |
| 930 | }else |
| 931 | if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){ |
| 932 | bOutput = (eType & 0x02)!=0; |
| 933 | nPanic++; |
| 934 | }else |
| 935 | if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){ |
| 936 | bOutput = (eType & 0x04)!=0; |
| 937 | nHang++; |
| 938 | }else |
| @@ -944,12 +971,16 @@ | |
| 944 | || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 |
| 945 | ){ |
| 946 | bOutput = (eType & 0x10)!=0; |
| 947 | nAuth++; |
| 948 | }else |
| 949 | { |
| 950 | bOutput = (eType & 0x40)!=0; |
| 951 | nOther++; |
| 952 | } |
| 953 | if( bOutput ){ |
| 954 | @ %h(zTime)\ |
| 955 | } |
| @@ -969,17 +1000,21 @@ | |
| 969 | fclose(in); |
| 970 | if( eType ){ |
| 971 | @ </pre> |
| 972 | } |
| 973 | if( eType==0 ){ |
| 974 | int nNonHack = nPanic + nHang + nAuth + nOther; |
| 975 | int nTotal = nNonHack + nHack + nXPost; |
| 976 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 977 | if( nPanic>0 ){ |
| 978 | @ <tr><td align="right">%d(nPanic)</td> |
| 979 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| 980 | } |
| 981 | if( nHack>0 ){ |
| 982 | @ <tr><td align="right">%d(nHack)</td> |
| 983 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 984 | } |
| 985 | if( nHang>0 ){ |
| @@ -992,19 +1027,23 @@ | |
| 992 | } |
| 993 | if( nAuth>0 ){ |
| 994 | @ <tr><td align="right">%d(nAuth)</td> |
| 995 | @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td> |
| 996 | } |
| 997 | if( nOther>0 ){ |
| 998 | @ <tr><td align="right">%d(nOther)</td> |
| 999 | @ <td><a href="./errorlog?y=64">Other</a></td> |
| 1000 | } |
| 1001 | @ <tr><td align="right">%d(nTotal)</td> |
| 1002 | if( nTotal>0 ){ |
| 1003 | @ <td><a href="./errorlog?y=255">All Messages</a></td> |
| 1004 | }else{ |
| 1005 | @ <td>All Messages</td> |
| 1006 | } |
| 1007 | @ </table> |
| 1008 | } |
| 1009 | style_finish_page(); |
| 1010 | } |
| 1011 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -100,10 +100,11 @@ | |
| 100 | const char *zReadCap; /* Capabilities of user group "reader" */ |
| 101 | const char *zPubPages; /* GLOB pattern for public pages */ |
| 102 | const char *zSelfCap; /* Capabilities of self-registered users */ |
| 103 | int hasSelfReg = 0; /* True if able to self-register */ |
| 104 | const char *zPublicUrl; /* Canonical access URL */ |
| 105 | const char *zVulnReport; /* The vuln-report setting */ |
| 106 | Blob cmd; |
| 107 | char *z; |
| 108 | int n, i; |
| 109 | CapabilityString *pCap; |
| 110 | char **azCSP; /* Parsed content security policy */ |
| @@ -362,10 +363,22 @@ | |
| 363 | @ <li><p><b>WARNING:</b> |
| 364 | @ The "strict-manifest-syntax" flag is off. This is a security |
| 365 | @ risk. Turn this setting on (its default) to protect the users |
| 366 | @ of this repository. |
| 367 | } |
| 368 | |
| 369 | zVulnReport = db_get("vuln-report","log"); |
| 370 | if( fossil_strcmp(zVulnReport,"block")!=0 |
| 371 | && fossil_strcmp(zVulnReport,"fatal")!=0 |
| 372 | ){ |
| 373 | @ <li><p><b>WARNING:</b> |
| 374 | @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a> |
| 375 | @ has a value of "%h(zVulnReport)". This disables defenses against |
| 376 | @ XSS or SQL-injection vulnerabilities caused by coding errors in |
| 377 | @ custom TH1 scripts. For the best security, change |
| 378 | @ the value of the vuln-report setting to "block" or "fatal". |
| 379 | } |
| 380 | |
| 381 | /* Obsolete: */ |
| 382 | if( hasAnyCap(zAnonCap, "d") || |
| 383 | hasAnyCap(zDevCap, "d") || |
| 384 | hasAnyCap(zReadCap, "d") ){ |
| @@ -810,35 +823,39 @@ | |
| 823 | ** WEBPAGE: errorlog |
| 824 | ** |
| 825 | ** Show the content of the error log. Only the administrator can view |
| 826 | ** this page. |
| 827 | ** |
| 828 | ** y=0x001 Show only hack attempts |
| 829 | ** y=0x002 Show only panics and assertion faults |
| 830 | ** y=0x004 Show hung backoffice processes |
| 831 | ** y=0x008 Show POST requests from a different origin |
| 832 | ** y=0x010 Show SQLITE_AUTH and similar |
| 833 | ** y=0x020 Show SMTP error reports |
| 834 | ** y=0x040 Show TH1 vulnerability reports |
| 835 | ** y=0x800 Show other uncategorized messages |
| 836 | ** |
| 837 | ** If y is omitted or is zero, a count of the various message types is |
| 838 | ** shown. |
| 839 | */ |
| 840 | void errorlog_page(void){ |
| 841 | i64 szFile; |
| 842 | FILE *in; |
| 843 | char *zLog; |
| 844 | const char *zType = P("y"); |
| 845 | static const int eAllTypes = 0x87f; |
| 846 | long eType = 0; |
| 847 | int bOutput = 0; |
| 848 | int prevWasTime = 0; |
| 849 | int nHack = 0; |
| 850 | int nPanic = 0; |
| 851 | int nOther = 0; |
| 852 | int nHang = 0; |
| 853 | int nXPost = 0; |
| 854 | int nAuth = 0; |
| 855 | int nSmtp = 0; |
| 856 | int nVuln = 0; |
| 857 | char z[10000]; |
| 858 | char zTime[10000]; |
| 859 | |
| 860 | login_check_credentials(); |
| 861 | if( !g.perm.Admin ){ |
| @@ -911,11 +928,17 @@ | |
| 928 | @ <li>POST requests from different origin |
| 929 | } |
| 930 | if( eType & 0x10 ){ |
| 931 | @ <li>SQLITE_AUTH and similar errors |
| 932 | } |
| 933 | if( eType & 0x20 ){ |
| 934 | @ <li>SMTP malfunctions |
| 935 | } |
| 936 | if( eType & 0x40 ){ |
| 937 | @ <li>TH1 vulnerabilities |
| 938 | } |
| 939 | if( eType & 0x800 ){ |
| 940 | @ <li>Other uncategorized messages |
| 941 | } |
| 942 | @ </ul> |
| 943 | } |
| 944 | @ <hr> |
| @@ -929,10 +952,14 @@ | |
| 952 | nHack++; |
| 953 | }else |
| 954 | if( (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0) ){ |
| 955 | bOutput = (eType & 0x02)!=0; |
| 956 | nPanic++; |
| 957 | }else |
| 958 | if( strncmp(z,"SMTP:", 5)==0 ){ |
| 959 | bOutput = (eType & 0x20)!=0; |
| 960 | nSmtp++; |
| 961 | }else |
| 962 | if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){ |
| 963 | bOutput = (eType & 0x04)!=0; |
| 964 | nHang++; |
| 965 | }else |
| @@ -944,12 +971,16 @@ | |
| 971 | || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 |
| 972 | ){ |
| 973 | bOutput = (eType & 0x10)!=0; |
| 974 | nAuth++; |
| 975 | }else |
| 976 | if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){ |
| 977 | bOutput = (eType & 0x40)!=0; |
| 978 | nVuln++; |
| 979 | }else |
| 980 | { |
| 981 | bOutput = (eType & 0x800)!=0; |
| 982 | nOther++; |
| 983 | } |
| 984 | if( bOutput ){ |
| 985 | @ %h(zTime)\ |
| 986 | } |
| @@ -969,17 +1000,21 @@ | |
| 1000 | fclose(in); |
| 1001 | if( eType ){ |
| 1002 | @ </pre> |
| 1003 | } |
| 1004 | if( eType==0 ){ |
| 1005 | int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther; |
| 1006 | int nTotal = nNonHack + nHack + nXPost; |
| 1007 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 1008 | if( nPanic>0 ){ |
| 1009 | @ <tr><td align="right">%d(nPanic)</td> |
| 1010 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| 1011 | } |
| 1012 | if( nVuln>0 ){ |
| 1013 | @ <tr><td align="right">%d(nVuln)</td> |
| 1014 | @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td> |
| 1015 | } |
| 1016 | if( nHack>0 ){ |
| 1017 | @ <tr><td align="right">%d(nHack)</td> |
| 1018 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 1019 | } |
| 1020 | if( nHang>0 ){ |
| @@ -992,19 +1027,23 @@ | |
| 1027 | } |
| 1028 | if( nAuth>0 ){ |
| 1029 | @ <tr><td align="right">%d(nAuth)</td> |
| 1030 | @ <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td> |
| 1031 | } |
| 1032 | if( nSmtp>0 ){ |
| 1033 | @ <tr><td align="right">%d(nSmtp)</td> |
| 1034 | @ <td><a href="./errorlog?y=32">SMTP faults</a></td> |
| 1035 | } |
| 1036 | if( nOther>0 ){ |
| 1037 | @ <tr><td align="right">%d(nOther)</td> |
| 1038 | @ <td><a href="./errorlog?y=2048">Other</a></td> |
| 1039 | } |
| 1040 | @ <tr><td align="right">%d(nTotal)</td> |
| 1041 | if( nTotal>0 ){ |
| 1042 | @ <td><a href="./errorlog?y=4095">All Messages</a></td> |
| 1043 | }else{ |
| 1044 | @ <td>All Messages</td> |
| 1045 | } |
| 1046 | @ </table> |
| 1047 | } |
| 1048 | style_finish_page(); |
| 1049 | } |
| 1050 |
+1
-1
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -216,11 +216,11 @@ | ||
| 216 | 216 | setup_menu_entry("Admin Log", "admin_log", |
| 217 | 217 | "The admin log records configuration changes to the repository\n" |
| 218 | 218 | "in the \"admin_log\" table.\n" |
| 219 | 219 | ); |
| 220 | 220 | } |
| 221 | - setup_menu_entry("Artifact Log", "rcvfromlist", | |
| 221 | + setup_menu_entry("Xfer Log", "rcvfromlist", | |
| 222 | 222 | "The artifact log records when new content is added in the\n" |
| 223 | 223 | "\"rcvfrom\" table.\n" |
| 224 | 224 | ); |
| 225 | 225 | if( db_get_boolean("access-log",1) ){ |
| 226 | 226 | setup_menu_entry("User Log", "user_log", |
| 227 | 227 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -216,11 +216,11 @@ | |
| 216 | setup_menu_entry("Admin Log", "admin_log", |
| 217 | "The admin log records configuration changes to the repository\n" |
| 218 | "in the \"admin_log\" table.\n" |
| 219 | ); |
| 220 | } |
| 221 | setup_menu_entry("Artifact Log", "rcvfromlist", |
| 222 | "The artifact log records when new content is added in the\n" |
| 223 | "\"rcvfrom\" table.\n" |
| 224 | ); |
| 225 | if( db_get_boolean("access-log",1) ){ |
| 226 | setup_menu_entry("User Log", "user_log", |
| 227 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -216,11 +216,11 @@ | |
| 216 | setup_menu_entry("Admin Log", "admin_log", |
| 217 | "The admin log records configuration changes to the repository\n" |
| 218 | "in the \"admin_log\" table.\n" |
| 219 | ); |
| 220 | } |
| 221 | setup_menu_entry("Xfer Log", "rcvfromlist", |
| 222 | "The artifact log records when new content is added in the\n" |
| 223 | "\"rcvfrom\" table.\n" |
| 224 | ); |
| 225 | if( db_get_boolean("access-log",1) ){ |
| 226 | setup_menu_entry("User Log", "user_log", |
| 227 |
+23
-19
| --- src/setupuser.c | ||
| +++ src/setupuser.c | ||
| @@ -41,21 +41,22 @@ | ||
| 41 | 41 | Stmt s; |
| 42 | 42 | double rNow; |
| 43 | 43 | const char *zWith = P("with"); |
| 44 | 44 | int bUnusedOnly = P("unused")!=0; |
| 45 | 45 | int bUbg = P("ubg")!=0; |
| 46 | + int bHaveAlerts; | |
| 46 | 47 | |
| 47 | 48 | login_check_credentials(); |
| 48 | 49 | if( !g.perm.Admin ){ |
| 49 | 50 | login_needed(0); |
| 50 | 51 | return; |
| 51 | 52 | } |
| 52 | - | |
| 53 | + bHaveAlerts = alert_tables_exist(); | |
| 53 | 54 | style_submenu_element("Add", "setup_uedit"); |
| 54 | 55 | style_submenu_element("Log", "access_log"); |
| 55 | 56 | style_submenu_element("Help", "setup_ulist_notes"); |
| 56 | - if( alert_tables_exist() ){ | |
| 57 | + if( bHaveAlerts ){ | |
| 57 | 58 | style_submenu_element("Subscribers", "subscribers"); |
| 58 | 59 | } |
| 59 | 60 | style_set_current_feature("setup"); |
| 60 | 61 | style_header("User List"); |
| 61 | 62 | if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ |
| @@ -147,31 +148,34 @@ | ||
| 147 | 148 | zWith = mprintf( |
| 148 | 149 | " AND login NOT IN (" |
| 149 | 150 | "SELECT user FROM event WHERE user NOT NULL " |
| 150 | 151 | "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)" |
| 151 | 152 | " AND uid NOT IN (SELECT uid FROM rcvfrom)", |
| 152 | - alert_tables_exist() ? | |
| 153 | + bHaveAlerts ? | |
| 153 | 154 | " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":""); |
| 154 | 155 | }else if( zWith && zWith[0] ){ |
| 155 | 156 | zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); |
| 156 | 157 | }else{ |
| 157 | 158 | zWith = ""; |
| 158 | 159 | } |
| 159 | 160 | db_prepare(&s, |
| 160 | - "SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," /* 0..4 */ | |
| 161 | - " lower(login) AS sortkey, " /* 5 */ | |
| 162 | - " CASE WHEN info LIKE '%%expires 20%%'" | |
| 161 | + /*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," | |
| 162 | + /* 5 */"lower(login) AS sortkey, " | |
| 163 | + /* 6 */"CASE WHEN info LIKE '%%expires 20%%'" | |
| 163 | 164 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 164 | - " END AS exp," /* 6 */ | |
| 165 | - "atime," /* 7 */ | |
| 166 | - " subscriber.ssub, subscriber.subscriberId," /* 8, 9 */ | |
| 167 | - " user.mtime AS sorttime," /* 10 */ | |
| 168 | - " subscriber.semail" /* 11 */ | |
| 169 | - " FROM user LEFT JOIN lastAccess ON login=uname" | |
| 170 | - " LEFT JOIN subscriber ON login=suname" | |
| 171 | - " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" | |
| 172 | - " ORDER BY sorttime DESC", zWith/*safe-for-%s*/ | |
| 165 | + " END AS exp," | |
| 166 | + /* 7 */"atime," | |
| 167 | + /* 8 */"user.mtime AS sorttime," | |
| 168 | + /*9-11*/"%s" | |
| 169 | + " FROM user LEFT JOIN lastAccess ON login=uname" | |
| 170 | + " LEFT JOIN subscriber ON login=suname" | |
| 171 | + " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" | |
| 172 | + " ORDER BY sorttime DESC", | |
| 173 | + bHaveAlerts | |
| 174 | + ? "subscriber.ssub, subscriber.subscriberId, subscriber.semail" | |
| 175 | + : "null, null, null", | |
| 176 | + zWith/*safe-for-%s*/ | |
| 173 | 177 | ); |
| 174 | 178 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 175 | 179 | while( db_step(&s)==SQLITE_ROW ){ |
| 176 | 180 | int uid = db_column_int(&s, 0); |
| 177 | 181 | const char *zLogin = db_column_text(&s, 1); |
| @@ -181,12 +185,12 @@ | ||
| 181 | 185 | const char *zSortKey = db_column_text(&s,5); |
| 182 | 186 | const char *zExp = db_column_text(&s,6); |
| 183 | 187 | double rATime = db_column_double(&s,7); |
| 184 | 188 | char *zAge = 0; |
| 185 | 189 | const char *zSub; |
| 186 | - int sid = db_column_int(&s,9); | |
| 187 | - sqlite3_int64 sorttime = db_column_int64(&s, 10); | |
| 190 | + int sid = db_column_int(&s,10); | |
| 191 | + sqlite3_int64 sorttime = db_column_int64(&s, 8); | |
| 188 | 192 | if( rATime>0.0 ){ |
| 189 | 193 | zAge = human_readable_age(rNow - rATime); |
| 190 | 194 | } |
| 191 | 195 | if( bUbg ){ |
| 192 | 196 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -198,13 +202,13 @@ | ||
| 198 | 202 | @ <td>%h(zCap) |
| 199 | 203 | @ <td>%h(zInfo) |
| 200 | 204 | @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") |
| 201 | 205 | @ <td>%h(zExp?zExp:"") |
| 202 | 206 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 203 | - if( db_column_type(&s,8)==SQLITE_NULL ){ | |
| 207 | + if( db_column_type(&s,9)==SQLITE_NULL ){ | |
| 204 | 208 | @ <td> |
| 205 | - }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ | |
| 209 | + }else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){ | |
| 206 | 210 | @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a> |
| 207 | 211 | }else{ |
| 208 | 212 | const char *zEmail = db_column_text(&s, 11); |
| 209 | 213 | char * zAt = zEmail ? mprintf(" → %h", zEmail) : mprintf(""); |
| 210 | 214 | @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt) |
| 211 | 215 |
| --- src/setupuser.c | |
| +++ src/setupuser.c | |
| @@ -41,21 +41,22 @@ | |
| 41 | Stmt s; |
| 42 | double rNow; |
| 43 | const char *zWith = P("with"); |
| 44 | int bUnusedOnly = P("unused")!=0; |
| 45 | int bUbg = P("ubg")!=0; |
| 46 | |
| 47 | login_check_credentials(); |
| 48 | if( !g.perm.Admin ){ |
| 49 | login_needed(0); |
| 50 | return; |
| 51 | } |
| 52 | |
| 53 | style_submenu_element("Add", "setup_uedit"); |
| 54 | style_submenu_element("Log", "access_log"); |
| 55 | style_submenu_element("Help", "setup_ulist_notes"); |
| 56 | if( alert_tables_exist() ){ |
| 57 | style_submenu_element("Subscribers", "subscribers"); |
| 58 | } |
| 59 | style_set_current_feature("setup"); |
| 60 | style_header("User List"); |
| 61 | if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ |
| @@ -147,31 +148,34 @@ | |
| 147 | zWith = mprintf( |
| 148 | " AND login NOT IN (" |
| 149 | "SELECT user FROM event WHERE user NOT NULL " |
| 150 | "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)" |
| 151 | " AND uid NOT IN (SELECT uid FROM rcvfrom)", |
| 152 | alert_tables_exist() ? |
| 153 | " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":""); |
| 154 | }else if( zWith && zWith[0] ){ |
| 155 | zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); |
| 156 | }else{ |
| 157 | zWith = ""; |
| 158 | } |
| 159 | db_prepare(&s, |
| 160 | "SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," /* 0..4 */ |
| 161 | " lower(login) AS sortkey, " /* 5 */ |
| 162 | " CASE WHEN info LIKE '%%expires 20%%'" |
| 163 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 164 | " END AS exp," /* 6 */ |
| 165 | "atime," /* 7 */ |
| 166 | " subscriber.ssub, subscriber.subscriberId," /* 8, 9 */ |
| 167 | " user.mtime AS sorttime," /* 10 */ |
| 168 | " subscriber.semail" /* 11 */ |
| 169 | " FROM user LEFT JOIN lastAccess ON login=uname" |
| 170 | " LEFT JOIN subscriber ON login=suname" |
| 171 | " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
| 172 | " ORDER BY sorttime DESC", zWith/*safe-for-%s*/ |
| 173 | ); |
| 174 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 175 | while( db_step(&s)==SQLITE_ROW ){ |
| 176 | int uid = db_column_int(&s, 0); |
| 177 | const char *zLogin = db_column_text(&s, 1); |
| @@ -181,12 +185,12 @@ | |
| 181 | const char *zSortKey = db_column_text(&s,5); |
| 182 | const char *zExp = db_column_text(&s,6); |
| 183 | double rATime = db_column_double(&s,7); |
| 184 | char *zAge = 0; |
| 185 | const char *zSub; |
| 186 | int sid = db_column_int(&s,9); |
| 187 | sqlite3_int64 sorttime = db_column_int64(&s, 10); |
| 188 | if( rATime>0.0 ){ |
| 189 | zAge = human_readable_age(rNow - rATime); |
| 190 | } |
| 191 | if( bUbg ){ |
| 192 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -198,13 +202,13 @@ | |
| 198 | @ <td>%h(zCap) |
| 199 | @ <td>%h(zInfo) |
| 200 | @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") |
| 201 | @ <td>%h(zExp?zExp:"") |
| 202 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 203 | if( db_column_type(&s,8)==SQLITE_NULL ){ |
| 204 | @ <td> |
| 205 | }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ |
| 206 | @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a> |
| 207 | }else{ |
| 208 | const char *zEmail = db_column_text(&s, 11); |
| 209 | char * zAt = zEmail ? mprintf(" → %h", zEmail) : mprintf(""); |
| 210 | @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt) |
| 211 |
| --- src/setupuser.c | |
| +++ src/setupuser.c | |
| @@ -41,21 +41,22 @@ | |
| 41 | Stmt s; |
| 42 | double rNow; |
| 43 | const char *zWith = P("with"); |
| 44 | int bUnusedOnly = P("unused")!=0; |
| 45 | int bUbg = P("ubg")!=0; |
| 46 | int bHaveAlerts; |
| 47 | |
| 48 | login_check_credentials(); |
| 49 | if( !g.perm.Admin ){ |
| 50 | login_needed(0); |
| 51 | return; |
| 52 | } |
| 53 | bHaveAlerts = alert_tables_exist(); |
| 54 | style_submenu_element("Add", "setup_uedit"); |
| 55 | style_submenu_element("Log", "access_log"); |
| 56 | style_submenu_element("Help", "setup_ulist_notes"); |
| 57 | if( bHaveAlerts ){ |
| 58 | style_submenu_element("Subscribers", "subscribers"); |
| 59 | } |
| 60 | style_set_current_feature("setup"); |
| 61 | style_header("User List"); |
| 62 | if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ |
| @@ -147,31 +148,34 @@ | |
| 148 | zWith = mprintf( |
| 149 | " AND login NOT IN (" |
| 150 | "SELECT user FROM event WHERE user NOT NULL " |
| 151 | "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)" |
| 152 | " AND uid NOT IN (SELECT uid FROM rcvfrom)", |
| 153 | bHaveAlerts ? |
| 154 | " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":""); |
| 155 | }else if( zWith && zWith[0] ){ |
| 156 | zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); |
| 157 | }else{ |
| 158 | zWith = ""; |
| 159 | } |
| 160 | db_prepare(&s, |
| 161 | /*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," |
| 162 | /* 5 */"lower(login) AS sortkey, " |
| 163 | /* 6 */"CASE WHEN info LIKE '%%expires 20%%'" |
| 164 | " THEN substr(info,instr(lower(info),'expires')+8,10)" |
| 165 | " END AS exp," |
| 166 | /* 7 */"atime," |
| 167 | /* 8 */"user.mtime AS sorttime," |
| 168 | /*9-11*/"%s" |
| 169 | " FROM user LEFT JOIN lastAccess ON login=uname" |
| 170 | " LEFT JOIN subscriber ON login=suname" |
| 171 | " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
| 172 | " ORDER BY sorttime DESC", |
| 173 | bHaveAlerts |
| 174 | ? "subscriber.ssub, subscriber.subscriberId, subscriber.semail" |
| 175 | : "null, null, null", |
| 176 | zWith/*safe-for-%s*/ |
| 177 | ); |
| 178 | rNow = db_double(0.0, "SELECT julianday('now');"); |
| 179 | while( db_step(&s)==SQLITE_ROW ){ |
| 180 | int uid = db_column_int(&s, 0); |
| 181 | const char *zLogin = db_column_text(&s, 1); |
| @@ -181,12 +185,12 @@ | |
| 185 | const char *zSortKey = db_column_text(&s,5); |
| 186 | const char *zExp = db_column_text(&s,6); |
| 187 | double rATime = db_column_double(&s,7); |
| 188 | char *zAge = 0; |
| 189 | const char *zSub; |
| 190 | int sid = db_column_int(&s,10); |
| 191 | sqlite3_int64 sorttime = db_column_int64(&s, 8); |
| 192 | if( rATime>0.0 ){ |
| 193 | zAge = human_readable_age(rNow - rATime); |
| 194 | } |
| 195 | if( bUbg ){ |
| 196 | @ <tr style='background-color: %h(user_color(zLogin));'> |
| @@ -198,13 +202,13 @@ | |
| 202 | @ <td>%h(zCap) |
| 203 | @ <td>%h(zInfo) |
| 204 | @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") |
| 205 | @ <td>%h(zExp?zExp:"") |
| 206 | @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
| 207 | if( db_column_type(&s,9)==SQLITE_NULL ){ |
| 208 | @ <td> |
| 209 | }else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){ |
| 210 | @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a> |
| 211 | }else{ |
| 212 | const char *zEmail = db_column_text(&s, 11); |
| 213 | char * zAt = zEmail ? mprintf(" → %h", zEmail) : mprintf(""); |
| 214 | @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt) |
| 215 |
+4
-3
| --- src/shun.c | ||
| +++ src/shun.c | ||
| @@ -373,11 +373,11 @@ | ||
| 373 | 373 | login_check_credentials(); |
| 374 | 374 | if( !g.perm.Admin ){ |
| 375 | 375 | login_needed(0); |
| 376 | 376 | return; |
| 377 | 377 | } |
| 378 | - style_header("Artifact Receipts"); | |
| 378 | + style_header("Xfer Log"); | |
| 379 | 379 | style_submenu_element("Log-Menu", "setup-logmenu"); |
| 380 | 380 | if( showAll ){ |
| 381 | 381 | ofst = 0; |
| 382 | 382 | }else{ |
| 383 | 383 | style_submenu_element("All", "rcvfromlist?all=1"); |
| @@ -415,12 +415,13 @@ | ||
| 415 | 415 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 416 | 416 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| 417 | 417 | showAll ? -1 : perScreen+1, ofst |
| 418 | 418 | ); |
| 419 | 419 | @ <p>Whenever new artifacts are added to the repository, either by |
| 420 | - @ push or using the web interface, an entry is made in the RCVFROM table | |
| 421 | - @ to record the source of that artifact. This log facilitates | |
| 420 | + @ push or using the web interface or by "fossil commit" or similar, | |
| 421 | + @ an entry is made in the RCVFROM table | |
| 422 | + @ to record the source of those artifacts. This log facilitates | |
| 422 | 423 | @ finding and fixing attempts to inject illicit content into the |
| 423 | 424 | @ repository.</p> |
| 424 | 425 | @ |
| 425 | 426 | @ <p>Click on the "rcvid" to show a list of specific artifacts received |
| 426 | 427 | @ by a transaction. After identifying illicit artifacts, remove them |
| 427 | 428 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -373,11 +373,11 @@ | |
| 373 | login_check_credentials(); |
| 374 | if( !g.perm.Admin ){ |
| 375 | login_needed(0); |
| 376 | return; |
| 377 | } |
| 378 | style_header("Artifact Receipts"); |
| 379 | style_submenu_element("Log-Menu", "setup-logmenu"); |
| 380 | if( showAll ){ |
| 381 | ofst = 0; |
| 382 | }else{ |
| 383 | style_submenu_element("All", "rcvfromlist?all=1"); |
| @@ -415,12 +415,13 @@ | |
| 415 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 416 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| 417 | showAll ? -1 : perScreen+1, ofst |
| 418 | ); |
| 419 | @ <p>Whenever new artifacts are added to the repository, either by |
| 420 | @ push or using the web interface, an entry is made in the RCVFROM table |
| 421 | @ to record the source of that artifact. This log facilitates |
| 422 | @ finding and fixing attempts to inject illicit content into the |
| 423 | @ repository.</p> |
| 424 | @ |
| 425 | @ <p>Click on the "rcvid" to show a list of specific artifacts received |
| 426 | @ by a transaction. After identifying illicit artifacts, remove them |
| 427 |
| --- src/shun.c | |
| +++ src/shun.c | |
| @@ -373,11 +373,11 @@ | |
| 373 | login_check_credentials(); |
| 374 | if( !g.perm.Admin ){ |
| 375 | login_needed(0); |
| 376 | return; |
| 377 | } |
| 378 | style_header("Xfer Log"); |
| 379 | style_submenu_element("Log-Menu", "setup-logmenu"); |
| 380 | if( showAll ){ |
| 381 | ofst = 0; |
| 382 | }else{ |
| 383 | style_submenu_element("All", "rcvfromlist?all=1"); |
| @@ -415,12 +415,13 @@ | |
| 415 | " FROM rcvfrom LEFT JOIN user USING(uid)" |
| 416 | " ORDER BY rcvid DESC LIMIT %d OFFSET %d", |
| 417 | showAll ? -1 : perScreen+1, ofst |
| 418 | ); |
| 419 | @ <p>Whenever new artifacts are added to the repository, either by |
| 420 | @ push or using the web interface or by "fossil commit" or similar, |
| 421 | @ an entry is made in the RCVFROM table |
| 422 | @ to record the source of those artifacts. This log facilitates |
| 423 | @ finding and fixing attempts to inject illicit content into the |
| 424 | @ repository.</p> |
| 425 | @ |
| 426 | @ <p>Click on the "rcvid" to show a list of specific artifacts received |
| 427 | @ by a transaction. After identifying illicit artifacts, remove them |
| 428 |
+70
-29
| --- src/smtp.c | ||
| +++ src/smtp.c | ||
| @@ -156,13 +156,15 @@ | ||
| 156 | 156 | const char *zDest; /* Domain that will receive the email */ |
| 157 | 157 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 158 | 158 | u32 smtpFlags; /* Flags changing the operation */ |
| 159 | 159 | FILE *logFile; /* Write session transcript to this log file */ |
| 160 | 160 | Blob *pTranscript; /* Record session transcript here */ |
| 161 | - int atEof; /* True after connection closes */ | |
| 161 | + int bOpen; /* True if connection is Open */ | |
| 162 | + int bFatal; /* Error is fatal. Do not retry */ | |
| 162 | 163 | char *zErr; /* Error message */ |
| 163 | 164 | Blob inbuf; /* Input buffer */ |
| 165 | + UrlData url; /* Address of the server */ | |
| 164 | 166 | }; |
| 165 | 167 | |
| 166 | 168 | /* Allowed values for SmtpSession.smtpFlags */ |
| 167 | 169 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 168 | 170 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| @@ -180,10 +182,32 @@ | ||
| 180 | 182 | blob_reset(&pSession->inbuf); |
| 181 | 183 | fossil_free(pSession->zHostname); |
| 182 | 184 | fossil_free(pSession->zErr); |
| 183 | 185 | fossil_free(pSession); |
| 184 | 186 | } |
| 187 | + | |
| 188 | +/* | |
| 189 | +** Set an error message on the SmtpSession | |
| 190 | +*/ | |
| 191 | +static void smtp_set_error( | |
| 192 | + SmtpSession *p, /* The SMTP context */ | |
| 193 | + int bFatal, /* Fatal error. Reset and retry is pointless */ | |
| 194 | + const char *zFormat, /* Error message. */ | |
| 195 | + ... | |
| 196 | +){ | |
| 197 | + if( bFatal ) p->bFatal = 1; | |
| 198 | + if( p->zErr==0 ){ | |
| 199 | + va_list ap; | |
| 200 | + va_start(ap, zFormat); | |
| 201 | + p->zErr = vmprintf(zFormat, ap); | |
| 202 | + va_end(ap); | |
| 203 | + } | |
| 204 | + if( p->bOpen ){ | |
| 205 | + socket_close(); | |
| 206 | + p->bOpen = 0; | |
| 207 | + } | |
| 208 | +} | |
| 185 | 209 | |
| 186 | 210 | /* |
| 187 | 211 | ** Allocate a new SmtpSession object. |
| 188 | 212 | ** |
| 189 | 213 | ** Both zFrom and zDest must be specified. smtpFlags may not contain |
| @@ -197,46 +221,39 @@ | ||
| 197 | 221 | const char *zDest, /* Domain of the server */ |
| 198 | 222 | u32 smtpFlags, /* Flags */ |
| 199 | 223 | int iPort /* TCP port if the SMTP_PORT flags is present */ |
| 200 | 224 | ){ |
| 201 | 225 | SmtpSession *p; |
| 202 | - UrlData url; | |
| 203 | 226 | |
| 204 | 227 | p = fossil_malloc( sizeof(*p) ); |
| 205 | 228 | memset(p, 0, sizeof(*p)); |
| 206 | 229 | p->zFrom = zFrom; |
| 207 | 230 | p->zDest = zDest; |
| 208 | 231 | p->smtpFlags = smtpFlags; |
| 209 | - memset(&url, 0, sizeof(url)); | |
| 210 | - url.port = 25; | |
| 232 | + p->url.port = 25; | |
| 211 | 233 | blob_init(&p->inbuf, 0, 0); |
| 212 | 234 | if( smtpFlags & SMTP_PORT ){ |
| 213 | - url.port = iPort; | |
| 235 | + p->url.port = iPort; | |
| 214 | 236 | } |
| 215 | 237 | if( (smtpFlags & SMTP_DIRECT)!=0 ){ |
| 216 | 238 | int i; |
| 217 | 239 | p->zHostname = fossil_strdup(zDest); |
| 218 | 240 | for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){} |
| 219 | 241 | if( p->zHostname[i]==':' ){ |
| 220 | 242 | p->zHostname[i] = 0; |
| 221 | - url.port = atoi(&p->zHostname[i+1]); | |
| 243 | + p->url.port = atoi(&p->zHostname[i+1]); | |
| 222 | 244 | } |
| 223 | 245 | }else{ |
| 224 | 246 | p->zHostname = smtp_mx_host(zDest); |
| 225 | 247 | } |
| 226 | 248 | if( p->zHostname==0 ){ |
| 227 | - p->atEof = 1; | |
| 228 | - p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest); | |
| 249 | + smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest); | |
| 229 | 250 | return p; |
| 230 | 251 | } |
| 231 | - url.name = p->zHostname; | |
| 252 | + p->url.name = p->zHostname; | |
| 232 | 253 | socket_global_init(); |
| 233 | - if( socket_open(&url) ){ | |
| 234 | - p->atEof = 1; | |
| 235 | - p->zErr = socket_errmsg(); | |
| 236 | - socket_close(); | |
| 237 | - } | |
| 254 | + p->bOpen = 0; | |
| 238 | 255 | return p; |
| 239 | 256 | } |
| 240 | 257 | |
| 241 | 258 | /* |
| 242 | 259 | ** Configure debugging options on SmtpSession. Add all bits in |
| @@ -261,11 +278,11 @@ | ||
| 261 | 278 | static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){ |
| 262 | 279 | Blob b = empty_blob; |
| 263 | 280 | va_list ap; |
| 264 | 281 | char *z; |
| 265 | 282 | int n; |
| 266 | - if( p->atEof ) return; | |
| 283 | + if( !p->bOpen ) return; | |
| 267 | 284 | va_start(ap, zFormat); |
| 268 | 285 | blob_vappendf(&b, zFormat, ap); |
| 269 | 286 | va_end(ap); |
| 270 | 287 | z = blob_buffer(&b); |
| 271 | 288 | n = blob_size(&b); |
| @@ -297,11 +314,11 @@ | ||
| 297 | 314 | char *z = blob_buffer(&p->inbuf); |
| 298 | 315 | int i = blob_tell(&p->inbuf); |
| 299 | 316 | int nDelay = 0; |
| 300 | 317 | if( i<n && z[n-1]=='\n' ){ |
| 301 | 318 | blob_line(&p->inbuf, in); |
| 302 | - }else if( p->atEof ){ | |
| 319 | + }else if( !p->bOpen ){ | |
| 303 | 320 | blob_init(in, 0, 0); |
| 304 | 321 | }else{ |
| 305 | 322 | if( n>0 && i>=n ){ |
| 306 | 323 | blob_truncate(&p->inbuf, 0); |
| 307 | 324 | blob_rewind(&p->inbuf); |
| @@ -320,13 +337,11 @@ | ||
| 320 | 337 | if( got==1000 ) continue; |
| 321 | 338 | } |
| 322 | 339 | nDelay++; |
| 323 | 340 | if( nDelay>100 ){ |
| 324 | 341 | blob_init(in, 0, 0); |
| 325 | - p->zErr = mprintf("timeout"); | |
| 326 | - socket_close(); | |
| 327 | - p->atEof = 1; | |
| 342 | + smtp_set_error(p, 1, "client times out waiting on server response"); | |
| 328 | 343 | return; |
| 329 | 344 | }else{ |
| 330 | 345 | sqlite3_sleep(100); |
| 331 | 346 | } |
| 332 | 347 | }while( n<1 || z[n-1]!='\n' ); |
| @@ -360,10 +375,11 @@ | ||
| 360 | 375 | ){ |
| 361 | 376 | int n; |
| 362 | 377 | char *z; |
| 363 | 378 | blob_truncate(in, 0); |
| 364 | 379 | smtp_recv_line(p, in); |
| 380 | + blob_trim(in); | |
| 365 | 381 | z = blob_str(in); |
| 366 | 382 | n = blob_size(in); |
| 367 | 383 | if( z[0]=='#' ){ |
| 368 | 384 | *piCode = 0; |
| 369 | 385 | *pbMore = 1; |
| @@ -381,48 +397,57 @@ | ||
| 381 | 397 | int smtp_client_quit(SmtpSession *p){ |
| 382 | 398 | Blob in = BLOB_INITIALIZER; |
| 383 | 399 | int iCode = 0; |
| 384 | 400 | int bMore = 0; |
| 385 | 401 | char *zArg = 0; |
| 386 | - if( !p->atEof ){ | |
| 402 | + if( p->bOpen ){ | |
| 387 | 403 | smtp_send_line(p, "QUIT\r\n"); |
| 388 | 404 | do{ |
| 389 | 405 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 390 | 406 | }while( bMore ); |
| 391 | - p->atEof = 1; | |
| 407 | + p->bOpen = 0; | |
| 408 | + socket_close(); | |
| 392 | 409 | } |
| 393 | - socket_close(); | |
| 394 | 410 | return 0; |
| 395 | 411 | } |
| 396 | 412 | |
| 397 | 413 | /* |
| 398 | 414 | ** Begin a client SMTP session. Wait for the initial 220 then send |
| 399 | 415 | ** the EHLO and wait for a 250. |
| 400 | 416 | ** |
| 401 | 417 | ** Return 0 on success and non-zero for a failure. |
| 402 | 418 | */ |
| 403 | -int smtp_client_startup(SmtpSession *p){ | |
| 419 | +static int smtp_client_startup(SmtpSession *p){ | |
| 404 | 420 | Blob in = BLOB_INITIALIZER; |
| 405 | 421 | int iCode = 0; |
| 406 | 422 | int bMore = 0; |
| 407 | 423 | char *zArg = 0; |
| 408 | - if( p==0 || p->atEof ) return 1; | |
| 424 | + if( p==0 || p->bFatal ) return 1; | |
| 425 | + if( socket_open(&p->url) ){ | |
| 426 | + smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg()); | |
| 427 | + return 1; | |
| 428 | + } | |
| 429 | + p->bOpen = 1; | |
| 409 | 430 | do{ |
| 410 | 431 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 411 | 432 | }while( bMore ); |
| 412 | 433 | if( iCode!=220 ){ |
| 434 | + smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg); | |
| 413 | 435 | smtp_client_quit(p); |
| 414 | 436 | return 1; |
| 415 | 437 | } |
| 416 | 438 | smtp_send_line(p, "EHLO %s\r\n", p->zFrom); |
| 417 | 439 | do{ |
| 418 | 440 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 419 | 441 | }while( bMore ); |
| 420 | 442 | if( iCode!=250 ){ |
| 443 | + smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg); | |
| 421 | 444 | smtp_client_quit(p); |
| 422 | 445 | return 1; |
| 423 | 446 | } |
| 447 | + fossil_free(p->zErr); | |
| 448 | + p->zErr = 0; | |
| 424 | 449 | return 0; |
| 425 | 450 | } |
| 426 | 451 | |
| 427 | 452 | /* |
| 428 | 453 | ** COMMAND: test-smtp-probe |
| @@ -550,27 +575,40 @@ | ||
| 550 | 575 | int iCode = 0; |
| 551 | 576 | int bMore = 0; |
| 552 | 577 | char *zArg = 0; |
| 553 | 578 | Blob in; |
| 554 | 579 | blob_init(&in, 0, 0); |
| 580 | + if( !p->bOpen ){ | |
| 581 | + if( !p->bFatal ) smtp_client_startup(p); | |
| 582 | + if( !p->bOpen ) return 1; | |
| 583 | + } | |
| 555 | 584 | smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom); |
| 556 | 585 | do{ |
| 557 | 586 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 558 | 587 | }while( bMore ); |
| 559 | - if( iCode!=250 ) return 1; | |
| 588 | + if( iCode!=250 ){ | |
| 589 | + smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg); | |
| 590 | + return 1; | |
| 591 | + } | |
| 560 | 592 | for(i=0; i<nTo; i++){ |
| 561 | 593 | smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]); |
| 562 | 594 | do{ |
| 563 | 595 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 564 | 596 | }while( bMore ); |
| 565 | - if( iCode!=250 ) return 1; | |
| 597 | + if( iCode!=250 ){ | |
| 598 | + smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg); | |
| 599 | + return 1; | |
| 600 | + } | |
| 566 | 601 | } |
| 567 | 602 | smtp_send_line(p, "DATA\r\n"); |
| 568 | 603 | do{ |
| 569 | 604 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 570 | 605 | }while( bMore ); |
| 571 | - if( iCode!=354 ) return 1; | |
| 606 | + if( iCode!=354 ){ | |
| 607 | + smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg); | |
| 608 | + return 1; | |
| 609 | + } | |
| 572 | 610 | smtp_send_email_body(zMsg, socket_send, 0); |
| 573 | 611 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 574 | 612 | fossil_print("C: # message content\nC: .\n"); |
| 575 | 613 | } |
| 576 | 614 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| @@ -580,11 +618,15 @@ | ||
| 580 | 618 | blob_appendf(p->pTranscript, "C: # message content\nC: .\n"); |
| 581 | 619 | } |
| 582 | 620 | do{ |
| 583 | 621 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 584 | 622 | }while( bMore ); |
| 585 | - if( iCode!=250 ) return 1; | |
| 623 | + if( iCode!=250 ){ | |
| 624 | + smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"", | |
| 625 | + iCode, zArg); | |
| 626 | + return 1; | |
| 627 | + } | |
| 586 | 628 | return 0; |
| 587 | 629 | } |
| 588 | 630 | |
| 589 | 631 | /* |
| 590 | 632 | ** The input is a base email address of the form "local@domain". |
| @@ -645,14 +687,13 @@ | ||
| 645 | 687 | p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort); |
| 646 | 688 | if( p->zErr ){ |
| 647 | 689 | fossil_fatal("%s", p->zErr); |
| 648 | 690 | } |
| 649 | 691 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 650 | - smtp_client_startup(p); | |
| 651 | 692 | smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body)); |
| 652 | 693 | smtp_client_quit(p); |
| 653 | 694 | if( p->zErr ){ |
| 654 | 695 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 655 | 696 | } |
| 656 | 697 | smtp_session_free(p); |
| 657 | 698 | blob_reset(&body); |
| 658 | 699 | } |
| 659 | 700 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -156,13 +156,15 @@ | |
| 156 | const char *zDest; /* Domain that will receive the email */ |
| 157 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 158 | u32 smtpFlags; /* Flags changing the operation */ |
| 159 | FILE *logFile; /* Write session transcript to this log file */ |
| 160 | Blob *pTranscript; /* Record session transcript here */ |
| 161 | int atEof; /* True after connection closes */ |
| 162 | char *zErr; /* Error message */ |
| 163 | Blob inbuf; /* Input buffer */ |
| 164 | }; |
| 165 | |
| 166 | /* Allowed values for SmtpSession.smtpFlags */ |
| 167 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 168 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| @@ -180,10 +182,32 @@ | |
| 180 | blob_reset(&pSession->inbuf); |
| 181 | fossil_free(pSession->zHostname); |
| 182 | fossil_free(pSession->zErr); |
| 183 | fossil_free(pSession); |
| 184 | } |
| 185 | |
| 186 | /* |
| 187 | ** Allocate a new SmtpSession object. |
| 188 | ** |
| 189 | ** Both zFrom and zDest must be specified. smtpFlags may not contain |
| @@ -197,46 +221,39 @@ | |
| 197 | const char *zDest, /* Domain of the server */ |
| 198 | u32 smtpFlags, /* Flags */ |
| 199 | int iPort /* TCP port if the SMTP_PORT flags is present */ |
| 200 | ){ |
| 201 | SmtpSession *p; |
| 202 | UrlData url; |
| 203 | |
| 204 | p = fossil_malloc( sizeof(*p) ); |
| 205 | memset(p, 0, sizeof(*p)); |
| 206 | p->zFrom = zFrom; |
| 207 | p->zDest = zDest; |
| 208 | p->smtpFlags = smtpFlags; |
| 209 | memset(&url, 0, sizeof(url)); |
| 210 | url.port = 25; |
| 211 | blob_init(&p->inbuf, 0, 0); |
| 212 | if( smtpFlags & SMTP_PORT ){ |
| 213 | url.port = iPort; |
| 214 | } |
| 215 | if( (smtpFlags & SMTP_DIRECT)!=0 ){ |
| 216 | int i; |
| 217 | p->zHostname = fossil_strdup(zDest); |
| 218 | for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){} |
| 219 | if( p->zHostname[i]==':' ){ |
| 220 | p->zHostname[i] = 0; |
| 221 | url.port = atoi(&p->zHostname[i+1]); |
| 222 | } |
| 223 | }else{ |
| 224 | p->zHostname = smtp_mx_host(zDest); |
| 225 | } |
| 226 | if( p->zHostname==0 ){ |
| 227 | p->atEof = 1; |
| 228 | p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest); |
| 229 | return p; |
| 230 | } |
| 231 | url.name = p->zHostname; |
| 232 | socket_global_init(); |
| 233 | if( socket_open(&url) ){ |
| 234 | p->atEof = 1; |
| 235 | p->zErr = socket_errmsg(); |
| 236 | socket_close(); |
| 237 | } |
| 238 | return p; |
| 239 | } |
| 240 | |
| 241 | /* |
| 242 | ** Configure debugging options on SmtpSession. Add all bits in |
| @@ -261,11 +278,11 @@ | |
| 261 | static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){ |
| 262 | Blob b = empty_blob; |
| 263 | va_list ap; |
| 264 | char *z; |
| 265 | int n; |
| 266 | if( p->atEof ) return; |
| 267 | va_start(ap, zFormat); |
| 268 | blob_vappendf(&b, zFormat, ap); |
| 269 | va_end(ap); |
| 270 | z = blob_buffer(&b); |
| 271 | n = blob_size(&b); |
| @@ -297,11 +314,11 @@ | |
| 297 | char *z = blob_buffer(&p->inbuf); |
| 298 | int i = blob_tell(&p->inbuf); |
| 299 | int nDelay = 0; |
| 300 | if( i<n && z[n-1]=='\n' ){ |
| 301 | blob_line(&p->inbuf, in); |
| 302 | }else if( p->atEof ){ |
| 303 | blob_init(in, 0, 0); |
| 304 | }else{ |
| 305 | if( n>0 && i>=n ){ |
| 306 | blob_truncate(&p->inbuf, 0); |
| 307 | blob_rewind(&p->inbuf); |
| @@ -320,13 +337,11 @@ | |
| 320 | if( got==1000 ) continue; |
| 321 | } |
| 322 | nDelay++; |
| 323 | if( nDelay>100 ){ |
| 324 | blob_init(in, 0, 0); |
| 325 | p->zErr = mprintf("timeout"); |
| 326 | socket_close(); |
| 327 | p->atEof = 1; |
| 328 | return; |
| 329 | }else{ |
| 330 | sqlite3_sleep(100); |
| 331 | } |
| 332 | }while( n<1 || z[n-1]!='\n' ); |
| @@ -360,10 +375,11 @@ | |
| 360 | ){ |
| 361 | int n; |
| 362 | char *z; |
| 363 | blob_truncate(in, 0); |
| 364 | smtp_recv_line(p, in); |
| 365 | z = blob_str(in); |
| 366 | n = blob_size(in); |
| 367 | if( z[0]=='#' ){ |
| 368 | *piCode = 0; |
| 369 | *pbMore = 1; |
| @@ -381,48 +397,57 @@ | |
| 381 | int smtp_client_quit(SmtpSession *p){ |
| 382 | Blob in = BLOB_INITIALIZER; |
| 383 | int iCode = 0; |
| 384 | int bMore = 0; |
| 385 | char *zArg = 0; |
| 386 | if( !p->atEof ){ |
| 387 | smtp_send_line(p, "QUIT\r\n"); |
| 388 | do{ |
| 389 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 390 | }while( bMore ); |
| 391 | p->atEof = 1; |
| 392 | } |
| 393 | socket_close(); |
| 394 | return 0; |
| 395 | } |
| 396 | |
| 397 | /* |
| 398 | ** Begin a client SMTP session. Wait for the initial 220 then send |
| 399 | ** the EHLO and wait for a 250. |
| 400 | ** |
| 401 | ** Return 0 on success and non-zero for a failure. |
| 402 | */ |
| 403 | int smtp_client_startup(SmtpSession *p){ |
| 404 | Blob in = BLOB_INITIALIZER; |
| 405 | int iCode = 0; |
| 406 | int bMore = 0; |
| 407 | char *zArg = 0; |
| 408 | if( p==0 || p->atEof ) return 1; |
| 409 | do{ |
| 410 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 411 | }while( bMore ); |
| 412 | if( iCode!=220 ){ |
| 413 | smtp_client_quit(p); |
| 414 | return 1; |
| 415 | } |
| 416 | smtp_send_line(p, "EHLO %s\r\n", p->zFrom); |
| 417 | do{ |
| 418 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 419 | }while( bMore ); |
| 420 | if( iCode!=250 ){ |
| 421 | smtp_client_quit(p); |
| 422 | return 1; |
| 423 | } |
| 424 | return 0; |
| 425 | } |
| 426 | |
| 427 | /* |
| 428 | ** COMMAND: test-smtp-probe |
| @@ -550,27 +575,40 @@ | |
| 550 | int iCode = 0; |
| 551 | int bMore = 0; |
| 552 | char *zArg = 0; |
| 553 | Blob in; |
| 554 | blob_init(&in, 0, 0); |
| 555 | smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom); |
| 556 | do{ |
| 557 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 558 | }while( bMore ); |
| 559 | if( iCode!=250 ) return 1; |
| 560 | for(i=0; i<nTo; i++){ |
| 561 | smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]); |
| 562 | do{ |
| 563 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 564 | }while( bMore ); |
| 565 | if( iCode!=250 ) return 1; |
| 566 | } |
| 567 | smtp_send_line(p, "DATA\r\n"); |
| 568 | do{ |
| 569 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 570 | }while( bMore ); |
| 571 | if( iCode!=354 ) return 1; |
| 572 | smtp_send_email_body(zMsg, socket_send, 0); |
| 573 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 574 | fossil_print("C: # message content\nC: .\n"); |
| 575 | } |
| 576 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| @@ -580,11 +618,15 @@ | |
| 580 | blob_appendf(p->pTranscript, "C: # message content\nC: .\n"); |
| 581 | } |
| 582 | do{ |
| 583 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 584 | }while( bMore ); |
| 585 | if( iCode!=250 ) return 1; |
| 586 | return 0; |
| 587 | } |
| 588 | |
| 589 | /* |
| 590 | ** The input is a base email address of the form "local@domain". |
| @@ -645,14 +687,13 @@ | |
| 645 | p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort); |
| 646 | if( p->zErr ){ |
| 647 | fossil_fatal("%s", p->zErr); |
| 648 | } |
| 649 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 650 | smtp_client_startup(p); |
| 651 | smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body)); |
| 652 | smtp_client_quit(p); |
| 653 | if( p->zErr ){ |
| 654 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 655 | } |
| 656 | smtp_session_free(p); |
| 657 | blob_reset(&body); |
| 658 | } |
| 659 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -156,13 +156,15 @@ | |
| 156 | const char *zDest; /* Domain that will receive the email */ |
| 157 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 158 | u32 smtpFlags; /* Flags changing the operation */ |
| 159 | FILE *logFile; /* Write session transcript to this log file */ |
| 160 | Blob *pTranscript; /* Record session transcript here */ |
| 161 | int bOpen; /* True if connection is Open */ |
| 162 | int bFatal; /* Error is fatal. Do not retry */ |
| 163 | char *zErr; /* Error message */ |
| 164 | Blob inbuf; /* Input buffer */ |
| 165 | UrlData url; /* Address of the server */ |
| 166 | }; |
| 167 | |
| 168 | /* Allowed values for SmtpSession.smtpFlags */ |
| 169 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 170 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| @@ -180,10 +182,32 @@ | |
| 182 | blob_reset(&pSession->inbuf); |
| 183 | fossil_free(pSession->zHostname); |
| 184 | fossil_free(pSession->zErr); |
| 185 | fossil_free(pSession); |
| 186 | } |
| 187 | |
| 188 | /* |
| 189 | ** Set an error message on the SmtpSession |
| 190 | */ |
| 191 | static void smtp_set_error( |
| 192 | SmtpSession *p, /* The SMTP context */ |
| 193 | int bFatal, /* Fatal error. Reset and retry is pointless */ |
| 194 | const char *zFormat, /* Error message. */ |
| 195 | ... |
| 196 | ){ |
| 197 | if( bFatal ) p->bFatal = 1; |
| 198 | if( p->zErr==0 ){ |
| 199 | va_list ap; |
| 200 | va_start(ap, zFormat); |
| 201 | p->zErr = vmprintf(zFormat, ap); |
| 202 | va_end(ap); |
| 203 | } |
| 204 | if( p->bOpen ){ |
| 205 | socket_close(); |
| 206 | p->bOpen = 0; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | /* |
| 211 | ** Allocate a new SmtpSession object. |
| 212 | ** |
| 213 | ** Both zFrom and zDest must be specified. smtpFlags may not contain |
| @@ -197,46 +221,39 @@ | |
| 221 | const char *zDest, /* Domain of the server */ |
| 222 | u32 smtpFlags, /* Flags */ |
| 223 | int iPort /* TCP port if the SMTP_PORT flags is present */ |
| 224 | ){ |
| 225 | SmtpSession *p; |
| 226 | |
| 227 | p = fossil_malloc( sizeof(*p) ); |
| 228 | memset(p, 0, sizeof(*p)); |
| 229 | p->zFrom = zFrom; |
| 230 | p->zDest = zDest; |
| 231 | p->smtpFlags = smtpFlags; |
| 232 | p->url.port = 25; |
| 233 | blob_init(&p->inbuf, 0, 0); |
| 234 | if( smtpFlags & SMTP_PORT ){ |
| 235 | p->url.port = iPort; |
| 236 | } |
| 237 | if( (smtpFlags & SMTP_DIRECT)!=0 ){ |
| 238 | int i; |
| 239 | p->zHostname = fossil_strdup(zDest); |
| 240 | for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){} |
| 241 | if( p->zHostname[i]==':' ){ |
| 242 | p->zHostname[i] = 0; |
| 243 | p->url.port = atoi(&p->zHostname[i+1]); |
| 244 | } |
| 245 | }else{ |
| 246 | p->zHostname = smtp_mx_host(zDest); |
| 247 | } |
| 248 | if( p->zHostname==0 ){ |
| 249 | smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest); |
| 250 | return p; |
| 251 | } |
| 252 | p->url.name = p->zHostname; |
| 253 | socket_global_init(); |
| 254 | p->bOpen = 0; |
| 255 | return p; |
| 256 | } |
| 257 | |
| 258 | /* |
| 259 | ** Configure debugging options on SmtpSession. Add all bits in |
| @@ -261,11 +278,11 @@ | |
| 278 | static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){ |
| 279 | Blob b = empty_blob; |
| 280 | va_list ap; |
| 281 | char *z; |
| 282 | int n; |
| 283 | if( !p->bOpen ) return; |
| 284 | va_start(ap, zFormat); |
| 285 | blob_vappendf(&b, zFormat, ap); |
| 286 | va_end(ap); |
| 287 | z = blob_buffer(&b); |
| 288 | n = blob_size(&b); |
| @@ -297,11 +314,11 @@ | |
| 314 | char *z = blob_buffer(&p->inbuf); |
| 315 | int i = blob_tell(&p->inbuf); |
| 316 | int nDelay = 0; |
| 317 | if( i<n && z[n-1]=='\n' ){ |
| 318 | blob_line(&p->inbuf, in); |
| 319 | }else if( !p->bOpen ){ |
| 320 | blob_init(in, 0, 0); |
| 321 | }else{ |
| 322 | if( n>0 && i>=n ){ |
| 323 | blob_truncate(&p->inbuf, 0); |
| 324 | blob_rewind(&p->inbuf); |
| @@ -320,13 +337,11 @@ | |
| 337 | if( got==1000 ) continue; |
| 338 | } |
| 339 | nDelay++; |
| 340 | if( nDelay>100 ){ |
| 341 | blob_init(in, 0, 0); |
| 342 | smtp_set_error(p, 1, "client times out waiting on server response"); |
| 343 | return; |
| 344 | }else{ |
| 345 | sqlite3_sleep(100); |
| 346 | } |
| 347 | }while( n<1 || z[n-1]!='\n' ); |
| @@ -360,10 +375,11 @@ | |
| 375 | ){ |
| 376 | int n; |
| 377 | char *z; |
| 378 | blob_truncate(in, 0); |
| 379 | smtp_recv_line(p, in); |
| 380 | blob_trim(in); |
| 381 | z = blob_str(in); |
| 382 | n = blob_size(in); |
| 383 | if( z[0]=='#' ){ |
| 384 | *piCode = 0; |
| 385 | *pbMore = 1; |
| @@ -381,48 +397,57 @@ | |
| 397 | int smtp_client_quit(SmtpSession *p){ |
| 398 | Blob in = BLOB_INITIALIZER; |
| 399 | int iCode = 0; |
| 400 | int bMore = 0; |
| 401 | char *zArg = 0; |
| 402 | if( p->bOpen ){ |
| 403 | smtp_send_line(p, "QUIT\r\n"); |
| 404 | do{ |
| 405 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 406 | }while( bMore ); |
| 407 | p->bOpen = 0; |
| 408 | socket_close(); |
| 409 | } |
| 410 | return 0; |
| 411 | } |
| 412 | |
| 413 | /* |
| 414 | ** Begin a client SMTP session. Wait for the initial 220 then send |
| 415 | ** the EHLO and wait for a 250. |
| 416 | ** |
| 417 | ** Return 0 on success and non-zero for a failure. |
| 418 | */ |
| 419 | static int smtp_client_startup(SmtpSession *p){ |
| 420 | Blob in = BLOB_INITIALIZER; |
| 421 | int iCode = 0; |
| 422 | int bMore = 0; |
| 423 | char *zArg = 0; |
| 424 | if( p==0 || p->bFatal ) return 1; |
| 425 | if( socket_open(&p->url) ){ |
| 426 | smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg()); |
| 427 | return 1; |
| 428 | } |
| 429 | p->bOpen = 1; |
| 430 | do{ |
| 431 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 432 | }while( bMore ); |
| 433 | if( iCode!=220 ){ |
| 434 | smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg); |
| 435 | smtp_client_quit(p); |
| 436 | return 1; |
| 437 | } |
| 438 | smtp_send_line(p, "EHLO %s\r\n", p->zFrom); |
| 439 | do{ |
| 440 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 441 | }while( bMore ); |
| 442 | if( iCode!=250 ){ |
| 443 | smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg); |
| 444 | smtp_client_quit(p); |
| 445 | return 1; |
| 446 | } |
| 447 | fossil_free(p->zErr); |
| 448 | p->zErr = 0; |
| 449 | return 0; |
| 450 | } |
| 451 | |
| 452 | /* |
| 453 | ** COMMAND: test-smtp-probe |
| @@ -550,27 +575,40 @@ | |
| 575 | int iCode = 0; |
| 576 | int bMore = 0; |
| 577 | char *zArg = 0; |
| 578 | Blob in; |
| 579 | blob_init(&in, 0, 0); |
| 580 | if( !p->bOpen ){ |
| 581 | if( !p->bFatal ) smtp_client_startup(p); |
| 582 | if( !p->bOpen ) return 1; |
| 583 | } |
| 584 | smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom); |
| 585 | do{ |
| 586 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 587 | }while( bMore ); |
| 588 | if( iCode!=250 ){ |
| 589 | smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg); |
| 590 | return 1; |
| 591 | } |
| 592 | for(i=0; i<nTo; i++){ |
| 593 | smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]); |
| 594 | do{ |
| 595 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 596 | }while( bMore ); |
| 597 | if( iCode!=250 ){ |
| 598 | smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg); |
| 599 | return 1; |
| 600 | } |
| 601 | } |
| 602 | smtp_send_line(p, "DATA\r\n"); |
| 603 | do{ |
| 604 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 605 | }while( bMore ); |
| 606 | if( iCode!=354 ){ |
| 607 | smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg); |
| 608 | return 1; |
| 609 | } |
| 610 | smtp_send_email_body(zMsg, socket_send, 0); |
| 611 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 612 | fossil_print("C: # message content\nC: .\n"); |
| 613 | } |
| 614 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| @@ -580,11 +618,15 @@ | |
| 618 | blob_appendf(p->pTranscript, "C: # message content\nC: .\n"); |
| 619 | } |
| 620 | do{ |
| 621 | smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg); |
| 622 | }while( bMore ); |
| 623 | if( iCode!=250 ){ |
| 624 | smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"", |
| 625 | iCode, zArg); |
| 626 | return 1; |
| 627 | } |
| 628 | return 0; |
| 629 | } |
| 630 | |
| 631 | /* |
| 632 | ** The input is a base email address of the form "local@domain". |
| @@ -645,14 +687,13 @@ | |
| 687 | p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort); |
| 688 | if( p->zErr ){ |
| 689 | fossil_fatal("%s", p->zErr); |
| 690 | } |
| 691 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 692 | smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body)); |
| 693 | smtp_client_quit(p); |
| 694 | if( p->zErr ){ |
| 695 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 696 | } |
| 697 | smtp_session_free(p); |
| 698 | blob_reset(&body); |
| 699 | } |
| 700 |
+8
-2
| --- src/stash.c | ||
| +++ src/stash.c | ||
| @@ -259,10 +259,11 @@ | ||
| 259 | 259 | const char *zComment; /* Comment to add to the stash */ |
| 260 | 260 | int stashid; /* ID of the new stash */ |
| 261 | 261 | int vid; /* Current check-out */ |
| 262 | 262 | |
| 263 | 263 | zComment = find_option("comment", "m", 1); |
| 264 | + (void)fossil_text_editor(); | |
| 264 | 265 | verify_all_options(); |
| 265 | 266 | if( zComment==0 ){ |
| 266 | 267 | Blob prompt; /* Prompt for stash comment */ |
| 267 | 268 | Blob comment; /* User comment reply */ |
| 268 | 269 | #if defined(_WIN32) || defined(__CYGWIN__) |
| @@ -508,19 +509,24 @@ | ||
| 508 | 509 | ** COMMAND: stash |
| 509 | 510 | ** |
| 510 | 511 | ** Usage: %fossil stash SUBCOMMAND ARGS... |
| 511 | 512 | ** |
| 512 | 513 | ** > fossil stash |
| 513 | -** > fossil stash save ?-m|--comment COMMENT? ?FILES...? | |
| 514 | -** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...? | |
| 514 | +** > fossil stash save ?FILES...? | |
| 515 | +** > fossil stash snapshot ?FILES...? | |
| 515 | 516 | ** |
| 516 | 517 | ** Save the current changes in the working tree as a new stash. |
| 517 | 518 | ** Then revert the changes back to the last check-in. If FILES |
| 518 | 519 | ** are listed, then only stash and revert the named files. The |
| 519 | 520 | ** "save" verb can be omitted if and only if there are no other |
| 520 | 521 | ** arguments. The "snapshot" verb works the same as "save" but |
| 521 | 522 | ** omits the revert, keeping the check-out unchanged. |
| 523 | +** | |
| 524 | +** Options: | |
| 525 | +** --editor NAME Use the NAME editor to enter comment | |
| 526 | +** -m|--comment COMMENT Comment text for the new stash | |
| 527 | +** | |
| 522 | 528 | ** |
| 523 | 529 | ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM? |
| 524 | 530 | ** |
| 525 | 531 | ** List all changes sets currently stashed. Show information about |
| 526 | 532 | ** individual files in each changeset if -v or --verbose is used. |
| 527 | 533 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -259,10 +259,11 @@ | |
| 259 | const char *zComment; /* Comment to add to the stash */ |
| 260 | int stashid; /* ID of the new stash */ |
| 261 | int vid; /* Current check-out */ |
| 262 | |
| 263 | zComment = find_option("comment", "m", 1); |
| 264 | verify_all_options(); |
| 265 | if( zComment==0 ){ |
| 266 | Blob prompt; /* Prompt for stash comment */ |
| 267 | Blob comment; /* User comment reply */ |
| 268 | #if defined(_WIN32) || defined(__CYGWIN__) |
| @@ -508,19 +509,24 @@ | |
| 508 | ** COMMAND: stash |
| 509 | ** |
| 510 | ** Usage: %fossil stash SUBCOMMAND ARGS... |
| 511 | ** |
| 512 | ** > fossil stash |
| 513 | ** > fossil stash save ?-m|--comment COMMENT? ?FILES...? |
| 514 | ** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...? |
| 515 | ** |
| 516 | ** Save the current changes in the working tree as a new stash. |
| 517 | ** Then revert the changes back to the last check-in. If FILES |
| 518 | ** are listed, then only stash and revert the named files. The |
| 519 | ** "save" verb can be omitted if and only if there are no other |
| 520 | ** arguments. The "snapshot" verb works the same as "save" but |
| 521 | ** omits the revert, keeping the check-out unchanged. |
| 522 | ** |
| 523 | ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM? |
| 524 | ** |
| 525 | ** List all changes sets currently stashed. Show information about |
| 526 | ** individual files in each changeset if -v or --verbose is used. |
| 527 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -259,10 +259,11 @@ | |
| 259 | const char *zComment; /* Comment to add to the stash */ |
| 260 | int stashid; /* ID of the new stash */ |
| 261 | int vid; /* Current check-out */ |
| 262 | |
| 263 | zComment = find_option("comment", "m", 1); |
| 264 | (void)fossil_text_editor(); |
| 265 | verify_all_options(); |
| 266 | if( zComment==0 ){ |
| 267 | Blob prompt; /* Prompt for stash comment */ |
| 268 | Blob comment; /* User comment reply */ |
| 269 | #if defined(_WIN32) || defined(__CYGWIN__) |
| @@ -508,19 +509,24 @@ | |
| 509 | ** COMMAND: stash |
| 510 | ** |
| 511 | ** Usage: %fossil stash SUBCOMMAND ARGS... |
| 512 | ** |
| 513 | ** > fossil stash |
| 514 | ** > fossil stash save ?FILES...? |
| 515 | ** > fossil stash snapshot ?FILES...? |
| 516 | ** |
| 517 | ** Save the current changes in the working tree as a new stash. |
| 518 | ** Then revert the changes back to the last check-in. If FILES |
| 519 | ** are listed, then only stash and revert the named files. The |
| 520 | ** "save" verb can be omitted if and only if there are no other |
| 521 | ** arguments. The "snapshot" verb works the same as "save" but |
| 522 | ** omits the revert, keeping the check-out unchanged. |
| 523 | ** |
| 524 | ** Options: |
| 525 | ** --editor NAME Use the NAME editor to enter comment |
| 526 | ** -m|--comment COMMENT Comment text for the new stash |
| 527 | ** |
| 528 | ** |
| 529 | ** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM? |
| 530 | ** |
| 531 | ** List all changes sets currently stashed. Show information about |
| 532 | ** individual files in each changeset if -v or --verbose is used. |
| 533 |
+3
-2
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -744,12 +744,13 @@ | ||
| 744 | 744 | ** is evaluated before the header is rendered). |
| 745 | 745 | */ |
| 746 | 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | 747 | fossil_free(zDfltCsp); |
| 748 | 748 | Th_Store("nonce", zNonce); |
| 749 | - Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); | |
| 750 | - Th_Store("project_description", db_get("project-description","")); | |
| 749 | + Th_StoreUnsafe("project_name", | |
| 750 | + db_get("project-name","Unnamed Fossil Project")); | |
| 751 | + Th_StoreUnsafe("project_description", db_get("project-description","")); | |
| 751 | 752 | if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); |
| 752 | 753 | Th_Store("baseurl", g.zBaseURL); |
| 753 | 754 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | 755 | Th_Store("home", g.zTop); |
| 755 | 756 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 | 757 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -744,12 +744,13 @@ | |
| 744 | ** is evaluated before the header is rendered). |
| 745 | */ |
| 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | fossil_free(zDfltCsp); |
| 748 | Th_Store("nonce", zNonce); |
| 749 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 750 | Th_Store("project_description", db_get("project-description","")); |
| 751 | if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); |
| 752 | Th_Store("baseurl", g.zBaseURL); |
| 753 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 754 | Th_Store("home", g.zTop); |
| 755 | Th_Store("index_page", db_get("index-page","/home")); |
| 756 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -744,12 +744,13 @@ | |
| 744 | ** is evaluated before the header is rendered). |
| 745 | */ |
| 746 | Th_MaybeStore("default_csp", zDfltCsp); |
| 747 | fossil_free(zDfltCsp); |
| 748 | Th_Store("nonce", zNonce); |
| 749 | Th_StoreUnsafe("project_name", |
| 750 | db_get("project-name","Unnamed Fossil Project")); |
| 751 | Th_StoreUnsafe("project_description", db_get("project-description","")); |
| 752 | if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); |
| 753 | Th_Store("baseurl", g.zBaseURL); |
| 754 | Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); |
| 755 | Th_Store("home", g.zTop); |
| 756 | Th_Store("index_page", db_get("index-page","/home")); |
| 757 |
M
src/th.c
+87
-54
| --- src/th.c | ||
| +++ src/th.c | ||
| @@ -7,10 +7,16 @@ | ||
| 7 | 7 | #include "config.h" |
| 8 | 8 | #include "th.h" |
| 9 | 9 | #include <string.h> |
| 10 | 10 | #include <assert.h> |
| 11 | 11 | |
| 12 | +/* | |
| 13 | +** External routines | |
| 14 | +*/ | |
| 15 | +void fossil_panic(const char*,...); | |
| 16 | +void fossil_errorlog(const char*,...); | |
| 17 | + | |
| 12 | 18 | /* |
| 13 | 19 | ** Values used for element values in the tcl_platform array. |
| 14 | 20 | */ |
| 15 | 21 | |
| 16 | 22 | #if !defined(TH_ENGINE) |
| @@ -197,10 +203,11 @@ | ||
| 197 | 203 | */ |
| 198 | 204 | struct Buffer { |
| 199 | 205 | char *zBuf; |
| 200 | 206 | int nBuf; |
| 201 | 207 | int nBufAlloc; |
| 208 | + int bTaint; | |
| 202 | 209 | }; |
| 203 | 210 | typedef struct Buffer Buffer; |
| 204 | 211 | static void thBufferInit(Buffer *); |
| 205 | 212 | static void thBufferFree(Th_Interp *interp, Buffer *); |
| 206 | 213 | |
| @@ -209,10 +216,18 @@ | ||
| 209 | 216 | ** be NULL as long as the number of bytes to copy is zero. |
| 210 | 217 | */ |
| 211 | 218 | static void th_memcpy(void *dest, const void *src, size_t n){ |
| 212 | 219 | if( n>0 ) memcpy(dest,src,n); |
| 213 | 220 | } |
| 221 | + | |
| 222 | +/* | |
| 223 | +** An oversized string has been encountered. Do not try to recover. | |
| 224 | +** Panic the process. | |
| 225 | +*/ | |
| 226 | +void Th_OversizeString(void){ | |
| 227 | + fossil_panic("string too large. maximum size 286MB."); | |
| 228 | +} | |
| 214 | 229 | |
| 215 | 230 | /* |
| 216 | 231 | ** Append nAdd bytes of content copied from zAdd to the end of buffer |
| 217 | 232 | ** pBuffer. If there is not enough space currently allocated, resize |
| 218 | 233 | ** the allocation to make space. |
| @@ -219,40 +234,46 @@ | ||
| 219 | 234 | */ |
| 220 | 235 | static void thBufferWriteResize( |
| 221 | 236 | Th_Interp *interp, |
| 222 | 237 | Buffer *pBuffer, |
| 223 | 238 | const char *zAdd, |
| 224 | - int nAdd | |
| 239 | + int nAddX | |
| 225 | 240 | ){ |
| 241 | + int nAdd = TH1_LEN(nAddX); | |
| 226 | 242 | int nNew = (pBuffer->nBuf+nAdd)*2+32; |
| 227 | 243 | #if defined(TH_MEMDEBUG) |
| 228 | 244 | char *zNew = (char *)Th_Malloc(interp, nNew); |
| 245 | + TH1_SIZECHECK(nNew); | |
| 229 | 246 | th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf); |
| 230 | 247 | Th_Free(interp, pBuffer->zBuf); |
| 231 | 248 | pBuffer->zBuf = zNew; |
| 232 | 249 | #else |
| 233 | 250 | int nOld = pBuffer->nBufAlloc; |
| 251 | + TH1_SIZECHECK(nNew); | |
| 234 | 252 | pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew); |
| 235 | 253 | memset(pBuffer->zBuf+nOld, 0, nNew-nOld); |
| 236 | 254 | #endif |
| 237 | 255 | pBuffer->nBufAlloc = nNew; |
| 238 | 256 | th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd); |
| 239 | 257 | pBuffer->nBuf += nAdd; |
| 258 | + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); | |
| 240 | 259 | } |
| 241 | 260 | static void thBufferWriteFast( |
| 242 | 261 | Th_Interp *interp, |
| 243 | 262 | Buffer *pBuffer, |
| 244 | 263 | const char *zAdd, |
| 245 | - int nAdd | |
| 264 | + int nAddX | |
| 246 | 265 | ){ |
| 266 | + int nAdd = TH1_LEN(nAddX); | |
| 247 | 267 | if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){ |
| 248 | - thBufferWriteResize(interp, pBuffer, zAdd, nAdd); | |
| 268 | + thBufferWriteResize(interp, pBuffer, zAdd, nAddX); | |
| 249 | 269 | }else{ |
| 250 | 270 | if( pBuffer->zBuf ){ |
| 251 | 271 | memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd); |
| 252 | 272 | } |
| 253 | 273 | pBuffer->nBuf += nAdd; |
| 274 | + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); | |
| 254 | 275 | } |
| 255 | 276 | } |
| 256 | 277 | #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d) |
| 257 | 278 | |
| 258 | 279 | /* |
| @@ -704,24 +725,25 @@ | ||
| 704 | 725 | int nWord |
| 705 | 726 | ){ |
| 706 | 727 | int rc = TH_OK; |
| 707 | 728 | Buffer output; |
| 708 | 729 | int i; |
| 730 | + int nn = TH1_LEN(nWord); | |
| 709 | 731 | |
| 710 | 732 | thBufferInit(&output); |
| 711 | 733 | |
| 712 | - if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){ | |
| 713 | - thBufferWrite(interp, &output, &zWord[1], nWord-2); | |
| 734 | + if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){ | |
| 735 | + thBufferWrite(interp, &output, &zWord[1], nn-2); | |
| 714 | 736 | }else{ |
| 715 | 737 | |
| 716 | 738 | /* If the word is surrounded by double-quotes strip these away. */ |
| 717 | - if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){ | |
| 739 | + if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){ | |
| 718 | 740 | zWord++; |
| 719 | - nWord -= 2; | |
| 741 | + nn -= 2; | |
| 720 | 742 | } |
| 721 | 743 | |
| 722 | - for(i=0; rc==TH_OK && i<nWord; i++){ | |
| 744 | + for(i=0; rc==TH_OK && i<nn; i++){ | |
| 723 | 745 | int nGet; |
| 724 | 746 | |
| 725 | 747 | int (*xGet)(Th_Interp *, const char*, int, int *) = 0; |
| 726 | 748 | int (*xSubst)(Th_Interp *, const char*, int) = 0; |
| 727 | 749 | |
| @@ -743,11 +765,11 @@ | ||
| 743 | 765 | thBufferAddChar(interp, &output, zWord[i]); |
| 744 | 766 | continue; /* Go to the next iteration of the for(...) loop */ |
| 745 | 767 | } |
| 746 | 768 | } |
| 747 | 769 | |
| 748 | - rc = xGet(interp, &zWord[i], nWord-i, &nGet); | |
| 770 | + rc = xGet(interp, &zWord[i], nn-i, &nGet); | |
| 749 | 771 | if( rc==TH_OK ){ |
| 750 | 772 | rc = xSubst(interp, &zWord[i], nGet); |
| 751 | 773 | } |
| 752 | 774 | if( rc==TH_OK ){ |
| 753 | 775 | const char *zRes; |
| @@ -758,11 +780,11 @@ | ||
| 758 | 780 | } |
| 759 | 781 | } |
| 760 | 782 | } |
| 761 | 783 | |
| 762 | 784 | if( rc==TH_OK ){ |
| 763 | - Th_SetResult(interp, output.zBuf, output.nBuf); | |
| 785 | + Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint); | |
| 764 | 786 | } |
| 765 | 787 | thBufferFree(interp, &output); |
| 766 | 788 | return rc; |
| 767 | 789 | } |
| 768 | 790 | |
| @@ -826,11 +848,11 @@ | ||
| 826 | 848 | Buffer strbuf; |
| 827 | 849 | Buffer lenbuf; |
| 828 | 850 | int nCount = 0; |
| 829 | 851 | |
| 830 | 852 | const char *zInput = zList; |
| 831 | - int nInput = nList; | |
| 853 | + int nInput = TH1_LEN(nList); | |
| 832 | 854 | |
| 833 | 855 | thBufferInit(&strbuf); |
| 834 | 856 | thBufferInit(&lenbuf); |
| 835 | 857 | |
| 836 | 858 | while( nInput>0 ){ |
| @@ -837,19 +859,19 @@ | ||
| 837 | 859 | const char *zWord; |
| 838 | 860 | int nWord; |
| 839 | 861 | |
| 840 | 862 | thNextSpace(interp, zInput, nInput, &nWord); |
| 841 | 863 | zInput += nWord; |
| 842 | - nInput = nList-(zInput-zList); | |
| 864 | + nInput = TH1_LEN(nList)-(zInput-zList); | |
| 843 | 865 | |
| 844 | 866 | if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0)) |
| 845 | 867 | || TH_OK!=(rc = thSubstWord(interp, zInput, nWord)) |
| 846 | 868 | ){ |
| 847 | 869 | goto finish; |
| 848 | 870 | } |
| 849 | - zInput = &zInput[nWord]; | |
| 850 | - nInput = nList-(zInput-zList); | |
| 871 | + zInput = &zInput[TH1_LEN(nWord)]; | |
| 872 | + nInput = TH1_LEN(nList)-(zInput-zList); | |
| 851 | 873 | if( nWord>0 ){ |
| 852 | 874 | zWord = Th_GetResult(interp, &nWord); |
| 853 | 875 | thBufferWrite(interp, &strbuf, zWord, nWord); |
| 854 | 876 | thBufferAddChar(interp, &strbuf, 0); |
| 855 | 877 | thBufferWrite(interp, &lenbuf, &nWord, sizeof(int)); |
| @@ -872,11 +894,11 @@ | ||
| 872 | 894 | zElem = (char *)&anElem[nCount]; |
| 873 | 895 | th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf); |
| 874 | 896 | th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf); |
| 875 | 897 | for(i=0; i<nCount;i++){ |
| 876 | 898 | azElem[i] = zElem; |
| 877 | - zElem += (anElem[i] + 1); | |
| 899 | + zElem += (TH1_LEN(anElem[i]) + 1); | |
| 878 | 900 | } |
| 879 | 901 | *pazElem = azElem; |
| 880 | 902 | *panElem = anElem; |
| 881 | 903 | } |
| 882 | 904 | if( pnCount ){ |
| @@ -894,12 +916,17 @@ | ||
| 894 | 916 | ** in the current stack frame. |
| 895 | 917 | */ |
| 896 | 918 | static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){ |
| 897 | 919 | int rc = TH_OK; |
| 898 | 920 | const char *zInput = zProgram; |
| 899 | - int nInput = nProgram; | |
| 921 | + int nInput = TH1_LEN(nProgram); | |
| 900 | 922 | |
| 923 | + if( TH1_TAINTED(nProgram) | |
| 924 | + && Th_ReportTaint(interp, "script", zProgram, nProgram) | |
| 925 | + ){ | |
| 926 | + return TH_ERROR; | |
| 927 | + } | |
| 901 | 928 | while( rc==TH_OK && nInput ){ |
| 902 | 929 | Th_HashEntry *pEntry; |
| 903 | 930 | int nSpace; |
| 904 | 931 | const char *zFirst; |
| 905 | 932 | |
| @@ -949,13 +976,13 @@ | ||
| 949 | 976 | if( rc!=TH_OK ) continue; |
| 950 | 977 | |
| 951 | 978 | if( argc>0 ){ |
| 952 | 979 | |
| 953 | 980 | /* Look up the command name in the command hash-table. */ |
| 954 | - pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0); | |
| 981 | + pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0); | |
| 955 | 982 | if( !pEntry ){ |
| 956 | - Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]); | |
| 983 | + Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0])); | |
| 957 | 984 | rc = TH_ERROR; |
| 958 | 985 | } |
| 959 | 986 | |
| 960 | 987 | /* Call the command procedure. */ |
| 961 | 988 | if( rc==TH_OK ){ |
| @@ -1053,10 +1080,12 @@ | ||
| 1053 | 1080 | }else{ |
| 1054 | 1081 | int nInput = nProgram; |
| 1055 | 1082 | |
| 1056 | 1083 | if( nInput<0 ){ |
| 1057 | 1084 | nInput = th_strlen(zProgram); |
| 1085 | + }else{ | |
| 1086 | + nInput = TH1_LEN(nInput); | |
| 1058 | 1087 | } |
| 1059 | 1088 | rc = thEvalLocal(interp, zProgram, nInput); |
| 1060 | 1089 | } |
| 1061 | 1090 | |
| 1062 | 1091 | interp->pFrame = pSavedFrame; |
| @@ -1095,10 +1124,12 @@ | ||
| 1095 | 1124 | int isGlobal = 0; |
| 1096 | 1125 | int i; |
| 1097 | 1126 | |
| 1098 | 1127 | if( nVarname<0 ){ |
| 1099 | 1128 | nVarname = th_strlen(zVarname); |
| 1129 | + }else{ | |
| 1130 | + nVarname = TH1_LEN(nVarname); | |
| 1100 | 1131 | } |
| 1101 | 1132 | nOuter = nVarname; |
| 1102 | 1133 | |
| 1103 | 1134 | /* If the variable name starts with "::", then do the lookup is in the |
| 1104 | 1135 | ** uppermost (global) frame. |
| @@ -1271,31 +1302,10 @@ | ||
| 1271 | 1302 | } |
| 1272 | 1303 | |
| 1273 | 1304 | return Th_SetResult(interp, pValue->zData, pValue->nData); |
| 1274 | 1305 | } |
| 1275 | 1306 | |
| 1276 | -/* | |
| 1277 | -** If interp has a variable with the given name, its value is returned | |
| 1278 | -** and its length is returned via *nOut if nOut is not NULL. If | |
| 1279 | -** interp has no such var then NULL is returned without setting any | |
| 1280 | -** error state and *nOut, if not NULL, is set to -1. The returned value | |
| 1281 | -** is owned by the interpreter and may be invalidated the next time | |
| 1282 | -** the interpreter is modified. | |
| 1283 | -*/ | |
| 1284 | -const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, | |
| 1285 | - int *nOut){ | |
| 1286 | - Th_Variable *pValue; | |
| 1287 | - | |
| 1288 | - pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0); | |
| 1289 | - if( !pValue || !pValue->zData ){ | |
| 1290 | - if( nOut!=0 ) *nOut = -1; | |
| 1291 | - return NULL; | |
| 1292 | - } | |
| 1293 | - if( nOut!=0 ) *nOut = pValue->nData; | |
| 1294 | - return pValue->zData; | |
| 1295 | -} | |
| 1296 | - | |
| 1297 | 1307 | /* |
| 1298 | 1308 | ** Return true if variable (zVar, nVar) exists. |
| 1299 | 1309 | */ |
| 1300 | 1310 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1301 | 1311 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| @@ -1324,28 +1334,32 @@ | ||
| 1324 | 1334 | int nVar, |
| 1325 | 1335 | const char *zValue, |
| 1326 | 1336 | int nValue |
| 1327 | 1337 | ){ |
| 1328 | 1338 | Th_Variable *pValue; |
| 1339 | + int nn; | |
| 1329 | 1340 | |
| 1341 | + nVar = TH1_LEN(nVar); | |
| 1330 | 1342 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1331 | 1343 | if( !pValue ){ |
| 1332 | 1344 | return TH_ERROR; |
| 1333 | 1345 | } |
| 1334 | 1346 | |
| 1335 | 1347 | if( nValue<0 ){ |
| 1336 | - nValue = th_strlen(zValue); | |
| 1348 | + nn = th_strlen(zValue); | |
| 1349 | + }else{ | |
| 1350 | + nn = TH1_LEN(nValue); | |
| 1337 | 1351 | } |
| 1338 | 1352 | if( pValue->zData ){ |
| 1339 | 1353 | Th_Free(interp, pValue->zData); |
| 1340 | 1354 | pValue->zData = 0; |
| 1341 | 1355 | } |
| 1342 | 1356 | |
| 1343 | - assert(zValue || nValue==0); | |
| 1344 | - pValue->zData = Th_Malloc(interp, nValue+1); | |
| 1345 | - pValue->zData[nValue] = '\0'; | |
| 1346 | - th_memcpy(pValue->zData, zValue, nValue); | |
| 1357 | + assert(zValue || nn==0); | |
| 1358 | + pValue->zData = Th_Malloc(interp, nn+1); | |
| 1359 | + pValue->zData[nn] = '\0'; | |
| 1360 | + th_memcpy(pValue->zData, zValue, nn); | |
| 1347 | 1361 | pValue->nData = nValue; |
| 1348 | 1362 | |
| 1349 | 1363 | return TH_OK; |
| 1350 | 1364 | } |
| 1351 | 1365 | |
| @@ -1458,10 +1472,12 @@ | ||
| 1458 | 1472 | */ |
| 1459 | 1473 | char *th_strdup(Th_Interp *interp, const char *z, int n){ |
| 1460 | 1474 | char *zRes; |
| 1461 | 1475 | if( n<0 ){ |
| 1462 | 1476 | n = th_strlen(z); |
| 1477 | + }else{ | |
| 1478 | + n = TH1_LEN(n); | |
| 1463 | 1479 | } |
| 1464 | 1480 | zRes = Th_Malloc(interp, n+1); |
| 1465 | 1481 | th_memcpy(zRes, z, n); |
| 1466 | 1482 | zRes[n] = '\0'; |
| 1467 | 1483 | return zRes; |
| @@ -1519,13 +1535,14 @@ | ||
| 1519 | 1535 | n = th_strlen(z); |
| 1520 | 1536 | } |
| 1521 | 1537 | |
| 1522 | 1538 | if( z && n>0 ){ |
| 1523 | 1539 | char *zResult; |
| 1524 | - zResult = Th_Malloc(pInterp, n+1); | |
| 1525 | - th_memcpy(zResult, z, n); | |
| 1526 | - zResult[n] = '\0'; | |
| 1540 | + int nn = TH1_LEN(n); | |
| 1541 | + zResult = Th_Malloc(pInterp, nn+1); | |
| 1542 | + th_memcpy(zResult, z, nn); | |
| 1543 | + zResult[nn] = '\0'; | |
| 1527 | 1544 | pInterp->zResult = zResult; |
| 1528 | 1545 | pInterp->nResult = n; |
| 1529 | 1546 | } |
| 1530 | 1547 | |
| 1531 | 1548 | return TH_OK; |
| @@ -1777,15 +1794,19 @@ | ||
| 1777 | 1794 | int hasSpecialChar = 0; /* Whitespace or {}[]'" */ |
| 1778 | 1795 | int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */ |
| 1779 | 1796 | int nBrace = 0; |
| 1780 | 1797 | |
| 1781 | 1798 | output.zBuf = *pzList; |
| 1782 | - output.nBuf = *pnList; | |
| 1799 | + output.nBuf = TH1_LEN(*pnList); | |
| 1783 | 1800 | output.nBufAlloc = output.nBuf; |
| 1801 | + output.bTaint = 0; | |
| 1802 | + TH1_XFER_TAINT(output.bTaint, *pnList); | |
| 1784 | 1803 | |
| 1785 | 1804 | if( nElem<0 ){ |
| 1786 | 1805 | nElem = th_strlen(zElem); |
| 1806 | + }else{ | |
| 1807 | + nElem = TH1_LEN(nElem); | |
| 1787 | 1808 | } |
| 1788 | 1809 | if( output.nBuf>0 ){ |
| 1789 | 1810 | thBufferAddChar(interp, &output, ' '); |
| 1790 | 1811 | } |
| 1791 | 1812 | |
| @@ -1834,24 +1855,28 @@ | ||
| 1834 | 1855 | int *pnStr, /* IN/OUT: Current length of *pzStr */ |
| 1835 | 1856 | const char *zElem, /* Data to append */ |
| 1836 | 1857 | int nElem /* Length of nElem */ |
| 1837 | 1858 | ){ |
| 1838 | 1859 | char *zNew; |
| 1839 | - int nNew; | |
| 1860 | + long long int nNew; | |
| 1861 | + int nn; | |
| 1840 | 1862 | |
| 1841 | 1863 | if( nElem<0 ){ |
| 1842 | - nElem = th_strlen(zElem); | |
| 1864 | + nn = th_strlen(zElem); | |
| 1865 | + }else{ | |
| 1866 | + nn = TH1_LEN(nElem); | |
| 1843 | 1867 | } |
| 1844 | 1868 | |
| 1845 | - nNew = *pnStr + nElem; | |
| 1869 | + nNew = TH1_LEN(*pnStr) + nn; | |
| 1870 | + TH1_SIZECHECK(nNew); | |
| 1846 | 1871 | zNew = Th_Malloc(interp, nNew); |
| 1847 | 1872 | th_memcpy(zNew, *pzStr, *pnStr); |
| 1848 | - th_memcpy(&zNew[*pnStr], zElem, nElem); | |
| 1873 | + th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn); | |
| 1849 | 1874 | |
| 1850 | 1875 | Th_Free(interp, *pzStr); |
| 1851 | 1876 | *pzStr = zNew; |
| 1852 | - *pnStr = nNew; | |
| 1877 | + *pnStr = (int)nNew; | |
| 1853 | 1878 | |
| 1854 | 1879 | return TH_OK; |
| 1855 | 1880 | } |
| 1856 | 1881 | |
| 1857 | 1882 | /* |
| @@ -2106,16 +2131,18 @@ | ||
| 2106 | 2131 | /* Evaluate left and right arguments, if they exist. */ |
| 2107 | 2132 | if( pExpr->pLeft ){ |
| 2108 | 2133 | rc = exprEval(interp, pExpr->pLeft); |
| 2109 | 2134 | if( rc==TH_OK ){ |
| 2110 | 2135 | zLeft = Th_TakeResult(interp, &nLeft); |
| 2136 | + nLeft = TH1_LEN(nLeft); | |
| 2111 | 2137 | } |
| 2112 | 2138 | } |
| 2113 | 2139 | if( rc==TH_OK && pExpr->pRight ){ |
| 2114 | 2140 | rc = exprEval(interp, pExpr->pRight); |
| 2115 | 2141 | if( rc==TH_OK ){ |
| 2116 | 2142 | zRight = Th_TakeResult(interp, &nRight); |
| 2143 | + nRight = TH1_LEN(nRight); | |
| 2117 | 2144 | } |
| 2118 | 2145 | } |
| 2119 | 2146 | |
| 2120 | 2147 | /* Convert arguments to their required forms. */ |
| 2121 | 2148 | if( rc==TH_OK ){ |
| @@ -2456,10 +2483,12 @@ | ||
| 2456 | 2483 | int nToken = 0; |
| 2457 | 2484 | Expr **apToken = 0; |
| 2458 | 2485 | |
| 2459 | 2486 | if( nExpr<0 ){ |
| 2460 | 2487 | nExpr = th_strlen(zExpr); |
| 2488 | + }else{ | |
| 2489 | + nExpr = TH1_LEN(nExpr); | |
| 2461 | 2490 | } |
| 2462 | 2491 | |
| 2463 | 2492 | /* Parse the expression to a list of tokens. */ |
| 2464 | 2493 | rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); |
| 2465 | 2494 | |
| @@ -2567,10 +2596,12 @@ | ||
| 2567 | 2596 | Th_HashEntry *pRet; |
| 2568 | 2597 | Th_HashEntry **ppRet; |
| 2569 | 2598 | |
| 2570 | 2599 | if( nKey<0 ){ |
| 2571 | 2600 | nKey = th_strlen(zKey); |
| 2601 | + }else{ | |
| 2602 | + nKey = TH1_LEN(nKey); | |
| 2572 | 2603 | } |
| 2573 | 2604 | |
| 2574 | 2605 | for(i=0; i<nKey; i++){ |
| 2575 | 2606 | iKey = (iKey<<3) ^ iKey ^ zKey[i]; |
| 2576 | 2607 | } |
| @@ -2800,10 +2831,12 @@ | ||
| 2800 | 2831 | int base = 10; |
| 2801 | 2832 | int (*isdigit)(char) = th_isdigit; |
| 2802 | 2833 | |
| 2803 | 2834 | if( n<0 ){ |
| 2804 | 2835 | n = th_strlen(z); |
| 2836 | + }else{ | |
| 2837 | + n = TH1_LEN(n); | |
| 2805 | 2838 | } |
| 2806 | 2839 | |
| 2807 | 2840 | if( n>1 && (z[0]=='-' || z[0]=='+') ){ |
| 2808 | 2841 | i = 1; |
| 2809 | 2842 | } |
| @@ -2859,11 +2892,11 @@ | ||
| 2859 | 2892 | const char *z, |
| 2860 | 2893 | int n, |
| 2861 | 2894 | double *pfOut |
| 2862 | 2895 | ){ |
| 2863 | 2896 | if( !sqlite3IsNumber((const char *)z, 0) ){ |
| 2864 | - Th_ErrorMessage(interp, "expected number, got: \"", z, n); | |
| 2897 | + Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n)); | |
| 2865 | 2898 | return TH_ERROR; |
| 2866 | 2899 | } |
| 2867 | 2900 | |
| 2868 | 2901 | sqlite3AtoF((const char *)z, pfOut); |
| 2869 | 2902 | return TH_OK; |
| 2870 | 2903 |
| --- src/th.c | |
| +++ src/th.c | |
| @@ -7,10 +7,16 @@ | |
| 7 | #include "config.h" |
| 8 | #include "th.h" |
| 9 | #include <string.h> |
| 10 | #include <assert.h> |
| 11 | |
| 12 | /* |
| 13 | ** Values used for element values in the tcl_platform array. |
| 14 | */ |
| 15 | |
| 16 | #if !defined(TH_ENGINE) |
| @@ -197,10 +203,11 @@ | |
| 197 | */ |
| 198 | struct Buffer { |
| 199 | char *zBuf; |
| 200 | int nBuf; |
| 201 | int nBufAlloc; |
| 202 | }; |
| 203 | typedef struct Buffer Buffer; |
| 204 | static void thBufferInit(Buffer *); |
| 205 | static void thBufferFree(Th_Interp *interp, Buffer *); |
| 206 | |
| @@ -209,10 +216,18 @@ | |
| 209 | ** be NULL as long as the number of bytes to copy is zero. |
| 210 | */ |
| 211 | static void th_memcpy(void *dest, const void *src, size_t n){ |
| 212 | if( n>0 ) memcpy(dest,src,n); |
| 213 | } |
| 214 | |
| 215 | /* |
| 216 | ** Append nAdd bytes of content copied from zAdd to the end of buffer |
| 217 | ** pBuffer. If there is not enough space currently allocated, resize |
| 218 | ** the allocation to make space. |
| @@ -219,40 +234,46 @@ | |
| 219 | */ |
| 220 | static void thBufferWriteResize( |
| 221 | Th_Interp *interp, |
| 222 | Buffer *pBuffer, |
| 223 | const char *zAdd, |
| 224 | int nAdd |
| 225 | ){ |
| 226 | int nNew = (pBuffer->nBuf+nAdd)*2+32; |
| 227 | #if defined(TH_MEMDEBUG) |
| 228 | char *zNew = (char *)Th_Malloc(interp, nNew); |
| 229 | th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf); |
| 230 | Th_Free(interp, pBuffer->zBuf); |
| 231 | pBuffer->zBuf = zNew; |
| 232 | #else |
| 233 | int nOld = pBuffer->nBufAlloc; |
| 234 | pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew); |
| 235 | memset(pBuffer->zBuf+nOld, 0, nNew-nOld); |
| 236 | #endif |
| 237 | pBuffer->nBufAlloc = nNew; |
| 238 | th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd); |
| 239 | pBuffer->nBuf += nAdd; |
| 240 | } |
| 241 | static void thBufferWriteFast( |
| 242 | Th_Interp *interp, |
| 243 | Buffer *pBuffer, |
| 244 | const char *zAdd, |
| 245 | int nAdd |
| 246 | ){ |
| 247 | if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){ |
| 248 | thBufferWriteResize(interp, pBuffer, zAdd, nAdd); |
| 249 | }else{ |
| 250 | if( pBuffer->zBuf ){ |
| 251 | memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd); |
| 252 | } |
| 253 | pBuffer->nBuf += nAdd; |
| 254 | } |
| 255 | } |
| 256 | #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d) |
| 257 | |
| 258 | /* |
| @@ -704,24 +725,25 @@ | |
| 704 | int nWord |
| 705 | ){ |
| 706 | int rc = TH_OK; |
| 707 | Buffer output; |
| 708 | int i; |
| 709 | |
| 710 | thBufferInit(&output); |
| 711 | |
| 712 | if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){ |
| 713 | thBufferWrite(interp, &output, &zWord[1], nWord-2); |
| 714 | }else{ |
| 715 | |
| 716 | /* If the word is surrounded by double-quotes strip these away. */ |
| 717 | if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){ |
| 718 | zWord++; |
| 719 | nWord -= 2; |
| 720 | } |
| 721 | |
| 722 | for(i=0; rc==TH_OK && i<nWord; i++){ |
| 723 | int nGet; |
| 724 | |
| 725 | int (*xGet)(Th_Interp *, const char*, int, int *) = 0; |
| 726 | int (*xSubst)(Th_Interp *, const char*, int) = 0; |
| 727 | |
| @@ -743,11 +765,11 @@ | |
| 743 | thBufferAddChar(interp, &output, zWord[i]); |
| 744 | continue; /* Go to the next iteration of the for(...) loop */ |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | rc = xGet(interp, &zWord[i], nWord-i, &nGet); |
| 749 | if( rc==TH_OK ){ |
| 750 | rc = xSubst(interp, &zWord[i], nGet); |
| 751 | } |
| 752 | if( rc==TH_OK ){ |
| 753 | const char *zRes; |
| @@ -758,11 +780,11 @@ | |
| 758 | } |
| 759 | } |
| 760 | } |
| 761 | |
| 762 | if( rc==TH_OK ){ |
| 763 | Th_SetResult(interp, output.zBuf, output.nBuf); |
| 764 | } |
| 765 | thBufferFree(interp, &output); |
| 766 | return rc; |
| 767 | } |
| 768 | |
| @@ -826,11 +848,11 @@ | |
| 826 | Buffer strbuf; |
| 827 | Buffer lenbuf; |
| 828 | int nCount = 0; |
| 829 | |
| 830 | const char *zInput = zList; |
| 831 | int nInput = nList; |
| 832 | |
| 833 | thBufferInit(&strbuf); |
| 834 | thBufferInit(&lenbuf); |
| 835 | |
| 836 | while( nInput>0 ){ |
| @@ -837,19 +859,19 @@ | |
| 837 | const char *zWord; |
| 838 | int nWord; |
| 839 | |
| 840 | thNextSpace(interp, zInput, nInput, &nWord); |
| 841 | zInput += nWord; |
| 842 | nInput = nList-(zInput-zList); |
| 843 | |
| 844 | if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0)) |
| 845 | || TH_OK!=(rc = thSubstWord(interp, zInput, nWord)) |
| 846 | ){ |
| 847 | goto finish; |
| 848 | } |
| 849 | zInput = &zInput[nWord]; |
| 850 | nInput = nList-(zInput-zList); |
| 851 | if( nWord>0 ){ |
| 852 | zWord = Th_GetResult(interp, &nWord); |
| 853 | thBufferWrite(interp, &strbuf, zWord, nWord); |
| 854 | thBufferAddChar(interp, &strbuf, 0); |
| 855 | thBufferWrite(interp, &lenbuf, &nWord, sizeof(int)); |
| @@ -872,11 +894,11 @@ | |
| 872 | zElem = (char *)&anElem[nCount]; |
| 873 | th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf); |
| 874 | th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf); |
| 875 | for(i=0; i<nCount;i++){ |
| 876 | azElem[i] = zElem; |
| 877 | zElem += (anElem[i] + 1); |
| 878 | } |
| 879 | *pazElem = azElem; |
| 880 | *panElem = anElem; |
| 881 | } |
| 882 | if( pnCount ){ |
| @@ -894,12 +916,17 @@ | |
| 894 | ** in the current stack frame. |
| 895 | */ |
| 896 | static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){ |
| 897 | int rc = TH_OK; |
| 898 | const char *zInput = zProgram; |
| 899 | int nInput = nProgram; |
| 900 | |
| 901 | while( rc==TH_OK && nInput ){ |
| 902 | Th_HashEntry *pEntry; |
| 903 | int nSpace; |
| 904 | const char *zFirst; |
| 905 | |
| @@ -949,13 +976,13 @@ | |
| 949 | if( rc!=TH_OK ) continue; |
| 950 | |
| 951 | if( argc>0 ){ |
| 952 | |
| 953 | /* Look up the command name in the command hash-table. */ |
| 954 | pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0); |
| 955 | if( !pEntry ){ |
| 956 | Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]); |
| 957 | rc = TH_ERROR; |
| 958 | } |
| 959 | |
| 960 | /* Call the command procedure. */ |
| 961 | if( rc==TH_OK ){ |
| @@ -1053,10 +1080,12 @@ | |
| 1053 | }else{ |
| 1054 | int nInput = nProgram; |
| 1055 | |
| 1056 | if( nInput<0 ){ |
| 1057 | nInput = th_strlen(zProgram); |
| 1058 | } |
| 1059 | rc = thEvalLocal(interp, zProgram, nInput); |
| 1060 | } |
| 1061 | |
| 1062 | interp->pFrame = pSavedFrame; |
| @@ -1095,10 +1124,12 @@ | |
| 1095 | int isGlobal = 0; |
| 1096 | int i; |
| 1097 | |
| 1098 | if( nVarname<0 ){ |
| 1099 | nVarname = th_strlen(zVarname); |
| 1100 | } |
| 1101 | nOuter = nVarname; |
| 1102 | |
| 1103 | /* If the variable name starts with "::", then do the lookup is in the |
| 1104 | ** uppermost (global) frame. |
| @@ -1271,31 +1302,10 @@ | |
| 1271 | } |
| 1272 | |
| 1273 | return Th_SetResult(interp, pValue->zData, pValue->nData); |
| 1274 | } |
| 1275 | |
| 1276 | /* |
| 1277 | ** If interp has a variable with the given name, its value is returned |
| 1278 | ** and its length is returned via *nOut if nOut is not NULL. If |
| 1279 | ** interp has no such var then NULL is returned without setting any |
| 1280 | ** error state and *nOut, if not NULL, is set to -1. The returned value |
| 1281 | ** is owned by the interpreter and may be invalidated the next time |
| 1282 | ** the interpreter is modified. |
| 1283 | */ |
| 1284 | const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, |
| 1285 | int *nOut){ |
| 1286 | Th_Variable *pValue; |
| 1287 | |
| 1288 | pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0); |
| 1289 | if( !pValue || !pValue->zData ){ |
| 1290 | if( nOut!=0 ) *nOut = -1; |
| 1291 | return NULL; |
| 1292 | } |
| 1293 | if( nOut!=0 ) *nOut = pValue->nData; |
| 1294 | return pValue->zData; |
| 1295 | } |
| 1296 | |
| 1297 | /* |
| 1298 | ** Return true if variable (zVar, nVar) exists. |
| 1299 | */ |
| 1300 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1301 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| @@ -1324,28 +1334,32 @@ | |
| 1324 | int nVar, |
| 1325 | const char *zValue, |
| 1326 | int nValue |
| 1327 | ){ |
| 1328 | Th_Variable *pValue; |
| 1329 | |
| 1330 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1331 | if( !pValue ){ |
| 1332 | return TH_ERROR; |
| 1333 | } |
| 1334 | |
| 1335 | if( nValue<0 ){ |
| 1336 | nValue = th_strlen(zValue); |
| 1337 | } |
| 1338 | if( pValue->zData ){ |
| 1339 | Th_Free(interp, pValue->zData); |
| 1340 | pValue->zData = 0; |
| 1341 | } |
| 1342 | |
| 1343 | assert(zValue || nValue==0); |
| 1344 | pValue->zData = Th_Malloc(interp, nValue+1); |
| 1345 | pValue->zData[nValue] = '\0'; |
| 1346 | th_memcpy(pValue->zData, zValue, nValue); |
| 1347 | pValue->nData = nValue; |
| 1348 | |
| 1349 | return TH_OK; |
| 1350 | } |
| 1351 | |
| @@ -1458,10 +1472,12 @@ | |
| 1458 | */ |
| 1459 | char *th_strdup(Th_Interp *interp, const char *z, int n){ |
| 1460 | char *zRes; |
| 1461 | if( n<0 ){ |
| 1462 | n = th_strlen(z); |
| 1463 | } |
| 1464 | zRes = Th_Malloc(interp, n+1); |
| 1465 | th_memcpy(zRes, z, n); |
| 1466 | zRes[n] = '\0'; |
| 1467 | return zRes; |
| @@ -1519,13 +1535,14 @@ | |
| 1519 | n = th_strlen(z); |
| 1520 | } |
| 1521 | |
| 1522 | if( z && n>0 ){ |
| 1523 | char *zResult; |
| 1524 | zResult = Th_Malloc(pInterp, n+1); |
| 1525 | th_memcpy(zResult, z, n); |
| 1526 | zResult[n] = '\0'; |
| 1527 | pInterp->zResult = zResult; |
| 1528 | pInterp->nResult = n; |
| 1529 | } |
| 1530 | |
| 1531 | return TH_OK; |
| @@ -1777,15 +1794,19 @@ | |
| 1777 | int hasSpecialChar = 0; /* Whitespace or {}[]'" */ |
| 1778 | int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */ |
| 1779 | int nBrace = 0; |
| 1780 | |
| 1781 | output.zBuf = *pzList; |
| 1782 | output.nBuf = *pnList; |
| 1783 | output.nBufAlloc = output.nBuf; |
| 1784 | |
| 1785 | if( nElem<0 ){ |
| 1786 | nElem = th_strlen(zElem); |
| 1787 | } |
| 1788 | if( output.nBuf>0 ){ |
| 1789 | thBufferAddChar(interp, &output, ' '); |
| 1790 | } |
| 1791 | |
| @@ -1834,24 +1855,28 @@ | |
| 1834 | int *pnStr, /* IN/OUT: Current length of *pzStr */ |
| 1835 | const char *zElem, /* Data to append */ |
| 1836 | int nElem /* Length of nElem */ |
| 1837 | ){ |
| 1838 | char *zNew; |
| 1839 | int nNew; |
| 1840 | |
| 1841 | if( nElem<0 ){ |
| 1842 | nElem = th_strlen(zElem); |
| 1843 | } |
| 1844 | |
| 1845 | nNew = *pnStr + nElem; |
| 1846 | zNew = Th_Malloc(interp, nNew); |
| 1847 | th_memcpy(zNew, *pzStr, *pnStr); |
| 1848 | th_memcpy(&zNew[*pnStr], zElem, nElem); |
| 1849 | |
| 1850 | Th_Free(interp, *pzStr); |
| 1851 | *pzStr = zNew; |
| 1852 | *pnStr = nNew; |
| 1853 | |
| 1854 | return TH_OK; |
| 1855 | } |
| 1856 | |
| 1857 | /* |
| @@ -2106,16 +2131,18 @@ | |
| 2106 | /* Evaluate left and right arguments, if they exist. */ |
| 2107 | if( pExpr->pLeft ){ |
| 2108 | rc = exprEval(interp, pExpr->pLeft); |
| 2109 | if( rc==TH_OK ){ |
| 2110 | zLeft = Th_TakeResult(interp, &nLeft); |
| 2111 | } |
| 2112 | } |
| 2113 | if( rc==TH_OK && pExpr->pRight ){ |
| 2114 | rc = exprEval(interp, pExpr->pRight); |
| 2115 | if( rc==TH_OK ){ |
| 2116 | zRight = Th_TakeResult(interp, &nRight); |
| 2117 | } |
| 2118 | } |
| 2119 | |
| 2120 | /* Convert arguments to their required forms. */ |
| 2121 | if( rc==TH_OK ){ |
| @@ -2456,10 +2483,12 @@ | |
| 2456 | int nToken = 0; |
| 2457 | Expr **apToken = 0; |
| 2458 | |
| 2459 | if( nExpr<0 ){ |
| 2460 | nExpr = th_strlen(zExpr); |
| 2461 | } |
| 2462 | |
| 2463 | /* Parse the expression to a list of tokens. */ |
| 2464 | rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); |
| 2465 | |
| @@ -2567,10 +2596,12 @@ | |
| 2567 | Th_HashEntry *pRet; |
| 2568 | Th_HashEntry **ppRet; |
| 2569 | |
| 2570 | if( nKey<0 ){ |
| 2571 | nKey = th_strlen(zKey); |
| 2572 | } |
| 2573 | |
| 2574 | for(i=0; i<nKey; i++){ |
| 2575 | iKey = (iKey<<3) ^ iKey ^ zKey[i]; |
| 2576 | } |
| @@ -2800,10 +2831,12 @@ | |
| 2800 | int base = 10; |
| 2801 | int (*isdigit)(char) = th_isdigit; |
| 2802 | |
| 2803 | if( n<0 ){ |
| 2804 | n = th_strlen(z); |
| 2805 | } |
| 2806 | |
| 2807 | if( n>1 && (z[0]=='-' || z[0]=='+') ){ |
| 2808 | i = 1; |
| 2809 | } |
| @@ -2859,11 +2892,11 @@ | |
| 2859 | const char *z, |
| 2860 | int n, |
| 2861 | double *pfOut |
| 2862 | ){ |
| 2863 | if( !sqlite3IsNumber((const char *)z, 0) ){ |
| 2864 | Th_ErrorMessage(interp, "expected number, got: \"", z, n); |
| 2865 | return TH_ERROR; |
| 2866 | } |
| 2867 | |
| 2868 | sqlite3AtoF((const char *)z, pfOut); |
| 2869 | return TH_OK; |
| 2870 |
| --- src/th.c | |
| +++ src/th.c | |
| @@ -7,10 +7,16 @@ | |
| 7 | #include "config.h" |
| 8 | #include "th.h" |
| 9 | #include <string.h> |
| 10 | #include <assert.h> |
| 11 | |
| 12 | /* |
| 13 | ** External routines |
| 14 | */ |
| 15 | void fossil_panic(const char*,...); |
| 16 | void fossil_errorlog(const char*,...); |
| 17 | |
| 18 | /* |
| 19 | ** Values used for element values in the tcl_platform array. |
| 20 | */ |
| 21 | |
| 22 | #if !defined(TH_ENGINE) |
| @@ -197,10 +203,11 @@ | |
| 203 | */ |
| 204 | struct Buffer { |
| 205 | char *zBuf; |
| 206 | int nBuf; |
| 207 | int nBufAlloc; |
| 208 | int bTaint; |
| 209 | }; |
| 210 | typedef struct Buffer Buffer; |
| 211 | static void thBufferInit(Buffer *); |
| 212 | static void thBufferFree(Th_Interp *interp, Buffer *); |
| 213 | |
| @@ -209,10 +216,18 @@ | |
| 216 | ** be NULL as long as the number of bytes to copy is zero. |
| 217 | */ |
| 218 | static void th_memcpy(void *dest, const void *src, size_t n){ |
| 219 | if( n>0 ) memcpy(dest,src,n); |
| 220 | } |
| 221 | |
| 222 | /* |
| 223 | ** An oversized string has been encountered. Do not try to recover. |
| 224 | ** Panic the process. |
| 225 | */ |
| 226 | void Th_OversizeString(void){ |
| 227 | fossil_panic("string too large. maximum size 286MB."); |
| 228 | } |
| 229 | |
| 230 | /* |
| 231 | ** Append nAdd bytes of content copied from zAdd to the end of buffer |
| 232 | ** pBuffer. If there is not enough space currently allocated, resize |
| 233 | ** the allocation to make space. |
| @@ -219,40 +234,46 @@ | |
| 234 | */ |
| 235 | static void thBufferWriteResize( |
| 236 | Th_Interp *interp, |
| 237 | Buffer *pBuffer, |
| 238 | const char *zAdd, |
| 239 | int nAddX |
| 240 | ){ |
| 241 | int nAdd = TH1_LEN(nAddX); |
| 242 | int nNew = (pBuffer->nBuf+nAdd)*2+32; |
| 243 | #if defined(TH_MEMDEBUG) |
| 244 | char *zNew = (char *)Th_Malloc(interp, nNew); |
| 245 | TH1_SIZECHECK(nNew); |
| 246 | th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf); |
| 247 | Th_Free(interp, pBuffer->zBuf); |
| 248 | pBuffer->zBuf = zNew; |
| 249 | #else |
| 250 | int nOld = pBuffer->nBufAlloc; |
| 251 | TH1_SIZECHECK(nNew); |
| 252 | pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew); |
| 253 | memset(pBuffer->zBuf+nOld, 0, nNew-nOld); |
| 254 | #endif |
| 255 | pBuffer->nBufAlloc = nNew; |
| 256 | th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd); |
| 257 | pBuffer->nBuf += nAdd; |
| 258 | TH1_XFER_TAINT(pBuffer->bTaint, nAddX); |
| 259 | } |
| 260 | static void thBufferWriteFast( |
| 261 | Th_Interp *interp, |
| 262 | Buffer *pBuffer, |
| 263 | const char *zAdd, |
| 264 | int nAddX |
| 265 | ){ |
| 266 | int nAdd = TH1_LEN(nAddX); |
| 267 | if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){ |
| 268 | thBufferWriteResize(interp, pBuffer, zAdd, nAddX); |
| 269 | }else{ |
| 270 | if( pBuffer->zBuf ){ |
| 271 | memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd); |
| 272 | } |
| 273 | pBuffer->nBuf += nAdd; |
| 274 | TH1_XFER_TAINT(pBuffer->bTaint, nAddX); |
| 275 | } |
| 276 | } |
| 277 | #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d) |
| 278 | |
| 279 | /* |
| @@ -704,24 +725,25 @@ | |
| 725 | int nWord |
| 726 | ){ |
| 727 | int rc = TH_OK; |
| 728 | Buffer output; |
| 729 | int i; |
| 730 | int nn = TH1_LEN(nWord); |
| 731 | |
| 732 | thBufferInit(&output); |
| 733 | |
| 734 | if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){ |
| 735 | thBufferWrite(interp, &output, &zWord[1], nn-2); |
| 736 | }else{ |
| 737 | |
| 738 | /* If the word is surrounded by double-quotes strip these away. */ |
| 739 | if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){ |
| 740 | zWord++; |
| 741 | nn -= 2; |
| 742 | } |
| 743 | |
| 744 | for(i=0; rc==TH_OK && i<nn; i++){ |
| 745 | int nGet; |
| 746 | |
| 747 | int (*xGet)(Th_Interp *, const char*, int, int *) = 0; |
| 748 | int (*xSubst)(Th_Interp *, const char*, int) = 0; |
| 749 | |
| @@ -743,11 +765,11 @@ | |
| 765 | thBufferAddChar(interp, &output, zWord[i]); |
| 766 | continue; /* Go to the next iteration of the for(...) loop */ |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | rc = xGet(interp, &zWord[i], nn-i, &nGet); |
| 771 | if( rc==TH_OK ){ |
| 772 | rc = xSubst(interp, &zWord[i], nGet); |
| 773 | } |
| 774 | if( rc==TH_OK ){ |
| 775 | const char *zRes; |
| @@ -758,11 +780,11 @@ | |
| 780 | } |
| 781 | } |
| 782 | } |
| 783 | |
| 784 | if( rc==TH_OK ){ |
| 785 | Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint); |
| 786 | } |
| 787 | thBufferFree(interp, &output); |
| 788 | return rc; |
| 789 | } |
| 790 | |
| @@ -826,11 +848,11 @@ | |
| 848 | Buffer strbuf; |
| 849 | Buffer lenbuf; |
| 850 | int nCount = 0; |
| 851 | |
| 852 | const char *zInput = zList; |
| 853 | int nInput = TH1_LEN(nList); |
| 854 | |
| 855 | thBufferInit(&strbuf); |
| 856 | thBufferInit(&lenbuf); |
| 857 | |
| 858 | while( nInput>0 ){ |
| @@ -837,19 +859,19 @@ | |
| 859 | const char *zWord; |
| 860 | int nWord; |
| 861 | |
| 862 | thNextSpace(interp, zInput, nInput, &nWord); |
| 863 | zInput += nWord; |
| 864 | nInput = TH1_LEN(nList)-(zInput-zList); |
| 865 | |
| 866 | if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0)) |
| 867 | || TH_OK!=(rc = thSubstWord(interp, zInput, nWord)) |
| 868 | ){ |
| 869 | goto finish; |
| 870 | } |
| 871 | zInput = &zInput[TH1_LEN(nWord)]; |
| 872 | nInput = TH1_LEN(nList)-(zInput-zList); |
| 873 | if( nWord>0 ){ |
| 874 | zWord = Th_GetResult(interp, &nWord); |
| 875 | thBufferWrite(interp, &strbuf, zWord, nWord); |
| 876 | thBufferAddChar(interp, &strbuf, 0); |
| 877 | thBufferWrite(interp, &lenbuf, &nWord, sizeof(int)); |
| @@ -872,11 +894,11 @@ | |
| 894 | zElem = (char *)&anElem[nCount]; |
| 895 | th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf); |
| 896 | th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf); |
| 897 | for(i=0; i<nCount;i++){ |
| 898 | azElem[i] = zElem; |
| 899 | zElem += (TH1_LEN(anElem[i]) + 1); |
| 900 | } |
| 901 | *pazElem = azElem; |
| 902 | *panElem = anElem; |
| 903 | } |
| 904 | if( pnCount ){ |
| @@ -894,12 +916,17 @@ | |
| 916 | ** in the current stack frame. |
| 917 | */ |
| 918 | static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){ |
| 919 | int rc = TH_OK; |
| 920 | const char *zInput = zProgram; |
| 921 | int nInput = TH1_LEN(nProgram); |
| 922 | |
| 923 | if( TH1_TAINTED(nProgram) |
| 924 | && Th_ReportTaint(interp, "script", zProgram, nProgram) |
| 925 | ){ |
| 926 | return TH_ERROR; |
| 927 | } |
| 928 | while( rc==TH_OK && nInput ){ |
| 929 | Th_HashEntry *pEntry; |
| 930 | int nSpace; |
| 931 | const char *zFirst; |
| 932 | |
| @@ -949,13 +976,13 @@ | |
| 976 | if( rc!=TH_OK ) continue; |
| 977 | |
| 978 | if( argc>0 ){ |
| 979 | |
| 980 | /* Look up the command name in the command hash-table. */ |
| 981 | pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0); |
| 982 | if( !pEntry ){ |
| 983 | Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0])); |
| 984 | rc = TH_ERROR; |
| 985 | } |
| 986 | |
| 987 | /* Call the command procedure. */ |
| 988 | if( rc==TH_OK ){ |
| @@ -1053,10 +1080,12 @@ | |
| 1080 | }else{ |
| 1081 | int nInput = nProgram; |
| 1082 | |
| 1083 | if( nInput<0 ){ |
| 1084 | nInput = th_strlen(zProgram); |
| 1085 | }else{ |
| 1086 | nInput = TH1_LEN(nInput); |
| 1087 | } |
| 1088 | rc = thEvalLocal(interp, zProgram, nInput); |
| 1089 | } |
| 1090 | |
| 1091 | interp->pFrame = pSavedFrame; |
| @@ -1095,10 +1124,12 @@ | |
| 1124 | int isGlobal = 0; |
| 1125 | int i; |
| 1126 | |
| 1127 | if( nVarname<0 ){ |
| 1128 | nVarname = th_strlen(zVarname); |
| 1129 | }else{ |
| 1130 | nVarname = TH1_LEN(nVarname); |
| 1131 | } |
| 1132 | nOuter = nVarname; |
| 1133 | |
| 1134 | /* If the variable name starts with "::", then do the lookup is in the |
| 1135 | ** uppermost (global) frame. |
| @@ -1271,31 +1302,10 @@ | |
| 1302 | } |
| 1303 | |
| 1304 | return Th_SetResult(interp, pValue->zData, pValue->nData); |
| 1305 | } |
| 1306 | |
| 1307 | /* |
| 1308 | ** Return true if variable (zVar, nVar) exists. |
| 1309 | */ |
| 1310 | int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ |
| 1311 | Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); |
| @@ -1324,28 +1334,32 @@ | |
| 1334 | int nVar, |
| 1335 | const char *zValue, |
| 1336 | int nValue |
| 1337 | ){ |
| 1338 | Th_Variable *pValue; |
| 1339 | int nn; |
| 1340 | |
| 1341 | nVar = TH1_LEN(nVar); |
| 1342 | pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); |
| 1343 | if( !pValue ){ |
| 1344 | return TH_ERROR; |
| 1345 | } |
| 1346 | |
| 1347 | if( nValue<0 ){ |
| 1348 | nn = th_strlen(zValue); |
| 1349 | }else{ |
| 1350 | nn = TH1_LEN(nValue); |
| 1351 | } |
| 1352 | if( pValue->zData ){ |
| 1353 | Th_Free(interp, pValue->zData); |
| 1354 | pValue->zData = 0; |
| 1355 | } |
| 1356 | |
| 1357 | assert(zValue || nn==0); |
| 1358 | pValue->zData = Th_Malloc(interp, nn+1); |
| 1359 | pValue->zData[nn] = '\0'; |
| 1360 | th_memcpy(pValue->zData, zValue, nn); |
| 1361 | pValue->nData = nValue; |
| 1362 | |
| 1363 | return TH_OK; |
| 1364 | } |
| 1365 | |
| @@ -1458,10 +1472,12 @@ | |
| 1472 | */ |
| 1473 | char *th_strdup(Th_Interp *interp, const char *z, int n){ |
| 1474 | char *zRes; |
| 1475 | if( n<0 ){ |
| 1476 | n = th_strlen(z); |
| 1477 | }else{ |
| 1478 | n = TH1_LEN(n); |
| 1479 | } |
| 1480 | zRes = Th_Malloc(interp, n+1); |
| 1481 | th_memcpy(zRes, z, n); |
| 1482 | zRes[n] = '\0'; |
| 1483 | return zRes; |
| @@ -1519,13 +1535,14 @@ | |
| 1535 | n = th_strlen(z); |
| 1536 | } |
| 1537 | |
| 1538 | if( z && n>0 ){ |
| 1539 | char *zResult; |
| 1540 | int nn = TH1_LEN(n); |
| 1541 | zResult = Th_Malloc(pInterp, nn+1); |
| 1542 | th_memcpy(zResult, z, nn); |
| 1543 | zResult[nn] = '\0'; |
| 1544 | pInterp->zResult = zResult; |
| 1545 | pInterp->nResult = n; |
| 1546 | } |
| 1547 | |
| 1548 | return TH_OK; |
| @@ -1777,15 +1794,19 @@ | |
| 1794 | int hasSpecialChar = 0; /* Whitespace or {}[]'" */ |
| 1795 | int hasEscapeChar = 0; /* '}' without matching '{' to the left or a '\\' */ |
| 1796 | int nBrace = 0; |
| 1797 | |
| 1798 | output.zBuf = *pzList; |
| 1799 | output.nBuf = TH1_LEN(*pnList); |
| 1800 | output.nBufAlloc = output.nBuf; |
| 1801 | output.bTaint = 0; |
| 1802 | TH1_XFER_TAINT(output.bTaint, *pnList); |
| 1803 | |
| 1804 | if( nElem<0 ){ |
| 1805 | nElem = th_strlen(zElem); |
| 1806 | }else{ |
| 1807 | nElem = TH1_LEN(nElem); |
| 1808 | } |
| 1809 | if( output.nBuf>0 ){ |
| 1810 | thBufferAddChar(interp, &output, ' '); |
| 1811 | } |
| 1812 | |
| @@ -1834,24 +1855,28 @@ | |
| 1855 | int *pnStr, /* IN/OUT: Current length of *pzStr */ |
| 1856 | const char *zElem, /* Data to append */ |
| 1857 | int nElem /* Length of nElem */ |
| 1858 | ){ |
| 1859 | char *zNew; |
| 1860 | long long int nNew; |
| 1861 | int nn; |
| 1862 | |
| 1863 | if( nElem<0 ){ |
| 1864 | nn = th_strlen(zElem); |
| 1865 | }else{ |
| 1866 | nn = TH1_LEN(nElem); |
| 1867 | } |
| 1868 | |
| 1869 | nNew = TH1_LEN(*pnStr) + nn; |
| 1870 | TH1_SIZECHECK(nNew); |
| 1871 | zNew = Th_Malloc(interp, nNew); |
| 1872 | th_memcpy(zNew, *pzStr, *pnStr); |
| 1873 | th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn); |
| 1874 | |
| 1875 | Th_Free(interp, *pzStr); |
| 1876 | *pzStr = zNew; |
| 1877 | *pnStr = (int)nNew; |
| 1878 | |
| 1879 | return TH_OK; |
| 1880 | } |
| 1881 | |
| 1882 | /* |
| @@ -2106,16 +2131,18 @@ | |
| 2131 | /* Evaluate left and right arguments, if they exist. */ |
| 2132 | if( pExpr->pLeft ){ |
| 2133 | rc = exprEval(interp, pExpr->pLeft); |
| 2134 | if( rc==TH_OK ){ |
| 2135 | zLeft = Th_TakeResult(interp, &nLeft); |
| 2136 | nLeft = TH1_LEN(nLeft); |
| 2137 | } |
| 2138 | } |
| 2139 | if( rc==TH_OK && pExpr->pRight ){ |
| 2140 | rc = exprEval(interp, pExpr->pRight); |
| 2141 | if( rc==TH_OK ){ |
| 2142 | zRight = Th_TakeResult(interp, &nRight); |
| 2143 | nRight = TH1_LEN(nRight); |
| 2144 | } |
| 2145 | } |
| 2146 | |
| 2147 | /* Convert arguments to their required forms. */ |
| 2148 | if( rc==TH_OK ){ |
| @@ -2456,10 +2483,12 @@ | |
| 2483 | int nToken = 0; |
| 2484 | Expr **apToken = 0; |
| 2485 | |
| 2486 | if( nExpr<0 ){ |
| 2487 | nExpr = th_strlen(zExpr); |
| 2488 | }else{ |
| 2489 | nExpr = TH1_LEN(nExpr); |
| 2490 | } |
| 2491 | |
| 2492 | /* Parse the expression to a list of tokens. */ |
| 2493 | rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); |
| 2494 | |
| @@ -2567,10 +2596,12 @@ | |
| 2596 | Th_HashEntry *pRet; |
| 2597 | Th_HashEntry **ppRet; |
| 2598 | |
| 2599 | if( nKey<0 ){ |
| 2600 | nKey = th_strlen(zKey); |
| 2601 | }else{ |
| 2602 | nKey = TH1_LEN(nKey); |
| 2603 | } |
| 2604 | |
| 2605 | for(i=0; i<nKey; i++){ |
| 2606 | iKey = (iKey<<3) ^ iKey ^ zKey[i]; |
| 2607 | } |
| @@ -2800,10 +2831,12 @@ | |
| 2831 | int base = 10; |
| 2832 | int (*isdigit)(char) = th_isdigit; |
| 2833 | |
| 2834 | if( n<0 ){ |
| 2835 | n = th_strlen(z); |
| 2836 | }else{ |
| 2837 | n = TH1_LEN(n); |
| 2838 | } |
| 2839 | |
| 2840 | if( n>1 && (z[0]=='-' || z[0]=='+') ){ |
| 2841 | i = 1; |
| 2842 | } |
| @@ -2859,11 +2892,11 @@ | |
| 2892 | const char *z, |
| 2893 | int n, |
| 2894 | double *pfOut |
| 2895 | ){ |
| 2896 | if( !sqlite3IsNumber((const char *)z, 0) ){ |
| 2897 | Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n)); |
| 2898 | return TH_ERROR; |
| 2899 | } |
| 2900 | |
| 2901 | sqlite3AtoF((const char *)z, pfOut); |
| 2902 | return TH_OK; |
| 2903 |
M
src/th.h
+53
-14
| --- src/th.h | ||
| +++ src/th.h | ||
| @@ -1,10 +1,56 @@ | ||
| 1 | - | |
| 2 | 1 | /* This header file defines the external interface to the custom Scripting |
| 3 | 2 | ** Language (TH) interpreter. TH is very similar to Tcl but is not an |
| 4 | 3 | ** exact clone. |
| 4 | +** | |
| 5 | +** TH1 was original developed to run SQLite tests on SymbianOS. This version | |
| 6 | +** of TH1 was repurposed as a scripted language for Fossil, and was heavily | |
| 7 | +** modified for that purpose, beginning in early 2008. | |
| 8 | +** | |
| 9 | +** More recently, TH1 has been enhanced to distinguish between regular text | |
| 10 | +** and "tainted" text. "Tainted" text is text that might have originated | |
| 11 | +** from an outside source and hence might not be trustworthy. To prevent | |
| 12 | +** cross-site scripting (XSS) and SQL-injections and similar attacks, | |
| 13 | +** tainted text should not be used for the following purposes: | |
| 14 | +** | |
| 15 | +** * executed as TH1 script or expression. | |
| 16 | +** * output as HTML or Javascript | |
| 17 | +** * used as part of an SQL query | |
| 18 | +** | |
| 19 | +** Tainted text can be converted into a safe form using commands like | |
| 20 | +** "htmlize". And some commands ("query" and "expr") know how to use | |
| 21 | +** potentially tainted variable values directly, and thus can bypass | |
| 22 | +** the restrictions above. | |
| 23 | +** | |
| 24 | +** Whether a string is clean or tainted is determined by its length integer. | |
| 25 | +** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length | |
| 26 | +** (about 268MB - more than sufficient for the purposes of Fossil). The top | |
| 27 | +** bit of the length integer is the sign bit, of course. The next three bits | |
| 28 | +** are reserved. One of those, the 0x10000000 bit, marks tainted strings. | |
| 5 | 29 | */ |
| 30 | +#define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */ | |
| 31 | +#define TH1_TAINT_BIT 0x10000000 /* The taint bit */ | |
| 32 | +#define TH1_SIGN 0x80000000 | |
| 33 | + | |
| 34 | +/* Convert an integer into a string length. Negative values remain negative */ | |
| 35 | +#define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X)) | |
| 36 | + | |
| 37 | +/* Return true if the string is tainted */ | |
| 38 | +#define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0) | |
| 39 | + | |
| 40 | +/* Remove taint from a string */ | |
| 41 | +#define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT) | |
| 42 | + | |
| 43 | +/* Add taint to a string */ | |
| 44 | +#define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT) | |
| 45 | + | |
| 46 | +/* If B is tainted, make A tainted too */ | |
| 47 | +#define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B)) | |
| 48 | + | |
| 49 | +/* Check to see if a string is too big for TH1 */ | |
| 50 | +#define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();} | |
| 51 | +void Th_OversizeString(void); | |
| 6 | 52 | |
| 7 | 53 | /* |
| 8 | 54 | ** Before creating an interpreter, the application must allocate and |
| 9 | 55 | ** populate an instance of the following structure. It must remain valid |
| 10 | 56 | ** for the lifetime of the interpreter. |
| @@ -24,10 +70,16 @@ | ||
| 24 | 70 | ** Create and delete interpreters. |
| 25 | 71 | */ |
| 26 | 72 | Th_Interp * Th_CreateInterp(Th_Vtab *); |
| 27 | 73 | void Th_DeleteInterp(Th_Interp *); |
| 28 | 74 | |
| 75 | +/* | |
| 76 | +** Report taint in the string zStr,nStr. That string represents "zTitle" | |
| 77 | +** If non-zero is returned error out of the caller. | |
| 78 | +*/ | |
| 79 | +int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr); | |
| 80 | + | |
| 29 | 81 | /* |
| 30 | 82 | ** Evaluate an TH program in the stack frame identified by parameter |
| 31 | 83 | ** iFrame, according to the following rules: |
| 32 | 84 | ** |
| 33 | 85 | ** * If iFrame is 0, this means the current frame. |
| @@ -56,23 +108,10 @@ | ||
| 56 | 108 | int Th_GetVar(Th_Interp *, const char *, int); |
| 57 | 109 | int Th_SetVar(Th_Interp *, const char *, int, const char *, int); |
| 58 | 110 | int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); |
| 59 | 111 | int Th_UnsetVar(Th_Interp *, const char *, int); |
| 60 | 112 | |
| 61 | -/* | |
| 62 | -** If interp has a variable with the given name, its value is returned | |
| 63 | -** and its length is returned via *nOut if nOut is not NULL. If | |
| 64 | -** interp has no such var then NULL is returned without setting any | |
| 65 | -** error state and *nOut, if not NULL, is set to 0. The returned value | |
| 66 | -** is owned by the interpreter and may be invalidated the next time | |
| 67 | -** the interpreter is modified. | |
| 68 | -** | |
| 69 | -** zVarName must be NUL-terminated. | |
| 70 | -*/ | |
| 71 | -const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, | |
| 72 | - int *nOut); | |
| 73 | - | |
| 74 | 113 | typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); |
| 75 | 114 | |
| 76 | 115 | /* |
| 77 | 116 | ** Register new commands. |
| 78 | 117 | */ |
| 79 | 118 |
| --- src/th.h | |
| +++ src/th.h | |
| @@ -1,10 +1,56 @@ | |
| 1 | |
| 2 | /* This header file defines the external interface to the custom Scripting |
| 3 | ** Language (TH) interpreter. TH is very similar to Tcl but is not an |
| 4 | ** exact clone. |
| 5 | */ |
| 6 | |
| 7 | /* |
| 8 | ** Before creating an interpreter, the application must allocate and |
| 9 | ** populate an instance of the following structure. It must remain valid |
| 10 | ** for the lifetime of the interpreter. |
| @@ -24,10 +70,16 @@ | |
| 24 | ** Create and delete interpreters. |
| 25 | */ |
| 26 | Th_Interp * Th_CreateInterp(Th_Vtab *); |
| 27 | void Th_DeleteInterp(Th_Interp *); |
| 28 | |
| 29 | /* |
| 30 | ** Evaluate an TH program in the stack frame identified by parameter |
| 31 | ** iFrame, according to the following rules: |
| 32 | ** |
| 33 | ** * If iFrame is 0, this means the current frame. |
| @@ -56,23 +108,10 @@ | |
| 56 | int Th_GetVar(Th_Interp *, const char *, int); |
| 57 | int Th_SetVar(Th_Interp *, const char *, int, const char *, int); |
| 58 | int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); |
| 59 | int Th_UnsetVar(Th_Interp *, const char *, int); |
| 60 | |
| 61 | /* |
| 62 | ** If interp has a variable with the given name, its value is returned |
| 63 | ** and its length is returned via *nOut if nOut is not NULL. If |
| 64 | ** interp has no such var then NULL is returned without setting any |
| 65 | ** error state and *nOut, if not NULL, is set to 0. The returned value |
| 66 | ** is owned by the interpreter and may be invalidated the next time |
| 67 | ** the interpreter is modified. |
| 68 | ** |
| 69 | ** zVarName must be NUL-terminated. |
| 70 | */ |
| 71 | const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, |
| 72 | int *nOut); |
| 73 | |
| 74 | typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); |
| 75 | |
| 76 | /* |
| 77 | ** Register new commands. |
| 78 | */ |
| 79 |
| --- src/th.h | |
| +++ src/th.h | |
| @@ -1,10 +1,56 @@ | |
| 1 | /* This header file defines the external interface to the custom Scripting |
| 2 | ** Language (TH) interpreter. TH is very similar to Tcl but is not an |
| 3 | ** exact clone. |
| 4 | ** |
| 5 | ** TH1 was original developed to run SQLite tests on SymbianOS. This version |
| 6 | ** of TH1 was repurposed as a scripted language for Fossil, and was heavily |
| 7 | ** modified for that purpose, beginning in early 2008. |
| 8 | ** |
| 9 | ** More recently, TH1 has been enhanced to distinguish between regular text |
| 10 | ** and "tainted" text. "Tainted" text is text that might have originated |
| 11 | ** from an outside source and hence might not be trustworthy. To prevent |
| 12 | ** cross-site scripting (XSS) and SQL-injections and similar attacks, |
| 13 | ** tainted text should not be used for the following purposes: |
| 14 | ** |
| 15 | ** * executed as TH1 script or expression. |
| 16 | ** * output as HTML or Javascript |
| 17 | ** * used as part of an SQL query |
| 18 | ** |
| 19 | ** Tainted text can be converted into a safe form using commands like |
| 20 | ** "htmlize". And some commands ("query" and "expr") know how to use |
| 21 | ** potentially tainted variable values directly, and thus can bypass |
| 22 | ** the restrictions above. |
| 23 | ** |
| 24 | ** Whether a string is clean or tainted is determined by its length integer. |
| 25 | ** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length |
| 26 | ** (about 268MB - more than sufficient for the purposes of Fossil). The top |
| 27 | ** bit of the length integer is the sign bit, of course. The next three bits |
| 28 | ** are reserved. One of those, the 0x10000000 bit, marks tainted strings. |
| 29 | */ |
| 30 | #define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */ |
| 31 | #define TH1_TAINT_BIT 0x10000000 /* The taint bit */ |
| 32 | #define TH1_SIGN 0x80000000 |
| 33 | |
| 34 | /* Convert an integer into a string length. Negative values remain negative */ |
| 35 | #define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X)) |
| 36 | |
| 37 | /* Return true if the string is tainted */ |
| 38 | #define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0) |
| 39 | |
| 40 | /* Remove taint from a string */ |
| 41 | #define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT) |
| 42 | |
| 43 | /* Add taint to a string */ |
| 44 | #define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT) |
| 45 | |
| 46 | /* If B is tainted, make A tainted too */ |
| 47 | #define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B)) |
| 48 | |
| 49 | /* Check to see if a string is too big for TH1 */ |
| 50 | #define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();} |
| 51 | void Th_OversizeString(void); |
| 52 | |
| 53 | /* |
| 54 | ** Before creating an interpreter, the application must allocate and |
| 55 | ** populate an instance of the following structure. It must remain valid |
| 56 | ** for the lifetime of the interpreter. |
| @@ -24,10 +70,16 @@ | |
| 70 | ** Create and delete interpreters. |
| 71 | */ |
| 72 | Th_Interp * Th_CreateInterp(Th_Vtab *); |
| 73 | void Th_DeleteInterp(Th_Interp *); |
| 74 | |
| 75 | /* |
| 76 | ** Report taint in the string zStr,nStr. That string represents "zTitle" |
| 77 | ** If non-zero is returned error out of the caller. |
| 78 | */ |
| 79 | int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr); |
| 80 | |
| 81 | /* |
| 82 | ** Evaluate an TH program in the stack frame identified by parameter |
| 83 | ** iFrame, according to the following rules: |
| 84 | ** |
| 85 | ** * If iFrame is 0, this means the current frame. |
| @@ -56,23 +108,10 @@ | |
| 108 | int Th_GetVar(Th_Interp *, const char *, int); |
| 109 | int Th_SetVar(Th_Interp *, const char *, int, const char *, int); |
| 110 | int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); |
| 111 | int Th_UnsetVar(Th_Interp *, const char *, int); |
| 112 | |
| 113 | typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); |
| 114 | |
| 115 | /* |
| 116 | ** Register new commands. |
| 117 | */ |
| 118 |
+92
-53
| --- src/th_lang.c | ||
| +++ src/th_lang.c | ||
| @@ -39,11 +39,11 @@ | ||
| 39 | 39 | |
| 40 | 40 | rc = Th_Eval(interp, 0, argv[1], -1); |
| 41 | 41 | if( argc==3 ){ |
| 42 | 42 | int nResult; |
| 43 | 43 | const char *zResult = Th_GetResult(interp, &nResult); |
| 44 | - Th_SetVar(interp, argv[2], argl[2], zResult, nResult); | |
| 44 | + Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult); | |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | Th_SetResultInt(interp, rc); |
| 48 | 48 | return TH_OK; |
| 49 | 49 | } |
| @@ -180,20 +180,24 @@ | ||
| 180 | 180 | int nVar; |
| 181 | 181 | char **azValue = 0; |
| 182 | 182 | int *anValue; |
| 183 | 183 | int nValue; |
| 184 | 184 | int ii, jj; |
| 185 | + int bTaint = 0; | |
| 185 | 186 | |
| 186 | 187 | if( argc!=4 ){ |
| 187 | 188 | return Th_WrongNumArgs(interp, "foreach varlist list script"); |
| 188 | 189 | } |
| 189 | 190 | rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar); |
| 190 | 191 | if( rc ) return rc; |
| 192 | + TH1_XFER_TAINT(bTaint, argl[2]); | |
| 191 | 193 | rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue); |
| 192 | 194 | for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){ |
| 193 | 195 | for(jj=0; jj<nVar; jj++){ |
| 194 | - Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]); | |
| 196 | + int x = anValue[ii+jj]; | |
| 197 | + TH1_XFER_TAINT(x, bTaint); | |
| 198 | + Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x); | |
| 195 | 199 | } |
| 196 | 200 | rc = eval_loopbody(interp, argv[3], argl[3]); |
| 197 | 201 | } |
| 198 | 202 | if( rc==TH_BREAK ) rc = TH_OK; |
| 199 | 203 | Th_Free(interp, azVar); |
| @@ -215,15 +219,18 @@ | ||
| 215 | 219 | int *argl |
| 216 | 220 | ){ |
| 217 | 221 | char *zList = 0; |
| 218 | 222 | int nList = 0; |
| 219 | 223 | int i; |
| 224 | + int bTaint = 0; | |
| 220 | 225 | |
| 221 | 226 | for(i=1; i<argc; i++){ |
| 227 | + TH1_XFER_TAINT(bTaint,argl[i]); | |
| 222 | 228 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 223 | 229 | } |
| 224 | 230 | |
| 231 | + TH1_XFER_TAINT(nList, bTaint); | |
| 225 | 232 | Th_SetResult(interp, zList, nList); |
| 226 | 233 | Th_Free(interp, zList); |
| 227 | 234 | |
| 228 | 235 | return TH_OK; |
| 229 | 236 | } |
| @@ -244,23 +251,27 @@ | ||
| 244 | 251 | int *argl |
| 245 | 252 | ){ |
| 246 | 253 | char *zList = 0; |
| 247 | 254 | int nList = 0; |
| 248 | 255 | int i, rc; |
| 256 | + int bTaint = 0; | |
| 249 | 257 | |
| 250 | 258 | if( argc<2 ){ |
| 251 | 259 | return Th_WrongNumArgs(interp, "lappend var ..."); |
| 252 | 260 | } |
| 253 | 261 | rc = Th_GetVar(interp, argv[1], argl[1]); |
| 254 | 262 | if( rc==TH_OK ){ |
| 255 | 263 | zList = Th_TakeResult(interp, &nList); |
| 256 | 264 | } |
| 257 | 265 | |
| 266 | + TH1_XFER_TAINT(bTaint, nList); | |
| 258 | 267 | for(i=2; i<argc; i++){ |
| 268 | + TH1_XFER_TAINT(bTaint, argl[i]); | |
| 259 | 269 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 260 | 270 | } |
| 261 | 271 | |
| 272 | + TH1_XFER_TAINT(nList, bTaint); | |
| 262 | 273 | Th_SetVar(interp, argv[1], argl[1], zList, nList); |
| 263 | 274 | Th_SetResult(interp, zList, nList); |
| 264 | 275 | Th_Free(interp, zList); |
| 265 | 276 | |
| 266 | 277 | return TH_OK; |
| @@ -283,23 +294,27 @@ | ||
| 283 | 294 | int rc; |
| 284 | 295 | |
| 285 | 296 | char **azElem; |
| 286 | 297 | int *anElem; |
| 287 | 298 | int nCount; |
| 299 | + int bTaint = 0; | |
| 288 | 300 | |
| 289 | 301 | if( argc!=3 ){ |
| 290 | 302 | return Th_WrongNumArgs(interp, "lindex list index"); |
| 291 | 303 | } |
| 292 | 304 | |
| 293 | 305 | if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){ |
| 294 | 306 | return TH_ERROR; |
| 295 | 307 | } |
| 296 | 308 | |
| 309 | + TH1_XFER_TAINT(bTaint, argl[1]); | |
| 297 | 310 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 298 | 311 | if( rc==TH_OK ){ |
| 299 | 312 | if( iElem<nCount && iElem>=0 ){ |
| 300 | - Th_SetResult(interp, azElem[iElem], anElem[iElem]); | |
| 313 | + int sz = anElem[iElem]; | |
| 314 | + TH1_XFER_TAINT(sz, bTaint); | |
| 315 | + Th_SetResult(interp, azElem[iElem], sz); | |
| 301 | 316 | }else{ |
| 302 | 317 | Th_SetResult(interp, 0, 0); |
| 303 | 318 | } |
| 304 | 319 | Th_Free(interp, azElem); |
| 305 | 320 | } |
| @@ -356,13 +371,14 @@ | ||
| 356 | 371 | return Th_WrongNumArgs(interp, "lsearch list string"); |
| 357 | 372 | } |
| 358 | 373 | |
| 359 | 374 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 360 | 375 | if( rc==TH_OK ){ |
| 376 | + int nn = TH1_LEN(argl[2]); | |
| 361 | 377 | Th_SetResultInt(interp, -1); |
| 362 | 378 | for(i=0; i<nCount; i++){ |
| 363 | - if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){ | |
| 379 | + if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){ | |
| 364 | 380 | Th_SetResultInt(interp, i); |
| 365 | 381 | break; |
| 366 | 382 | } |
| 367 | 383 | } |
| 368 | 384 | Th_Free(interp, azElem); |
| @@ -561,28 +577,31 @@ | ||
| 561 | 577 | int nUsage = 0; /* Number of bytes at zUsage */ |
| 562 | 578 | |
| 563 | 579 | if( argc!=4 ){ |
| 564 | 580 | return Th_WrongNumArgs(interp, "proc name arglist code"); |
| 565 | 581 | } |
| 566 | - if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){ | |
| 582 | + if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), | |
| 583 | + &azParam, &anParam, &nParam) ){ | |
| 567 | 584 | return TH_ERROR; |
| 568 | 585 | } |
| 569 | 586 | |
| 570 | 587 | /* Allocate the new ProcDefn structure. */ |
| 571 | 588 | nByte = sizeof(ProcDefn) + /* ProcDefn structure */ |
| 572 | 589 | (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ |
| 573 | 590 | (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ |
| 574 | - argl[3] + /* zProgram */ | |
| 575 | - argl[2]; /* Space for copies of parameter names and default values */ | |
| 591 | + TH1_LEN(argl[3]) + /* zProgram */ | |
| 592 | + TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */ | |
| 576 | 593 | p = (ProcDefn *)Th_Malloc(interp, nByte); |
| 577 | 594 | |
| 578 | 595 | /* If the last parameter in the parameter list is "args", then set the |
| 579 | 596 | ** ProcDefn.hasArgs flag. The "args" parameter does not require an |
| 580 | 597 | ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. |
| 581 | 598 | */ |
| 582 | 599 | if( nParam>0 ){ |
| 583 | - if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){ | |
| 600 | + if( TH1_LEN(anParam[nParam-1])==4 | |
| 601 | + && 0==memcmp(azParam[nParam-1], "args", 4) | |
| 602 | + ){ | |
| 584 | 603 | p->hasArgs = 1; |
| 585 | 604 | nParam--; |
| 586 | 605 | } |
| 587 | 606 | } |
| 588 | 607 | |
| @@ -590,12 +609,12 @@ | ||
| 590 | 609 | p->azParam = (char **)&p[1]; |
| 591 | 610 | p->anParam = (int *)&p->azParam[nParam]; |
| 592 | 611 | p->azDefault = (char **)&p->anParam[nParam]; |
| 593 | 612 | p->anDefault = (int *)&p->azDefault[nParam]; |
| 594 | 613 | p->zProgram = (char *)&p->anDefault[nParam]; |
| 595 | - memcpy(p->zProgram, argv[3], argl[3]); | |
| 596 | - p->nProgram = argl[3]; | |
| 614 | + memcpy(p->zProgram, argv[3], TH1_LEN(argl[3])); | |
| 615 | + p->nProgram = TH1_LEN(argl[3]); | |
| 597 | 616 | zSpace = &p->zProgram[p->nProgram]; |
| 598 | 617 | |
| 599 | 618 | for(i=0; i<nParam; i++){ |
| 600 | 619 | char **az; |
| 601 | 620 | int *an; |
| @@ -672,11 +691,12 @@ | ||
| 672 | 691 | int *argl |
| 673 | 692 | ){ |
| 674 | 693 | if( argc!=3 ){ |
| 675 | 694 | return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); |
| 676 | 695 | } |
| 677 | - return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]); | |
| 696 | + return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]), | |
| 697 | + argv[2], TH1_LEN(argl[2])); | |
| 678 | 698 | } |
| 679 | 699 | |
| 680 | 700 | /* |
| 681 | 701 | ** TH Syntax: |
| 682 | 702 | ** |
| @@ -746,13 +766,13 @@ | ||
| 746 | 766 | if( argc!=4 ){ |
| 747 | 767 | return Th_WrongNumArgs(interp, "string compare str1 str2"); |
| 748 | 768 | } |
| 749 | 769 | |
| 750 | 770 | zLeft = argv[2]; |
| 751 | - nLeft = argl[2]; | |
| 771 | + nLeft = TH1_LEN(argl[2]); | |
| 752 | 772 | zRight = argv[3]; |
| 753 | - nRight = argl[3]; | |
| 773 | + nRight = TH1_LEN(argl[3]); | |
| 754 | 774 | |
| 755 | 775 | for(i=0; iRes==0 && i<nLeft && i<nRight; i++){ |
| 756 | 776 | iRes = zLeft[i]-zRight[i]; |
| 757 | 777 | } |
| 758 | 778 | if( iRes==0 ){ |
| @@ -779,12 +799,12 @@ | ||
| 779 | 799 | |
| 780 | 800 | if( argc!=4 ){ |
| 781 | 801 | return Th_WrongNumArgs(interp, "string first needle haystack"); |
| 782 | 802 | } |
| 783 | 803 | |
| 784 | - nNeedle = argl[2]; | |
| 785 | - nHaystack = argl[3]; | |
| 804 | + nNeedle = TH1_LEN(argl[2]); | |
| 805 | + nHaystack = TH1_LEN(argl[3]); | |
| 786 | 806 | |
| 787 | 807 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 788 | 808 | const char *zNeedle = argv[2]; |
| 789 | 809 | const char *zHaystack = argv[3]; |
| 790 | 810 | int i; |
| @@ -812,20 +832,22 @@ | ||
| 812 | 832 | |
| 813 | 833 | if( argc!=4 ){ |
| 814 | 834 | return Th_WrongNumArgs(interp, "string index string index"); |
| 815 | 835 | } |
| 816 | 836 | |
| 817 | - if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){ | |
| 818 | - iIndex = argl[2]-1; | |
| 837 | + if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){ | |
| 838 | + iIndex = TH1_LEN(argl[2])-1; | |
| 819 | 839 | }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ |
| 820 | 840 | Th_ErrorMessage( |
| 821 | 841 | interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); |
| 822 | 842 | return TH_ERROR; |
| 823 | 843 | } |
| 824 | 844 | |
| 825 | - if( iIndex>=0 && iIndex<argl[2] ){ | |
| 826 | - return Th_SetResult(interp, &argv[2][iIndex], 1); | |
| 845 | + if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){ | |
| 846 | + int sz = 1; | |
| 847 | + TH1_XFER_TAINT(sz, argl[2]); | |
| 848 | + return Th_SetResult(interp, &argv[2][iIndex], sz); | |
| 827 | 849 | }else{ |
| 828 | 850 | return Th_SetResult(interp, 0, 0); |
| 829 | 851 | } |
| 830 | 852 | } |
| 831 | 853 | |
| @@ -838,41 +860,44 @@ | ||
| 838 | 860 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 839 | 861 | ){ |
| 840 | 862 | if( argc!=4 ){ |
| 841 | 863 | return Th_WrongNumArgs(interp, "string is class string"); |
| 842 | 864 | } |
| 843 | - if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){ | |
| 865 | + if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){ | |
| 844 | 866 | int i; |
| 845 | 867 | int iRes = 1; |
| 846 | 868 | |
| 847 | - for(i=0; i<argl[3]; i++){ | |
| 869 | + for(i=0; i<TH1_LEN(argl[3]); i++){ | |
| 848 | 870 | if( !th_isalnum(argv[3][i]) ){ |
| 849 | 871 | iRes = 0; |
| 850 | 872 | } |
| 851 | 873 | } |
| 852 | 874 | |
| 853 | 875 | return Th_SetResultInt(interp, iRes); |
| 854 | - }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){ | |
| 876 | + }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){ | |
| 855 | 877 | double fVal; |
| 856 | 878 | if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){ |
| 857 | 879 | return Th_SetResultInt(interp, 1); |
| 858 | 880 | } |
| 859 | 881 | return Th_SetResultInt(interp, 0); |
| 860 | - }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){ | |
| 882 | + }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){ | |
| 861 | 883 | int iVal; |
| 862 | 884 | if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){ |
| 863 | 885 | return Th_SetResultInt(interp, 1); |
| 864 | 886 | } |
| 865 | 887 | return Th_SetResultInt(interp, 0); |
| 866 | - }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){ | |
| 888 | + }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){ | |
| 867 | 889 | if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){ |
| 868 | 890 | return Th_SetResultInt(interp, 1); |
| 869 | 891 | } |
| 870 | 892 | return Th_SetResultInt(interp, 0); |
| 893 | + }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){ | |
| 894 | + return Th_SetResultInt(interp, TH1_TAINTED(argl[3])); | |
| 871 | 895 | }else{ |
| 872 | 896 | Th_ErrorMessage(interp, |
| 873 | - "Expected alnum, double, integer, or list, got:", argv[2], argl[2]); | |
| 897 | + "Expected alnum, double, integer, list, or tainted, got:", | |
| 898 | + argv[2], TH1_LEN(argl[2])); | |
| 874 | 899 | return TH_ERROR; |
| 875 | 900 | } |
| 876 | 901 | } |
| 877 | 902 | |
| 878 | 903 | /* |
| @@ -889,12 +914,12 @@ | ||
| 889 | 914 | |
| 890 | 915 | if( argc!=4 ){ |
| 891 | 916 | return Th_WrongNumArgs(interp, "string last needle haystack"); |
| 892 | 917 | } |
| 893 | 918 | |
| 894 | - nNeedle = argl[2]; | |
| 895 | - nHaystack = argl[3]; | |
| 919 | + nNeedle = TH1_LEN(argl[2]); | |
| 920 | + nHaystack = TH1_LEN(argl[3]); | |
| 896 | 921 | |
| 897 | 922 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 898 | 923 | const char *zNeedle = argv[2]; |
| 899 | 924 | const char *zHaystack = argv[3]; |
| 900 | 925 | int i; |
| @@ -919,11 +944,11 @@ | ||
| 919 | 944 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 920 | 945 | ){ |
| 921 | 946 | if( argc!=3 ){ |
| 922 | 947 | return Th_WrongNumArgs(interp, "string length string"); |
| 923 | 948 | } |
| 924 | - return Th_SetResultInt(interp, argl[2]); | |
| 949 | + return Th_SetResultInt(interp, TH1_LEN(argl[2])); | |
| 925 | 950 | } |
| 926 | 951 | |
| 927 | 952 | /* |
| 928 | 953 | ** TH Syntax: |
| 929 | 954 | ** |
| @@ -938,12 +963,12 @@ | ||
| 938 | 963 | char *zPat, *zStr; |
| 939 | 964 | int rc; |
| 940 | 965 | if( argc!=4 ){ |
| 941 | 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 942 | 967 | } |
| 943 | - zPat = fossil_strndup(argv[2],argl[2]); | |
| 944 | - zStr = fossil_strndup(argv[3],argl[3]); | |
| 968 | + zPat = fossil_strndup(argv[2],TH1_LEN(argl[2])); | |
| 969 | + zStr = fossil_strndup(argv[3],TH1_LEN(argl[3])); | |
| 945 | 970 | rc = sqlite3_strglob(zPat,zStr); |
| 946 | 971 | fossil_free(zPat); |
| 947 | 972 | fossil_free(zStr); |
| 948 | 973 | return Th_SetResultInt(interp, !rc); |
| 949 | 974 | } |
| @@ -956,31 +981,34 @@ | ||
| 956 | 981 | static int string_range_command( |
| 957 | 982 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 958 | 983 | ){ |
| 959 | 984 | int iStart; |
| 960 | 985 | int iEnd; |
| 986 | + int sz; | |
| 961 | 987 | |
| 962 | 988 | if( argc!=5 ){ |
| 963 | 989 | return Th_WrongNumArgs(interp, "string range string first last"); |
| 964 | 990 | } |
| 965 | 991 | |
| 966 | - if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){ | |
| 967 | - iEnd = argl[2]; | |
| 992 | + if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){ | |
| 993 | + iEnd = TH1_LEN(argl[2]); | |
| 968 | 994 | }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ |
| 969 | 995 | Th_ErrorMessage( |
| 970 | - interp, "Expected \"end\" or integer, got:", argv[4], argl[4]); | |
| 996 | + interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4])); | |
| 971 | 997 | return TH_ERROR; |
| 972 | 998 | } |
| 973 | 999 | if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ |
| 974 | 1000 | return TH_ERROR; |
| 975 | 1001 | } |
| 976 | 1002 | |
| 977 | 1003 | if( iStart<0 ) iStart = 0; |
| 978 | - if( iEnd>=argl[2] ) iEnd = argl[2]-1; | |
| 1004 | + if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1; | |
| 979 | 1005 | if( iStart>iEnd ) iEnd = iStart-1; |
| 1006 | + sz = iEnd - iStart + 1; | |
| 1007 | + TH1_XFER_TAINT(sz, argl[2]); | |
| 980 | 1008 | |
| 981 | - return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1); | |
| 1009 | + return Th_SetResult(interp, &argv[2][iStart], sz); | |
| 982 | 1010 | } |
| 983 | 1011 | |
| 984 | 1012 | /* |
| 985 | 1013 | ** TH Syntax: |
| 986 | 1014 | ** |
| @@ -989,27 +1017,33 @@ | ||
| 989 | 1017 | static int string_repeat_command( |
| 990 | 1018 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 991 | 1019 | ){ |
| 992 | 1020 | int n; |
| 993 | 1021 | int i; |
| 994 | - int nByte; | |
| 1022 | + int sz; | |
| 1023 | + long long int nByte; | |
| 995 | 1024 | char *zByte; |
| 996 | 1025 | |
| 997 | 1026 | if( argc!=4 ){ |
| 998 | 1027 | return Th_WrongNumArgs(interp, "string repeat string n"); |
| 999 | 1028 | } |
| 1000 | 1029 | if( Th_ToInt(interp, argv[3], argl[3], &n) ){ |
| 1001 | 1030 | return TH_ERROR; |
| 1002 | 1031 | } |
| 1003 | 1032 | |
| 1004 | - nByte = argl[2] * n; | |
| 1033 | + nByte = n; | |
| 1034 | + sz = TH1_LEN(argl[2]); | |
| 1035 | + nByte *= sz; | |
| 1036 | + TH1_SIZECHECK(nByte+1); | |
| 1005 | 1037 | zByte = Th_Malloc(interp, nByte+1); |
| 1006 | - for(i=0; i<nByte; i+=argl[2]){ | |
| 1007 | - memcpy(&zByte[i], argv[2], argl[2]); | |
| 1038 | + for(i=0; i<nByte; i+=sz){ | |
| 1039 | + memcpy(&zByte[i], argv[2], sz); | |
| 1008 | 1040 | } |
| 1009 | 1041 | |
| 1010 | - Th_SetResult(interp, zByte, nByte); | |
| 1042 | + n = nByte; | |
| 1043 | + TH1_XFER_TAINT(n, argl[2]); | |
| 1044 | + Th_SetResult(interp, zByte, n); | |
| 1011 | 1045 | Th_Free(interp, zByte); |
| 1012 | 1046 | return TH_OK; |
| 1013 | 1047 | } |
| 1014 | 1048 | |
| 1015 | 1049 | /* |
| @@ -1027,17 +1061,18 @@ | ||
| 1027 | 1061 | |
| 1028 | 1062 | if( argc!=3 ){ |
| 1029 | 1063 | return Th_WrongNumArgs(interp, "string trim string"); |
| 1030 | 1064 | } |
| 1031 | 1065 | z = argv[2]; |
| 1032 | - n = argl[2]; | |
| 1033 | - if( argl[1]<5 || argv[1][4]=='l' ){ | |
| 1066 | + n = TH1_LEN(argl[2]); | |
| 1067 | + if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){ | |
| 1034 | 1068 | while( n && th_isspace(z[0]) ){ z++; n--; } |
| 1035 | 1069 | } |
| 1036 | - if( argl[1]<5 || argv[1][4]=='r' ){ | |
| 1070 | + if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){ | |
| 1037 | 1071 | while( n && th_isspace(z[n-1]) ){ n--; } |
| 1038 | 1072 | } |
| 1073 | + TH1_XFER_TAINT(n, argl[2]); | |
| 1039 | 1074 | Th_SetResult(interp, z, n); |
| 1040 | 1075 | return TH_OK; |
| 1041 | 1076 | } |
| 1042 | 1077 | |
| 1043 | 1078 | /* |
| @@ -1051,11 +1086,11 @@ | ||
| 1051 | 1086 | int rc; |
| 1052 | 1087 | |
| 1053 | 1088 | if( argc!=3 ){ |
| 1054 | 1089 | return Th_WrongNumArgs(interp, "info exists var"); |
| 1055 | 1090 | } |
| 1056 | - rc = Th_ExistsVar(interp, argv[2], argl[2]); | |
| 1091 | + rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2])); | |
| 1057 | 1092 | Th_SetResultInt(interp, rc); |
| 1058 | 1093 | return TH_OK; |
| 1059 | 1094 | } |
| 1060 | 1095 | |
| 1061 | 1096 | /* |
| @@ -1117,11 +1152,11 @@ | ||
| 1117 | 1152 | int rc; |
| 1118 | 1153 | |
| 1119 | 1154 | if( argc!=3 ){ |
| 1120 | 1155 | return Th_WrongNumArgs(interp, "array exists var"); |
| 1121 | 1156 | } |
| 1122 | - rc = Th_ExistsArrayVar(interp, argv[2], argl[2]); | |
| 1157 | + rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2])); | |
| 1123 | 1158 | Th_SetResultInt(interp, rc); |
| 1124 | 1159 | return TH_OK; |
| 1125 | 1160 | } |
| 1126 | 1161 | |
| 1127 | 1162 | /* |
| @@ -1137,11 +1172,11 @@ | ||
| 1137 | 1172 | int nElem = 0; |
| 1138 | 1173 | |
| 1139 | 1174 | if( argc!=3 ){ |
| 1140 | 1175 | return Th_WrongNumArgs(interp, "array names varname"); |
| 1141 | 1176 | } |
| 1142 | - rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem); | |
| 1177 | + rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem); | |
| 1143 | 1178 | if( rc!=TH_OK ){ |
| 1144 | 1179 | return rc; |
| 1145 | 1180 | } |
| 1146 | 1181 | Th_SetResult(interp, zElem, nElem); |
| 1147 | 1182 | if( zElem ) Th_Free(interp, zElem); |
| @@ -1161,11 +1196,11 @@ | ||
| 1161 | 1196 | int *argl |
| 1162 | 1197 | ){ |
| 1163 | 1198 | if( argc!=2 ){ |
| 1164 | 1199 | return Th_WrongNumArgs(interp, "unset var"); |
| 1165 | 1200 | } |
| 1166 | - return Th_UnsetVar(interp, argv[1], argl[1]); | |
| 1201 | + return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1])); | |
| 1167 | 1202 | } |
| 1168 | 1203 | |
| 1169 | 1204 | int Th_CallSubCommand( |
| 1170 | 1205 | Th_Interp *interp, |
| 1171 | 1206 | void *ctx, |
| @@ -1176,19 +1211,22 @@ | ||
| 1176 | 1211 | ){ |
| 1177 | 1212 | if( argc>1 ){ |
| 1178 | 1213 | int i; |
| 1179 | 1214 | for(i=0; aSub[i].zName; i++){ |
| 1180 | 1215 | const char *zName = aSub[i].zName; |
| 1181 | - if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){ | |
| 1216 | + if( th_strlen(zName)==TH1_LEN(argl[1]) | |
| 1217 | + && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){ | |
| 1182 | 1218 | return aSub[i].xProc(interp, ctx, argc, argv, argl); |
| 1183 | 1219 | } |
| 1184 | 1220 | } |
| 1185 | 1221 | } |
| 1186 | 1222 | if(argc<2){ |
| 1187 | - Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]); | |
| 1223 | + Th_ErrorMessage(interp, "Expected sub-command for", | |
| 1224 | + argv[0], TH1_LEN(argl[0])); | |
| 1188 | 1225 | }else{ |
| 1189 | - Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]); | |
| 1226 | + Th_ErrorMessage(interp, "Expected sub-command, got:", | |
| 1227 | + argv[1], TH1_LEN(argl[1])); | |
| 1190 | 1228 | } |
| 1191 | 1229 | return TH_ERROR; |
| 1192 | 1230 | } |
| 1193 | 1231 | |
| 1194 | 1232 | /* |
| @@ -1319,11 +1357,11 @@ | ||
| 1319 | 1357 | int iFrame = -1; |
| 1320 | 1358 | |
| 1321 | 1359 | if( argc!=2 && argc!=3 ){ |
| 1322 | 1360 | return Th_WrongNumArgs(interp, "uplevel ?level? script..."); |
| 1323 | 1361 | } |
| 1324 | - if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){ | |
| 1362 | + if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){ | |
| 1325 | 1363 | return TH_ERROR; |
| 1326 | 1364 | } |
| 1327 | 1365 | return Th_Eval(interp, iFrame, argv[argc-1], -1); |
| 1328 | 1366 | } |
| 1329 | 1367 | |
| @@ -1342,19 +1380,20 @@ | ||
| 1342 | 1380 | int iVar = 1; |
| 1343 | 1381 | int iFrame = -1; |
| 1344 | 1382 | int rc = TH_OK; |
| 1345 | 1383 | int i; |
| 1346 | 1384 | |
| 1347 | - if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){ | |
| 1385 | + if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){ | |
| 1348 | 1386 | iVar++; |
| 1349 | 1387 | } |
| 1350 | 1388 | if( argc==iVar || (argc-iVar)%2 ){ |
| 1351 | 1389 | return Th_WrongNumArgs(interp, |
| 1352 | 1390 | "upvar frame othervar myvar ?othervar myvar...?"); |
| 1353 | 1391 | } |
| 1354 | 1392 | for(i=iVar; rc==TH_OK && i<argc; i=i+2){ |
| 1355 | - rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]); | |
| 1393 | + rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]), | |
| 1394 | + iFrame, argv[i], TH1_LEN(argl[i])); | |
| 1356 | 1395 | } |
| 1357 | 1396 | return rc; |
| 1358 | 1397 | } |
| 1359 | 1398 | |
| 1360 | 1399 | /* |
| 1361 | 1400 |
| --- src/th_lang.c | |
| +++ src/th_lang.c | |
| @@ -39,11 +39,11 @@ | |
| 39 | |
| 40 | rc = Th_Eval(interp, 0, argv[1], -1); |
| 41 | if( argc==3 ){ |
| 42 | int nResult; |
| 43 | const char *zResult = Th_GetResult(interp, &nResult); |
| 44 | Th_SetVar(interp, argv[2], argl[2], zResult, nResult); |
| 45 | } |
| 46 | |
| 47 | Th_SetResultInt(interp, rc); |
| 48 | return TH_OK; |
| 49 | } |
| @@ -180,20 +180,24 @@ | |
| 180 | int nVar; |
| 181 | char **azValue = 0; |
| 182 | int *anValue; |
| 183 | int nValue; |
| 184 | int ii, jj; |
| 185 | |
| 186 | if( argc!=4 ){ |
| 187 | return Th_WrongNumArgs(interp, "foreach varlist list script"); |
| 188 | } |
| 189 | rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar); |
| 190 | if( rc ) return rc; |
| 191 | rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue); |
| 192 | for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){ |
| 193 | for(jj=0; jj<nVar; jj++){ |
| 194 | Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]); |
| 195 | } |
| 196 | rc = eval_loopbody(interp, argv[3], argl[3]); |
| 197 | } |
| 198 | if( rc==TH_BREAK ) rc = TH_OK; |
| 199 | Th_Free(interp, azVar); |
| @@ -215,15 +219,18 @@ | |
| 215 | int *argl |
| 216 | ){ |
| 217 | char *zList = 0; |
| 218 | int nList = 0; |
| 219 | int i; |
| 220 | |
| 221 | for(i=1; i<argc; i++){ |
| 222 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 223 | } |
| 224 | |
| 225 | Th_SetResult(interp, zList, nList); |
| 226 | Th_Free(interp, zList); |
| 227 | |
| 228 | return TH_OK; |
| 229 | } |
| @@ -244,23 +251,27 @@ | |
| 244 | int *argl |
| 245 | ){ |
| 246 | char *zList = 0; |
| 247 | int nList = 0; |
| 248 | int i, rc; |
| 249 | |
| 250 | if( argc<2 ){ |
| 251 | return Th_WrongNumArgs(interp, "lappend var ..."); |
| 252 | } |
| 253 | rc = Th_GetVar(interp, argv[1], argl[1]); |
| 254 | if( rc==TH_OK ){ |
| 255 | zList = Th_TakeResult(interp, &nList); |
| 256 | } |
| 257 | |
| 258 | for(i=2; i<argc; i++){ |
| 259 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 260 | } |
| 261 | |
| 262 | Th_SetVar(interp, argv[1], argl[1], zList, nList); |
| 263 | Th_SetResult(interp, zList, nList); |
| 264 | Th_Free(interp, zList); |
| 265 | |
| 266 | return TH_OK; |
| @@ -283,23 +294,27 @@ | |
| 283 | int rc; |
| 284 | |
| 285 | char **azElem; |
| 286 | int *anElem; |
| 287 | int nCount; |
| 288 | |
| 289 | if( argc!=3 ){ |
| 290 | return Th_WrongNumArgs(interp, "lindex list index"); |
| 291 | } |
| 292 | |
| 293 | if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){ |
| 294 | return TH_ERROR; |
| 295 | } |
| 296 | |
| 297 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 298 | if( rc==TH_OK ){ |
| 299 | if( iElem<nCount && iElem>=0 ){ |
| 300 | Th_SetResult(interp, azElem[iElem], anElem[iElem]); |
| 301 | }else{ |
| 302 | Th_SetResult(interp, 0, 0); |
| 303 | } |
| 304 | Th_Free(interp, azElem); |
| 305 | } |
| @@ -356,13 +371,14 @@ | |
| 356 | return Th_WrongNumArgs(interp, "lsearch list string"); |
| 357 | } |
| 358 | |
| 359 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 360 | if( rc==TH_OK ){ |
| 361 | Th_SetResultInt(interp, -1); |
| 362 | for(i=0; i<nCount; i++){ |
| 363 | if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){ |
| 364 | Th_SetResultInt(interp, i); |
| 365 | break; |
| 366 | } |
| 367 | } |
| 368 | Th_Free(interp, azElem); |
| @@ -561,28 +577,31 @@ | |
| 561 | int nUsage = 0; /* Number of bytes at zUsage */ |
| 562 | |
| 563 | if( argc!=4 ){ |
| 564 | return Th_WrongNumArgs(interp, "proc name arglist code"); |
| 565 | } |
| 566 | if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){ |
| 567 | return TH_ERROR; |
| 568 | } |
| 569 | |
| 570 | /* Allocate the new ProcDefn structure. */ |
| 571 | nByte = sizeof(ProcDefn) + /* ProcDefn structure */ |
| 572 | (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ |
| 573 | (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ |
| 574 | argl[3] + /* zProgram */ |
| 575 | argl[2]; /* Space for copies of parameter names and default values */ |
| 576 | p = (ProcDefn *)Th_Malloc(interp, nByte); |
| 577 | |
| 578 | /* If the last parameter in the parameter list is "args", then set the |
| 579 | ** ProcDefn.hasArgs flag. The "args" parameter does not require an |
| 580 | ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. |
| 581 | */ |
| 582 | if( nParam>0 ){ |
| 583 | if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){ |
| 584 | p->hasArgs = 1; |
| 585 | nParam--; |
| 586 | } |
| 587 | } |
| 588 | |
| @@ -590,12 +609,12 @@ | |
| 590 | p->azParam = (char **)&p[1]; |
| 591 | p->anParam = (int *)&p->azParam[nParam]; |
| 592 | p->azDefault = (char **)&p->anParam[nParam]; |
| 593 | p->anDefault = (int *)&p->azDefault[nParam]; |
| 594 | p->zProgram = (char *)&p->anDefault[nParam]; |
| 595 | memcpy(p->zProgram, argv[3], argl[3]); |
| 596 | p->nProgram = argl[3]; |
| 597 | zSpace = &p->zProgram[p->nProgram]; |
| 598 | |
| 599 | for(i=0; i<nParam; i++){ |
| 600 | char **az; |
| 601 | int *an; |
| @@ -672,11 +691,12 @@ | |
| 672 | int *argl |
| 673 | ){ |
| 674 | if( argc!=3 ){ |
| 675 | return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); |
| 676 | } |
| 677 | return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]); |
| 678 | } |
| 679 | |
| 680 | /* |
| 681 | ** TH Syntax: |
| 682 | ** |
| @@ -746,13 +766,13 @@ | |
| 746 | if( argc!=4 ){ |
| 747 | return Th_WrongNumArgs(interp, "string compare str1 str2"); |
| 748 | } |
| 749 | |
| 750 | zLeft = argv[2]; |
| 751 | nLeft = argl[2]; |
| 752 | zRight = argv[3]; |
| 753 | nRight = argl[3]; |
| 754 | |
| 755 | for(i=0; iRes==0 && i<nLeft && i<nRight; i++){ |
| 756 | iRes = zLeft[i]-zRight[i]; |
| 757 | } |
| 758 | if( iRes==0 ){ |
| @@ -779,12 +799,12 @@ | |
| 779 | |
| 780 | if( argc!=4 ){ |
| 781 | return Th_WrongNumArgs(interp, "string first needle haystack"); |
| 782 | } |
| 783 | |
| 784 | nNeedle = argl[2]; |
| 785 | nHaystack = argl[3]; |
| 786 | |
| 787 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 788 | const char *zNeedle = argv[2]; |
| 789 | const char *zHaystack = argv[3]; |
| 790 | int i; |
| @@ -812,20 +832,22 @@ | |
| 812 | |
| 813 | if( argc!=4 ){ |
| 814 | return Th_WrongNumArgs(interp, "string index string index"); |
| 815 | } |
| 816 | |
| 817 | if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){ |
| 818 | iIndex = argl[2]-1; |
| 819 | }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ |
| 820 | Th_ErrorMessage( |
| 821 | interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); |
| 822 | return TH_ERROR; |
| 823 | } |
| 824 | |
| 825 | if( iIndex>=0 && iIndex<argl[2] ){ |
| 826 | return Th_SetResult(interp, &argv[2][iIndex], 1); |
| 827 | }else{ |
| 828 | return Th_SetResult(interp, 0, 0); |
| 829 | } |
| 830 | } |
| 831 | |
| @@ -838,41 +860,44 @@ | |
| 838 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 839 | ){ |
| 840 | if( argc!=4 ){ |
| 841 | return Th_WrongNumArgs(interp, "string is class string"); |
| 842 | } |
| 843 | if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){ |
| 844 | int i; |
| 845 | int iRes = 1; |
| 846 | |
| 847 | for(i=0; i<argl[3]; i++){ |
| 848 | if( !th_isalnum(argv[3][i]) ){ |
| 849 | iRes = 0; |
| 850 | } |
| 851 | } |
| 852 | |
| 853 | return Th_SetResultInt(interp, iRes); |
| 854 | }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){ |
| 855 | double fVal; |
| 856 | if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){ |
| 857 | return Th_SetResultInt(interp, 1); |
| 858 | } |
| 859 | return Th_SetResultInt(interp, 0); |
| 860 | }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){ |
| 861 | int iVal; |
| 862 | if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){ |
| 863 | return Th_SetResultInt(interp, 1); |
| 864 | } |
| 865 | return Th_SetResultInt(interp, 0); |
| 866 | }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){ |
| 867 | if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){ |
| 868 | return Th_SetResultInt(interp, 1); |
| 869 | } |
| 870 | return Th_SetResultInt(interp, 0); |
| 871 | }else{ |
| 872 | Th_ErrorMessage(interp, |
| 873 | "Expected alnum, double, integer, or list, got:", argv[2], argl[2]); |
| 874 | return TH_ERROR; |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | /* |
| @@ -889,12 +914,12 @@ | |
| 889 | |
| 890 | if( argc!=4 ){ |
| 891 | return Th_WrongNumArgs(interp, "string last needle haystack"); |
| 892 | } |
| 893 | |
| 894 | nNeedle = argl[2]; |
| 895 | nHaystack = argl[3]; |
| 896 | |
| 897 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 898 | const char *zNeedle = argv[2]; |
| 899 | const char *zHaystack = argv[3]; |
| 900 | int i; |
| @@ -919,11 +944,11 @@ | |
| 919 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 920 | ){ |
| 921 | if( argc!=3 ){ |
| 922 | return Th_WrongNumArgs(interp, "string length string"); |
| 923 | } |
| 924 | return Th_SetResultInt(interp, argl[2]); |
| 925 | } |
| 926 | |
| 927 | /* |
| 928 | ** TH Syntax: |
| 929 | ** |
| @@ -938,12 +963,12 @@ | |
| 938 | char *zPat, *zStr; |
| 939 | int rc; |
| 940 | if( argc!=4 ){ |
| 941 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 942 | } |
| 943 | zPat = fossil_strndup(argv[2],argl[2]); |
| 944 | zStr = fossil_strndup(argv[3],argl[3]); |
| 945 | rc = sqlite3_strglob(zPat,zStr); |
| 946 | fossil_free(zPat); |
| 947 | fossil_free(zStr); |
| 948 | return Th_SetResultInt(interp, !rc); |
| 949 | } |
| @@ -956,31 +981,34 @@ | |
| 956 | static int string_range_command( |
| 957 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 958 | ){ |
| 959 | int iStart; |
| 960 | int iEnd; |
| 961 | |
| 962 | if( argc!=5 ){ |
| 963 | return Th_WrongNumArgs(interp, "string range string first last"); |
| 964 | } |
| 965 | |
| 966 | if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){ |
| 967 | iEnd = argl[2]; |
| 968 | }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ |
| 969 | Th_ErrorMessage( |
| 970 | interp, "Expected \"end\" or integer, got:", argv[4], argl[4]); |
| 971 | return TH_ERROR; |
| 972 | } |
| 973 | if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ |
| 974 | return TH_ERROR; |
| 975 | } |
| 976 | |
| 977 | if( iStart<0 ) iStart = 0; |
| 978 | if( iEnd>=argl[2] ) iEnd = argl[2]-1; |
| 979 | if( iStart>iEnd ) iEnd = iStart-1; |
| 980 | |
| 981 | return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1); |
| 982 | } |
| 983 | |
| 984 | /* |
| 985 | ** TH Syntax: |
| 986 | ** |
| @@ -989,27 +1017,33 @@ | |
| 989 | static int string_repeat_command( |
| 990 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 991 | ){ |
| 992 | int n; |
| 993 | int i; |
| 994 | int nByte; |
| 995 | char *zByte; |
| 996 | |
| 997 | if( argc!=4 ){ |
| 998 | return Th_WrongNumArgs(interp, "string repeat string n"); |
| 999 | } |
| 1000 | if( Th_ToInt(interp, argv[3], argl[3], &n) ){ |
| 1001 | return TH_ERROR; |
| 1002 | } |
| 1003 | |
| 1004 | nByte = argl[2] * n; |
| 1005 | zByte = Th_Malloc(interp, nByte+1); |
| 1006 | for(i=0; i<nByte; i+=argl[2]){ |
| 1007 | memcpy(&zByte[i], argv[2], argl[2]); |
| 1008 | } |
| 1009 | |
| 1010 | Th_SetResult(interp, zByte, nByte); |
| 1011 | Th_Free(interp, zByte); |
| 1012 | return TH_OK; |
| 1013 | } |
| 1014 | |
| 1015 | /* |
| @@ -1027,17 +1061,18 @@ | |
| 1027 | |
| 1028 | if( argc!=3 ){ |
| 1029 | return Th_WrongNumArgs(interp, "string trim string"); |
| 1030 | } |
| 1031 | z = argv[2]; |
| 1032 | n = argl[2]; |
| 1033 | if( argl[1]<5 || argv[1][4]=='l' ){ |
| 1034 | while( n && th_isspace(z[0]) ){ z++; n--; } |
| 1035 | } |
| 1036 | if( argl[1]<5 || argv[1][4]=='r' ){ |
| 1037 | while( n && th_isspace(z[n-1]) ){ n--; } |
| 1038 | } |
| 1039 | Th_SetResult(interp, z, n); |
| 1040 | return TH_OK; |
| 1041 | } |
| 1042 | |
| 1043 | /* |
| @@ -1051,11 +1086,11 @@ | |
| 1051 | int rc; |
| 1052 | |
| 1053 | if( argc!=3 ){ |
| 1054 | return Th_WrongNumArgs(interp, "info exists var"); |
| 1055 | } |
| 1056 | rc = Th_ExistsVar(interp, argv[2], argl[2]); |
| 1057 | Th_SetResultInt(interp, rc); |
| 1058 | return TH_OK; |
| 1059 | } |
| 1060 | |
| 1061 | /* |
| @@ -1117,11 +1152,11 @@ | |
| 1117 | int rc; |
| 1118 | |
| 1119 | if( argc!=3 ){ |
| 1120 | return Th_WrongNumArgs(interp, "array exists var"); |
| 1121 | } |
| 1122 | rc = Th_ExistsArrayVar(interp, argv[2], argl[2]); |
| 1123 | Th_SetResultInt(interp, rc); |
| 1124 | return TH_OK; |
| 1125 | } |
| 1126 | |
| 1127 | /* |
| @@ -1137,11 +1172,11 @@ | |
| 1137 | int nElem = 0; |
| 1138 | |
| 1139 | if( argc!=3 ){ |
| 1140 | return Th_WrongNumArgs(interp, "array names varname"); |
| 1141 | } |
| 1142 | rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem); |
| 1143 | if( rc!=TH_OK ){ |
| 1144 | return rc; |
| 1145 | } |
| 1146 | Th_SetResult(interp, zElem, nElem); |
| 1147 | if( zElem ) Th_Free(interp, zElem); |
| @@ -1161,11 +1196,11 @@ | |
| 1161 | int *argl |
| 1162 | ){ |
| 1163 | if( argc!=2 ){ |
| 1164 | return Th_WrongNumArgs(interp, "unset var"); |
| 1165 | } |
| 1166 | return Th_UnsetVar(interp, argv[1], argl[1]); |
| 1167 | } |
| 1168 | |
| 1169 | int Th_CallSubCommand( |
| 1170 | Th_Interp *interp, |
| 1171 | void *ctx, |
| @@ -1176,19 +1211,22 @@ | |
| 1176 | ){ |
| 1177 | if( argc>1 ){ |
| 1178 | int i; |
| 1179 | for(i=0; aSub[i].zName; i++){ |
| 1180 | const char *zName = aSub[i].zName; |
| 1181 | if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){ |
| 1182 | return aSub[i].xProc(interp, ctx, argc, argv, argl); |
| 1183 | } |
| 1184 | } |
| 1185 | } |
| 1186 | if(argc<2){ |
| 1187 | Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]); |
| 1188 | }else{ |
| 1189 | Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]); |
| 1190 | } |
| 1191 | return TH_ERROR; |
| 1192 | } |
| 1193 | |
| 1194 | /* |
| @@ -1319,11 +1357,11 @@ | |
| 1319 | int iFrame = -1; |
| 1320 | |
| 1321 | if( argc!=2 && argc!=3 ){ |
| 1322 | return Th_WrongNumArgs(interp, "uplevel ?level? script..."); |
| 1323 | } |
| 1324 | if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){ |
| 1325 | return TH_ERROR; |
| 1326 | } |
| 1327 | return Th_Eval(interp, iFrame, argv[argc-1], -1); |
| 1328 | } |
| 1329 | |
| @@ -1342,19 +1380,20 @@ | |
| 1342 | int iVar = 1; |
| 1343 | int iFrame = -1; |
| 1344 | int rc = TH_OK; |
| 1345 | int i; |
| 1346 | |
| 1347 | if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){ |
| 1348 | iVar++; |
| 1349 | } |
| 1350 | if( argc==iVar || (argc-iVar)%2 ){ |
| 1351 | return Th_WrongNumArgs(interp, |
| 1352 | "upvar frame othervar myvar ?othervar myvar...?"); |
| 1353 | } |
| 1354 | for(i=iVar; rc==TH_OK && i<argc; i=i+2){ |
| 1355 | rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]); |
| 1356 | } |
| 1357 | return rc; |
| 1358 | } |
| 1359 | |
| 1360 | /* |
| 1361 |
| --- src/th_lang.c | |
| +++ src/th_lang.c | |
| @@ -39,11 +39,11 @@ | |
| 39 | |
| 40 | rc = Th_Eval(interp, 0, argv[1], -1); |
| 41 | if( argc==3 ){ |
| 42 | int nResult; |
| 43 | const char *zResult = Th_GetResult(interp, &nResult); |
| 44 | Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult); |
| 45 | } |
| 46 | |
| 47 | Th_SetResultInt(interp, rc); |
| 48 | return TH_OK; |
| 49 | } |
| @@ -180,20 +180,24 @@ | |
| 180 | int nVar; |
| 181 | char **azValue = 0; |
| 182 | int *anValue; |
| 183 | int nValue; |
| 184 | int ii, jj; |
| 185 | int bTaint = 0; |
| 186 | |
| 187 | if( argc!=4 ){ |
| 188 | return Th_WrongNumArgs(interp, "foreach varlist list script"); |
| 189 | } |
| 190 | rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar); |
| 191 | if( rc ) return rc; |
| 192 | TH1_XFER_TAINT(bTaint, argl[2]); |
| 193 | rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue); |
| 194 | for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){ |
| 195 | for(jj=0; jj<nVar; jj++){ |
| 196 | int x = anValue[ii+jj]; |
| 197 | TH1_XFER_TAINT(x, bTaint); |
| 198 | Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x); |
| 199 | } |
| 200 | rc = eval_loopbody(interp, argv[3], argl[3]); |
| 201 | } |
| 202 | if( rc==TH_BREAK ) rc = TH_OK; |
| 203 | Th_Free(interp, azVar); |
| @@ -215,15 +219,18 @@ | |
| 219 | int *argl |
| 220 | ){ |
| 221 | char *zList = 0; |
| 222 | int nList = 0; |
| 223 | int i; |
| 224 | int bTaint = 0; |
| 225 | |
| 226 | for(i=1; i<argc; i++){ |
| 227 | TH1_XFER_TAINT(bTaint,argl[i]); |
| 228 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 229 | } |
| 230 | |
| 231 | TH1_XFER_TAINT(nList, bTaint); |
| 232 | Th_SetResult(interp, zList, nList); |
| 233 | Th_Free(interp, zList); |
| 234 | |
| 235 | return TH_OK; |
| 236 | } |
| @@ -244,23 +251,27 @@ | |
| 251 | int *argl |
| 252 | ){ |
| 253 | char *zList = 0; |
| 254 | int nList = 0; |
| 255 | int i, rc; |
| 256 | int bTaint = 0; |
| 257 | |
| 258 | if( argc<2 ){ |
| 259 | return Th_WrongNumArgs(interp, "lappend var ..."); |
| 260 | } |
| 261 | rc = Th_GetVar(interp, argv[1], argl[1]); |
| 262 | if( rc==TH_OK ){ |
| 263 | zList = Th_TakeResult(interp, &nList); |
| 264 | } |
| 265 | |
| 266 | TH1_XFER_TAINT(bTaint, nList); |
| 267 | for(i=2; i<argc; i++){ |
| 268 | TH1_XFER_TAINT(bTaint, argl[i]); |
| 269 | Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); |
| 270 | } |
| 271 | |
| 272 | TH1_XFER_TAINT(nList, bTaint); |
| 273 | Th_SetVar(interp, argv[1], argl[1], zList, nList); |
| 274 | Th_SetResult(interp, zList, nList); |
| 275 | Th_Free(interp, zList); |
| 276 | |
| 277 | return TH_OK; |
| @@ -283,23 +294,27 @@ | |
| 294 | int rc; |
| 295 | |
| 296 | char **azElem; |
| 297 | int *anElem; |
| 298 | int nCount; |
| 299 | int bTaint = 0; |
| 300 | |
| 301 | if( argc!=3 ){ |
| 302 | return Th_WrongNumArgs(interp, "lindex list index"); |
| 303 | } |
| 304 | |
| 305 | if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){ |
| 306 | return TH_ERROR; |
| 307 | } |
| 308 | |
| 309 | TH1_XFER_TAINT(bTaint, argl[1]); |
| 310 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 311 | if( rc==TH_OK ){ |
| 312 | if( iElem<nCount && iElem>=0 ){ |
| 313 | int sz = anElem[iElem]; |
| 314 | TH1_XFER_TAINT(sz, bTaint); |
| 315 | Th_SetResult(interp, azElem[iElem], sz); |
| 316 | }else{ |
| 317 | Th_SetResult(interp, 0, 0); |
| 318 | } |
| 319 | Th_Free(interp, azElem); |
| 320 | } |
| @@ -356,13 +371,14 @@ | |
| 371 | return Th_WrongNumArgs(interp, "lsearch list string"); |
| 372 | } |
| 373 | |
| 374 | rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); |
| 375 | if( rc==TH_OK ){ |
| 376 | int nn = TH1_LEN(argl[2]); |
| 377 | Th_SetResultInt(interp, -1); |
| 378 | for(i=0; i<nCount; i++){ |
| 379 | if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){ |
| 380 | Th_SetResultInt(interp, i); |
| 381 | break; |
| 382 | } |
| 383 | } |
| 384 | Th_Free(interp, azElem); |
| @@ -561,28 +577,31 @@ | |
| 577 | int nUsage = 0; /* Number of bytes at zUsage */ |
| 578 | |
| 579 | if( argc!=4 ){ |
| 580 | return Th_WrongNumArgs(interp, "proc name arglist code"); |
| 581 | } |
| 582 | if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), |
| 583 | &azParam, &anParam, &nParam) ){ |
| 584 | return TH_ERROR; |
| 585 | } |
| 586 | |
| 587 | /* Allocate the new ProcDefn structure. */ |
| 588 | nByte = sizeof(ProcDefn) + /* ProcDefn structure */ |
| 589 | (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ |
| 590 | (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ |
| 591 | TH1_LEN(argl[3]) + /* zProgram */ |
| 592 | TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */ |
| 593 | p = (ProcDefn *)Th_Malloc(interp, nByte); |
| 594 | |
| 595 | /* If the last parameter in the parameter list is "args", then set the |
| 596 | ** ProcDefn.hasArgs flag. The "args" parameter does not require an |
| 597 | ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. |
| 598 | */ |
| 599 | if( nParam>0 ){ |
| 600 | if( TH1_LEN(anParam[nParam-1])==4 |
| 601 | && 0==memcmp(azParam[nParam-1], "args", 4) |
| 602 | ){ |
| 603 | p->hasArgs = 1; |
| 604 | nParam--; |
| 605 | } |
| 606 | } |
| 607 | |
| @@ -590,12 +609,12 @@ | |
| 609 | p->azParam = (char **)&p[1]; |
| 610 | p->anParam = (int *)&p->azParam[nParam]; |
| 611 | p->azDefault = (char **)&p->anParam[nParam]; |
| 612 | p->anDefault = (int *)&p->azDefault[nParam]; |
| 613 | p->zProgram = (char *)&p->anDefault[nParam]; |
| 614 | memcpy(p->zProgram, argv[3], TH1_LEN(argl[3])); |
| 615 | p->nProgram = TH1_LEN(argl[3]); |
| 616 | zSpace = &p->zProgram[p->nProgram]; |
| 617 | |
| 618 | for(i=0; i<nParam; i++){ |
| 619 | char **az; |
| 620 | int *an; |
| @@ -672,11 +691,12 @@ | |
| 691 | int *argl |
| 692 | ){ |
| 693 | if( argc!=3 ){ |
| 694 | return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); |
| 695 | } |
| 696 | return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]), |
| 697 | argv[2], TH1_LEN(argl[2])); |
| 698 | } |
| 699 | |
| 700 | /* |
| 701 | ** TH Syntax: |
| 702 | ** |
| @@ -746,13 +766,13 @@ | |
| 766 | if( argc!=4 ){ |
| 767 | return Th_WrongNumArgs(interp, "string compare str1 str2"); |
| 768 | } |
| 769 | |
| 770 | zLeft = argv[2]; |
| 771 | nLeft = TH1_LEN(argl[2]); |
| 772 | zRight = argv[3]; |
| 773 | nRight = TH1_LEN(argl[3]); |
| 774 | |
| 775 | for(i=0; iRes==0 && i<nLeft && i<nRight; i++){ |
| 776 | iRes = zLeft[i]-zRight[i]; |
| 777 | } |
| 778 | if( iRes==0 ){ |
| @@ -779,12 +799,12 @@ | |
| 799 | |
| 800 | if( argc!=4 ){ |
| 801 | return Th_WrongNumArgs(interp, "string first needle haystack"); |
| 802 | } |
| 803 | |
| 804 | nNeedle = TH1_LEN(argl[2]); |
| 805 | nHaystack = TH1_LEN(argl[3]); |
| 806 | |
| 807 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 808 | const char *zNeedle = argv[2]; |
| 809 | const char *zHaystack = argv[3]; |
| 810 | int i; |
| @@ -812,20 +832,22 @@ | |
| 832 | |
| 833 | if( argc!=4 ){ |
| 834 | return Th_WrongNumArgs(interp, "string index string index"); |
| 835 | } |
| 836 | |
| 837 | if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){ |
| 838 | iIndex = TH1_LEN(argl[2])-1; |
| 839 | }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ |
| 840 | Th_ErrorMessage( |
| 841 | interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); |
| 842 | return TH_ERROR; |
| 843 | } |
| 844 | |
| 845 | if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){ |
| 846 | int sz = 1; |
| 847 | TH1_XFER_TAINT(sz, argl[2]); |
| 848 | return Th_SetResult(interp, &argv[2][iIndex], sz); |
| 849 | }else{ |
| 850 | return Th_SetResult(interp, 0, 0); |
| 851 | } |
| 852 | } |
| 853 | |
| @@ -838,41 +860,44 @@ | |
| 860 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 861 | ){ |
| 862 | if( argc!=4 ){ |
| 863 | return Th_WrongNumArgs(interp, "string is class string"); |
| 864 | } |
| 865 | if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){ |
| 866 | int i; |
| 867 | int iRes = 1; |
| 868 | |
| 869 | for(i=0; i<TH1_LEN(argl[3]); i++){ |
| 870 | if( !th_isalnum(argv[3][i]) ){ |
| 871 | iRes = 0; |
| 872 | } |
| 873 | } |
| 874 | |
| 875 | return Th_SetResultInt(interp, iRes); |
| 876 | }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){ |
| 877 | double fVal; |
| 878 | if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){ |
| 879 | return Th_SetResultInt(interp, 1); |
| 880 | } |
| 881 | return Th_SetResultInt(interp, 0); |
| 882 | }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){ |
| 883 | int iVal; |
| 884 | if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){ |
| 885 | return Th_SetResultInt(interp, 1); |
| 886 | } |
| 887 | return Th_SetResultInt(interp, 0); |
| 888 | }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){ |
| 889 | if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){ |
| 890 | return Th_SetResultInt(interp, 1); |
| 891 | } |
| 892 | return Th_SetResultInt(interp, 0); |
| 893 | }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){ |
| 894 | return Th_SetResultInt(interp, TH1_TAINTED(argl[3])); |
| 895 | }else{ |
| 896 | Th_ErrorMessage(interp, |
| 897 | "Expected alnum, double, integer, list, or tainted, got:", |
| 898 | argv[2], TH1_LEN(argl[2])); |
| 899 | return TH_ERROR; |
| 900 | } |
| 901 | } |
| 902 | |
| 903 | /* |
| @@ -889,12 +914,12 @@ | |
| 914 | |
| 915 | if( argc!=4 ){ |
| 916 | return Th_WrongNumArgs(interp, "string last needle haystack"); |
| 917 | } |
| 918 | |
| 919 | nNeedle = TH1_LEN(argl[2]); |
| 920 | nHaystack = TH1_LEN(argl[3]); |
| 921 | |
| 922 | if( nNeedle && nHaystack && nNeedle<=nHaystack ){ |
| 923 | const char *zNeedle = argv[2]; |
| 924 | const char *zHaystack = argv[3]; |
| 925 | int i; |
| @@ -919,11 +944,11 @@ | |
| 944 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 945 | ){ |
| 946 | if( argc!=3 ){ |
| 947 | return Th_WrongNumArgs(interp, "string length string"); |
| 948 | } |
| 949 | return Th_SetResultInt(interp, TH1_LEN(argl[2])); |
| 950 | } |
| 951 | |
| 952 | /* |
| 953 | ** TH Syntax: |
| 954 | ** |
| @@ -938,12 +963,12 @@ | |
| 963 | char *zPat, *zStr; |
| 964 | int rc; |
| 965 | if( argc!=4 ){ |
| 966 | return Th_WrongNumArgs(interp, "string match pattern string"); |
| 967 | } |
| 968 | zPat = fossil_strndup(argv[2],TH1_LEN(argl[2])); |
| 969 | zStr = fossil_strndup(argv[3],TH1_LEN(argl[3])); |
| 970 | rc = sqlite3_strglob(zPat,zStr); |
| 971 | fossil_free(zPat); |
| 972 | fossil_free(zStr); |
| 973 | return Th_SetResultInt(interp, !rc); |
| 974 | } |
| @@ -956,31 +981,34 @@ | |
| 981 | static int string_range_command( |
| 982 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 983 | ){ |
| 984 | int iStart; |
| 985 | int iEnd; |
| 986 | int sz; |
| 987 | |
| 988 | if( argc!=5 ){ |
| 989 | return Th_WrongNumArgs(interp, "string range string first last"); |
| 990 | } |
| 991 | |
| 992 | if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){ |
| 993 | iEnd = TH1_LEN(argl[2]); |
| 994 | }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ |
| 995 | Th_ErrorMessage( |
| 996 | interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4])); |
| 997 | return TH_ERROR; |
| 998 | } |
| 999 | if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ |
| 1000 | return TH_ERROR; |
| 1001 | } |
| 1002 | |
| 1003 | if( iStart<0 ) iStart = 0; |
| 1004 | if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1; |
| 1005 | if( iStart>iEnd ) iEnd = iStart-1; |
| 1006 | sz = iEnd - iStart + 1; |
| 1007 | TH1_XFER_TAINT(sz, argl[2]); |
| 1008 | |
| 1009 | return Th_SetResult(interp, &argv[2][iStart], sz); |
| 1010 | } |
| 1011 | |
| 1012 | /* |
| 1013 | ** TH Syntax: |
| 1014 | ** |
| @@ -989,27 +1017,33 @@ | |
| 1017 | static int string_repeat_command( |
| 1018 | Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl |
| 1019 | ){ |
| 1020 | int n; |
| 1021 | int i; |
| 1022 | int sz; |
| 1023 | long long int nByte; |
| 1024 | char *zByte; |
| 1025 | |
| 1026 | if( argc!=4 ){ |
| 1027 | return Th_WrongNumArgs(interp, "string repeat string n"); |
| 1028 | } |
| 1029 | if( Th_ToInt(interp, argv[3], argl[3], &n) ){ |
| 1030 | return TH_ERROR; |
| 1031 | } |
| 1032 | |
| 1033 | nByte = n; |
| 1034 | sz = TH1_LEN(argl[2]); |
| 1035 | nByte *= sz; |
| 1036 | TH1_SIZECHECK(nByte+1); |
| 1037 | zByte = Th_Malloc(interp, nByte+1); |
| 1038 | for(i=0; i<nByte; i+=sz){ |
| 1039 | memcpy(&zByte[i], argv[2], sz); |
| 1040 | } |
| 1041 | |
| 1042 | n = nByte; |
| 1043 | TH1_XFER_TAINT(n, argl[2]); |
| 1044 | Th_SetResult(interp, zByte, n); |
| 1045 | Th_Free(interp, zByte); |
| 1046 | return TH_OK; |
| 1047 | } |
| 1048 | |
| 1049 | /* |
| @@ -1027,17 +1061,18 @@ | |
| 1061 | |
| 1062 | if( argc!=3 ){ |
| 1063 | return Th_WrongNumArgs(interp, "string trim string"); |
| 1064 | } |
| 1065 | z = argv[2]; |
| 1066 | n = TH1_LEN(argl[2]); |
| 1067 | if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){ |
| 1068 | while( n && th_isspace(z[0]) ){ z++; n--; } |
| 1069 | } |
| 1070 | if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){ |
| 1071 | while( n && th_isspace(z[n-1]) ){ n--; } |
| 1072 | } |
| 1073 | TH1_XFER_TAINT(n, argl[2]); |
| 1074 | Th_SetResult(interp, z, n); |
| 1075 | return TH_OK; |
| 1076 | } |
| 1077 | |
| 1078 | /* |
| @@ -1051,11 +1086,11 @@ | |
| 1086 | int rc; |
| 1087 | |
| 1088 | if( argc!=3 ){ |
| 1089 | return Th_WrongNumArgs(interp, "info exists var"); |
| 1090 | } |
| 1091 | rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2])); |
| 1092 | Th_SetResultInt(interp, rc); |
| 1093 | return TH_OK; |
| 1094 | } |
| 1095 | |
| 1096 | /* |
| @@ -1117,11 +1152,11 @@ | |
| 1152 | int rc; |
| 1153 | |
| 1154 | if( argc!=3 ){ |
| 1155 | return Th_WrongNumArgs(interp, "array exists var"); |
| 1156 | } |
| 1157 | rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2])); |
| 1158 | Th_SetResultInt(interp, rc); |
| 1159 | return TH_OK; |
| 1160 | } |
| 1161 | |
| 1162 | /* |
| @@ -1137,11 +1172,11 @@ | |
| 1172 | int nElem = 0; |
| 1173 | |
| 1174 | if( argc!=3 ){ |
| 1175 | return Th_WrongNumArgs(interp, "array names varname"); |
| 1176 | } |
| 1177 | rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem); |
| 1178 | if( rc!=TH_OK ){ |
| 1179 | return rc; |
| 1180 | } |
| 1181 | Th_SetResult(interp, zElem, nElem); |
| 1182 | if( zElem ) Th_Free(interp, zElem); |
| @@ -1161,11 +1196,11 @@ | |
| 1196 | int *argl |
| 1197 | ){ |
| 1198 | if( argc!=2 ){ |
| 1199 | return Th_WrongNumArgs(interp, "unset var"); |
| 1200 | } |
| 1201 | return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1])); |
| 1202 | } |
| 1203 | |
| 1204 | int Th_CallSubCommand( |
| 1205 | Th_Interp *interp, |
| 1206 | void *ctx, |
| @@ -1176,19 +1211,22 @@ | |
| 1211 | ){ |
| 1212 | if( argc>1 ){ |
| 1213 | int i; |
| 1214 | for(i=0; aSub[i].zName; i++){ |
| 1215 | const char *zName = aSub[i].zName; |
| 1216 | if( th_strlen(zName)==TH1_LEN(argl[1]) |
| 1217 | && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){ |
| 1218 | return aSub[i].xProc(interp, ctx, argc, argv, argl); |
| 1219 | } |
| 1220 | } |
| 1221 | } |
| 1222 | if(argc<2){ |
| 1223 | Th_ErrorMessage(interp, "Expected sub-command for", |
| 1224 | argv[0], TH1_LEN(argl[0])); |
| 1225 | }else{ |
| 1226 | Th_ErrorMessage(interp, "Expected sub-command, got:", |
| 1227 | argv[1], TH1_LEN(argl[1])); |
| 1228 | } |
| 1229 | return TH_ERROR; |
| 1230 | } |
| 1231 | |
| 1232 | /* |
| @@ -1319,11 +1357,11 @@ | |
| 1357 | int iFrame = -1; |
| 1358 | |
| 1359 | if( argc!=2 && argc!=3 ){ |
| 1360 | return Th_WrongNumArgs(interp, "uplevel ?level? script..."); |
| 1361 | } |
| 1362 | if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){ |
| 1363 | return TH_ERROR; |
| 1364 | } |
| 1365 | return Th_Eval(interp, iFrame, argv[argc-1], -1); |
| 1366 | } |
| 1367 | |
| @@ -1342,19 +1380,20 @@ | |
| 1380 | int iVar = 1; |
| 1381 | int iFrame = -1; |
| 1382 | int rc = TH_OK; |
| 1383 | int i; |
| 1384 | |
| 1385 | if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){ |
| 1386 | iVar++; |
| 1387 | } |
| 1388 | if( argc==iVar || (argc-iVar)%2 ){ |
| 1389 | return Th_WrongNumArgs(interp, |
| 1390 | "upvar frame othervar myvar ?othervar myvar...?"); |
| 1391 | } |
| 1392 | for(i=iVar; rc==TH_OK && i<argc; i=i+2){ |
| 1393 | rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]), |
| 1394 | iFrame, argv[i], TH1_LEN(argl[i])); |
| 1395 | } |
| 1396 | return rc; |
| 1397 | } |
| 1398 | |
| 1399 | /* |
| 1400 |
+209
-89
| --- src/th_main.c | ||
| +++ src/th_main.c | ||
| @@ -31,13 +31,11 @@ | ||
| 31 | 31 | #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ |
| 32 | 32 | #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ |
| 33 | 33 | #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ |
| 34 | 34 | #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ |
| 35 | 35 | #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */ |
| 36 | -#define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/ | |
| 37 | - /* output. */ | |
| 38 | -#define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */ | |
| 36 | +#define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */ | |
| 39 | 37 | |
| 40 | 38 | /* |
| 41 | 39 | ** Useful and/or "well-known" combinations of flag values. |
| 42 | 40 | */ |
| 43 | 41 | #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ |
| @@ -262,11 +260,11 @@ | ||
| 262 | 260 | ){ |
| 263 | 261 | char *zOut; |
| 264 | 262 | if( argc!=2 ){ |
| 265 | 263 | return Th_WrongNumArgs(interp, "httpize STRING"); |
| 266 | 264 | } |
| 267 | - zOut = httpize((char*)argv[1], argl[1]); | |
| 265 | + zOut = httpize((char*)argv[1], TH1_LEN(argl[1])); | |
| 268 | 266 | Th_SetResult(interp, zOut, -1); |
| 269 | 267 | free(zOut); |
| 270 | 268 | return TH_OK; |
| 271 | 269 | } |
| 272 | 270 | |
| @@ -291,51 +289,12 @@ | ||
| 291 | 289 | if( argc<2 || argc>3 ){ |
| 292 | 290 | return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); |
| 293 | 291 | } |
| 294 | 292 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); |
| 295 | 293 | if( g.thTrace ){ |
| 296 | - Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput); | |
| 297 | - } | |
| 298 | - return rc; | |
| 299 | -} | |
| 300 | - | |
| 301 | -/* | |
| 302 | -** TH1 command: enable_htmlify ?BOOLEAN? | |
| 303 | -** | |
| 304 | -** Enable or disable the HTML escaping done by all output which | |
| 305 | -** originates from TH1 (via sendText()). | |
| 306 | -** | |
| 307 | -** If passed no arguments it instead returns 0 or 1 to indicate the | |
| 308 | -** current state. | |
| 309 | -*/ | |
| 310 | -static int enableHtmlifyCmd( | |
| 311 | - Th_Interp *interp, | |
| 312 | - void *p, | |
| 313 | - int argc, | |
| 314 | - const char **argv, | |
| 315 | - int *argl | |
| 316 | -){ | |
| 317 | - int rc = 0, buul; | |
| 318 | - if( argc>3 ){ | |
| 319 | - return Th_WrongNumArgs(interp, | |
| 320 | - "enable_htmlify [TRACE_LABEL] ?BOOLEAN?"); | |
| 321 | - } | |
| 322 | - buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1; | |
| 323 | - Th_SetResultInt(g.interp, buul); | |
| 324 | - if(argc>1){ | |
| 325 | - if( g.thTrace ){ | |
| 326 | - Th_Trace("enable_htmlify {%.*s} -> %d<br>\n", | |
| 327 | - argl[1],argv[1],buul); | |
| 328 | - } | |
| 329 | - rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul); | |
| 330 | - if(!rc){ | |
| 331 | - if(buul){ | |
| 332 | - g.th1Flags &= ~TH_INIT_NO_ENCODE; | |
| 333 | - }else{ | |
| 334 | - g.th1Flags |= TH_INIT_NO_ENCODE; | |
| 335 | - } | |
| 336 | - } | |
| 294 | + Th_Trace("enable_output {%.*s} -> %d<br>\n", | |
| 295 | + TH1_LEN(argl[1]),argv[1],enableOutput); | |
| 337 | 296 | } |
| 338 | 297 | return rc; |
| 339 | 298 | } |
| 340 | 299 | |
| 341 | 300 | /* |
| @@ -375,25 +334,25 @@ | ||
| 375 | 334 | |
| 376 | 335 | /* |
| 377 | 336 | ** Send text to the appropriate output: If pOut is not NULL, it is |
| 378 | 337 | ** appended there, else to the console or to the CGI reply buffer. |
| 379 | 338 | ** Escape all characters with special meaning to HTML if the encode |
| 380 | -** parameter is true, with the exception that that flag is ignored if | |
| 381 | -** g.th1Flags has the TH_INIT_NO_ENCODE flag. | |
| 339 | +** parameter is true. | |
| 382 | 340 | ** |
| 383 | 341 | ** If pOut is NULL and the global pThOut is not then that blob |
| 384 | 342 | ** is used for output. |
| 385 | 343 | */ |
| 386 | -static void sendText(Blob * pOut, const char *z, int n, int encode){ | |
| 344 | +static void sendText(Blob *pOut, const char *z, int n, int encode){ | |
| 387 | 345 | if(0==pOut && pThOut!=0){ |
| 388 | 346 | pOut = pThOut; |
| 389 | 347 | } |
| 390 | - if(TH_INIT_NO_ENCODE & g.th1Flags){ | |
| 391 | - encode = 0; | |
| 392 | - } | |
| 393 | 348 | if( enableOutput && n ){ |
| 394 | - if( n<0 ) n = strlen(z); | |
| 349 | + if( n<0 ){ | |
| 350 | + n = strlen(z); | |
| 351 | + }else{ | |
| 352 | + n = TH1_LEN(n); | |
| 353 | + } | |
| 395 | 354 | if( encode ){ |
| 396 | 355 | z = htmlize(z, n); |
| 397 | 356 | n = strlen(z); |
| 398 | 357 | } |
| 399 | 358 | if(pOut!=0){ |
| @@ -525,14 +484,22 @@ | ||
| 525 | 484 | void *pConvert, |
| 526 | 485 | int argc, |
| 527 | 486 | const char **argv, |
| 528 | 487 | int *argl |
| 529 | 488 | ){ |
| 489 | + int encode = *(unsigned int*)pConvert; | |
| 490 | + int n; | |
| 530 | 491 | if( argc!=2 ){ |
| 531 | 492 | return Th_WrongNumArgs(interp, "puts STRING"); |
| 532 | 493 | } |
| 533 | - sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert); | |
| 494 | + n = argl[1]; | |
| 495 | + if( encode==0 && n>0 && TH1_TAINTED(n) ){ | |
| 496 | + if( Th_ReportTaint(interp, "output string", argv[1], n) ){ | |
| 497 | + return TH_ERROR; | |
| 498 | + } | |
| 499 | + } | |
| 500 | + sendText(0,(char*)argv[1], TH1_LEN(n), encode); | |
| 534 | 501 | return TH_OK; |
| 535 | 502 | } |
| 536 | 503 | |
| 537 | 504 | /* |
| 538 | 505 | ** TH1 command: redirect URL ?withMethod? |
| @@ -557,10 +524,15 @@ | ||
| 557 | 524 | } |
| 558 | 525 | if( argc==3 ){ |
| 559 | 526 | if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ |
| 560 | 527 | return TH_ERROR; |
| 561 | 528 | } |
| 529 | + } | |
| 530 | + if( TH1_TAINTED(argl[1]) | |
| 531 | + && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1]) | |
| 532 | + ){ | |
| 533 | + return TH_ERROR; | |
| 562 | 534 | } |
| 563 | 535 | if( withMethod ){ |
| 564 | 536 | cgi_redirect_with_method(argv[1]); |
| 565 | 537 | }else{ |
| 566 | 538 | cgi_redirect(argv[1]); |
| @@ -660,11 +632,11 @@ | ||
| 660 | 632 | int nValue = 0; |
| 661 | 633 | if( argc!=2 ){ |
| 662 | 634 | return Th_WrongNumArgs(interp, "markdown STRING"); |
| 663 | 635 | } |
| 664 | 636 | blob_zero(&src); |
| 665 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 637 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 666 | 638 | blob_zero(&title); blob_zero(&body); |
| 667 | 639 | markdown_to_html(&src, &title, &body); |
| 668 | 640 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); |
| 669 | 641 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); |
| 670 | 642 | Th_SetResult(interp, zValue, nValue); |
| @@ -690,11 +662,11 @@ | ||
| 690 | 662 | if( argc!=2 ){ |
| 691 | 663 | return Th_WrongNumArgs(interp, "wiki STRING"); |
| 692 | 664 | } |
| 693 | 665 | if( enableOutput ){ |
| 694 | 666 | Blob src; |
| 695 | - blob_init(&src, (char*)argv[1], argl[1]); | |
| 667 | + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); | |
| 696 | 668 | wiki_convert(&src, 0, flags); |
| 697 | 669 | blob_reset(&src); |
| 698 | 670 | } |
| 699 | 671 | return TH_OK; |
| 700 | 672 | } |
| @@ -735,11 +707,11 @@ | ||
| 735 | 707 | ){ |
| 736 | 708 | char *zOut; |
| 737 | 709 | if( argc!=2 ){ |
| 738 | 710 | return Th_WrongNumArgs(interp, "htmlize STRING"); |
| 739 | 711 | } |
| 740 | - zOut = htmlize((char*)argv[1], argl[1]); | |
| 712 | + zOut = htmlize((char*)argv[1], TH1_LEN(argl[1])); | |
| 741 | 713 | Th_SetResult(interp, zOut, -1); |
| 742 | 714 | free(zOut); |
| 743 | 715 | return TH_OK; |
| 744 | 716 | } |
| 745 | 717 | |
| @@ -757,11 +729,11 @@ | ||
| 757 | 729 | ){ |
| 758 | 730 | char *zOut; |
| 759 | 731 | if( argc!=2 ){ |
| 760 | 732 | return Th_WrongNumArgs(interp, "encode64 STRING"); |
| 761 | 733 | } |
| 762 | - zOut = encode64((char*)argv[1], argl[1]); | |
| 734 | + zOut = encode64((char*)argv[1], TH1_LEN(argl[1])); | |
| 763 | 735 | Th_SetResult(interp, zOut, -1); |
| 764 | 736 | free(zOut); |
| 765 | 737 | return TH_OK; |
| 766 | 738 | } |
| 767 | 739 | |
| @@ -778,11 +750,11 @@ | ||
| 778 | 750 | int argc, |
| 779 | 751 | const char **argv, |
| 780 | 752 | int *argl |
| 781 | 753 | ){ |
| 782 | 754 | char *zOut; |
| 783 | - if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){ | |
| 755 | + if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){ | |
| 784 | 756 | zOut = db_text("??", "SELECT datetime('now',toLocal())"); |
| 785 | 757 | }else{ |
| 786 | 758 | zOut = db_text("??", "SELECT datetime('now')"); |
| 787 | 759 | } |
| 788 | 760 | Th_SetResult(interp, zOut, -1); |
| @@ -810,13 +782,13 @@ | ||
| 810 | 782 | if( argc<2 ){ |
| 811 | 783 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 812 | 784 | } |
| 813 | 785 | for(i=1; rc==1 && i<argc; i++){ |
| 814 | 786 | if( g.thTrace ){ |
| 815 | - Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]); | |
| 787 | + Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i])); | |
| 816 | 788 | } |
| 817 | - rc = login_has_capability((char*)argv[i],argl[i],*(int*)p); | |
| 789 | + rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p); | |
| 818 | 790 | } |
| 819 | 791 | if( g.thTrace ){ |
| 820 | 792 | Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); |
| 821 | 793 | Th_Free(interp, zCapList); |
| 822 | 794 | } |
| @@ -858,11 +830,11 @@ | ||
| 858 | 830 | int i; |
| 859 | 831 | |
| 860 | 832 | if( argc!=2 ){ |
| 861 | 833 | return Th_WrongNumArgs(interp, "capexpr EXPR"); |
| 862 | 834 | } |
| 863 | - rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap); | |
| 835 | + rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap); | |
| 864 | 836 | if( rc ) return rc; |
| 865 | 837 | rc = 0; |
| 866 | 838 | for(i=0; i<nCap; i++){ |
| 867 | 839 | if( azCap[i][0]=='!' ){ |
| 868 | 840 | rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); |
| @@ -921,11 +893,12 @@ | ||
| 921 | 893 | if( argc<2 ){ |
| 922 | 894 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 923 | 895 | } |
| 924 | 896 | for(i=1; i<argc && rc; i++){ |
| 925 | 897 | int match = 0; |
| 926 | - for(j=0; j<argl[i]; j++){ | |
| 898 | + int nn = TH1_LEN(argl[i]); | |
| 899 | + for(j=0; j<nn; j++){ | |
| 927 | 900 | switch( argv[i][j] ){ |
| 928 | 901 | case 'c': match |= searchCap & SRCH_CKIN; break; |
| 929 | 902 | case 'd': match |= searchCap & SRCH_DOC; break; |
| 930 | 903 | case 't': match |= searchCap & SRCH_TKT; break; |
| 931 | 904 | case 'w': match |= searchCap & SRCH_WIKI; break; |
| @@ -932,11 +905,11 @@ | ||
| 932 | 905 | } |
| 933 | 906 | } |
| 934 | 907 | if( !match ) rc = 0; |
| 935 | 908 | } |
| 936 | 909 | if( g.thTrace ){ |
| 937 | - Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc); | |
| 910 | + Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); | |
| 938 | 911 | } |
| 939 | 912 | Th_SetResultInt(interp, rc); |
| 940 | 913 | return TH_OK; |
| 941 | 914 | } |
| 942 | 915 | |
| @@ -1051,11 +1024,11 @@ | ||
| 1051 | 1024 | #endif |
| 1052 | 1025 | else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ |
| 1053 | 1026 | rc = 1; |
| 1054 | 1027 | } |
| 1055 | 1028 | if( g.thTrace ){ |
| 1056 | - Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc); | |
| 1029 | + Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc); | |
| 1057 | 1030 | } |
| 1058 | 1031 | Th_SetResultInt(interp, rc); |
| 1059 | 1032 | return TH_OK; |
| 1060 | 1033 | } |
| 1061 | 1034 | |
| @@ -1104,18 +1077,20 @@ | ||
| 1104 | 1077 | const char **argv, |
| 1105 | 1078 | int *argl |
| 1106 | 1079 | ){ |
| 1107 | 1080 | int rc = 0; |
| 1108 | 1081 | int i; |
| 1082 | + int nn; | |
| 1109 | 1083 | if( argc!=2 ){ |
| 1110 | 1084 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 1111 | 1085 | } |
| 1112 | - for(i=0; rc==0 && i<argl[1]; i++){ | |
| 1086 | + nn = TH1_LEN(argl[1]); | |
| 1087 | + for(i=0; rc==0 && i<nn; i++){ | |
| 1113 | 1088 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 1114 | 1089 | } |
| 1115 | 1090 | if( g.thTrace ){ |
| 1116 | - Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc); | |
| 1091 | + Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); | |
| 1117 | 1092 | } |
| 1118 | 1093 | Th_SetResultInt(interp, rc); |
| 1119 | 1094 | return TH_OK; |
| 1120 | 1095 | } |
| 1121 | 1096 | |
| @@ -1140,22 +1115,23 @@ | ||
| 1140 | 1115 | return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); |
| 1141 | 1116 | } |
| 1142 | 1117 | if( enableOutput ){ |
| 1143 | 1118 | int height; |
| 1144 | 1119 | Blob name; |
| 1145 | - int nValue; | |
| 1120 | + int nValue = 0; | |
| 1146 | 1121 | const char *zValue; |
| 1147 | 1122 | char *z, *zH; |
| 1148 | 1123 | int nElem; |
| 1149 | 1124 | int *aszElem; |
| 1150 | 1125 | char **azElem; |
| 1151 | 1126 | int i; |
| 1152 | 1127 | |
| 1153 | 1128 | if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; |
| 1154 | - Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem); | |
| 1155 | - blob_init(&name, (char*)argv[1], argl[1]); | |
| 1129 | + Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem); | |
| 1130 | + blob_init(&name, (char*)argv[1], TH1_LEN(argl[1])); | |
| 1156 | 1131 | zValue = Th_Fetch(blob_str(&name), &nValue); |
| 1132 | + nValue = TH1_LEN(nValue); | |
| 1157 | 1133 | zH = htmlize(blob_buffer(&name), blob_size(&name)); |
| 1158 | 1134 | z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); |
| 1159 | 1135 | free(zH); |
| 1160 | 1136 | sendText(0,z, -1, 0); |
| 1161 | 1137 | free(z); |
| @@ -1247,11 +1223,11 @@ | ||
| 1247 | 1223 | return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); |
| 1248 | 1224 | } |
| 1249 | 1225 | if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; |
| 1250 | 1226 | if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; |
| 1251 | 1227 | z = argv[1]; |
| 1252 | - size = argl[1]; | |
| 1228 | + size = TH1_LEN(argl[1]); | |
| 1253 | 1229 | for(n=1, i=0; i<size; i++){ |
| 1254 | 1230 | if( z[i]=='\n' ){ |
| 1255 | 1231 | n++; |
| 1256 | 1232 | if( n>=iMax ) break; |
| 1257 | 1233 | } |
| @@ -1407,11 +1383,12 @@ | ||
| 1407 | 1383 | return TH_OK; |
| 1408 | 1384 | }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ |
| 1409 | 1385 | Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); |
| 1410 | 1386 | return TH_OK; |
| 1411 | 1387 | }else{ |
| 1412 | - Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]); | |
| 1388 | + Th_ErrorMessage(interp, "unsupported global state:", | |
| 1389 | + argv[1], TH1_LEN(argl[1])); | |
| 1413 | 1390 | return TH_ERROR; |
| 1414 | 1391 | } |
| 1415 | 1392 | } |
| 1416 | 1393 | |
| 1417 | 1394 | /* |
| @@ -1426,17 +1403,21 @@ | ||
| 1426 | 1403 | int argc, |
| 1427 | 1404 | const char **argv, |
| 1428 | 1405 | int *argl |
| 1429 | 1406 | ){ |
| 1430 | 1407 | const char *zDefault = 0; |
| 1408 | + const char *zVal; | |
| 1409 | + int sz; | |
| 1431 | 1410 | if( argc!=2 && argc!=3 ){ |
| 1432 | 1411 | return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); |
| 1433 | 1412 | } |
| 1434 | 1413 | if( argc==3 ){ |
| 1435 | 1414 | zDefault = argv[2]; |
| 1436 | 1415 | } |
| 1437 | - Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1); | |
| 1416 | + zVal = cgi_parameter(argv[1], zDefault); | |
| 1417 | + sz = th_strlen(zVal); | |
| 1418 | + Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz)); | |
| 1438 | 1419 | return TH_OK; |
| 1439 | 1420 | } |
| 1440 | 1421 | |
| 1441 | 1422 | /* |
| 1442 | 1423 | ** TH1 command: setParameter NAME VALUE |
| @@ -1848,10 +1829,47 @@ | ||
| 1848 | 1829 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); |
| 1849 | 1830 | Th_SetResult(interp, zUTime, -1); |
| 1850 | 1831 | return TH_OK; |
| 1851 | 1832 | } |
| 1852 | 1833 | |
| 1834 | +/* | |
| 1835 | +** TH1 command: taint STRING | |
| 1836 | +** | |
| 1837 | +** Return a copy of STRING that is marked as tainted. | |
| 1838 | +*/ | |
| 1839 | +static int taintCmd( | |
| 1840 | + Th_Interp *interp, | |
| 1841 | + void *p, | |
| 1842 | + int argc, | |
| 1843 | + const char **argv, | |
| 1844 | + int *argl | |
| 1845 | +){ | |
| 1846 | + if( argc!=2 ){ | |
| 1847 | + return Th_WrongNumArgs(interp, "STRING"); | |
| 1848 | + } | |
| 1849 | + Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1])); | |
| 1850 | + return TH_OK; | |
| 1851 | +} | |
| 1852 | + | |
| 1853 | +/* | |
| 1854 | +** TH1 command: untaint STRING | |
| 1855 | +** | |
| 1856 | +** Return a copy of STRING that is marked as untainted. | |
| 1857 | +*/ | |
| 1858 | +static int untaintCmd( | |
| 1859 | + Th_Interp *interp, | |
| 1860 | + void *p, | |
| 1861 | + int argc, | |
| 1862 | + const char **argv, | |
| 1863 | + int *argl | |
| 1864 | +){ | |
| 1865 | + if( argc!=2 ){ | |
| 1866 | + return Th_WrongNumArgs(interp, "STRING"); | |
| 1867 | + } | |
| 1868 | + Th_SetResult(interp, argv[1], TH1_LEN(argl[1])); | |
| 1869 | + return TH_OK; | |
| 1870 | +} | |
| 1853 | 1871 | |
| 1854 | 1872 | /* |
| 1855 | 1873 | ** TH1 command: randhex N |
| 1856 | 1874 | ** |
| 1857 | 1875 | ** Return N*2 random hexadecimal digits with N<50. If N is omitted, |
| @@ -1923,11 +1941,13 @@ | ||
| 1923 | 1941 | int res = TH_OK; |
| 1924 | 1942 | int nVar; |
| 1925 | 1943 | char *zErr = 0; |
| 1926 | 1944 | int noComplain = 0; |
| 1927 | 1945 | |
| 1928 | - if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){ | |
| 1946 | + if( argc>3 && TH1_LEN(argl[1])==11 | |
| 1947 | + && strncmp(argv[1], "-nocomplain", 11)==0 | |
| 1948 | + ){ | |
| 1929 | 1949 | argc--; |
| 1930 | 1950 | argv++; |
| 1931 | 1951 | argl++; |
| 1932 | 1952 | noComplain = 1; |
| 1933 | 1953 | } |
| @@ -1939,15 +1959,22 @@ | ||
| 1939 | 1959 | Th_ErrorMessage(interp, "database is not open", 0, 0); |
| 1940 | 1960 | return TH_ERROR; |
| 1941 | 1961 | } |
| 1942 | 1962 | zSql = argv[1]; |
| 1943 | 1963 | nSql = argl[1]; |
| 1964 | + if( TH1_TAINTED(nSql) ){ | |
| 1965 | + if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){ | |
| 1966 | + return TH_ERROR; | |
| 1967 | + } | |
| 1968 | + nSql = TH1_LEN(nSql); | |
| 1969 | + } | |
| 1970 | + | |
| 1944 | 1971 | while( res==TH_OK && nSql>0 ){ |
| 1945 | 1972 | zErr = 0; |
| 1946 | 1973 | report_restrict_sql(&zErr); |
| 1947 | 1974 | g.dbIgnoreErrors++; |
| 1948 | - rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail); | |
| 1975 | + rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail); | |
| 1949 | 1976 | g.dbIgnoreErrors--; |
| 1950 | 1977 | report_unrestrict_sql(); |
| 1951 | 1978 | if( rc!=0 || zErr!=0 ){ |
| 1952 | 1979 | if( noComplain ) return TH_OK; |
| 1953 | 1980 | Th_ErrorMessage(interp, "SQL error: ", |
| @@ -1964,31 +1991,31 @@ | ||
| 1964 | 1991 | int szVar = zVar ? th_strlen(zVar) : 0; |
| 1965 | 1992 | if( szVar>1 && zVar[0]=='$' |
| 1966 | 1993 | && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ |
| 1967 | 1994 | int nVal; |
| 1968 | 1995 | const char *zVal = Th_GetResult(interp, &nVal); |
| 1969 | - sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT); | |
| 1996 | + sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT); | |
| 1970 | 1997 | } |
| 1971 | 1998 | } |
| 1972 | 1999 | while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ |
| 1973 | 2000 | int nCol = sqlite3_column_count(pStmt); |
| 1974 | 2001 | for(i=0; i<nCol; i++){ |
| 1975 | 2002 | const char *zCol = sqlite3_column_name(pStmt, i); |
| 1976 | 2003 | int szCol = th_strlen(zCol); |
| 1977 | 2004 | const char *zVal = (const char*)sqlite3_column_text(pStmt, i); |
| 1978 | 2005 | int szVal = sqlite3_column_bytes(pStmt, i); |
| 1979 | - Th_SetVar(interp, zCol, szCol, zVal, szVal); | |
| 2006 | + Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal)); | |
| 1980 | 2007 | } |
| 1981 | 2008 | if( g.thTrace ){ |
| 1982 | - Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]); | |
| 2009 | + Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]); | |
| 1983 | 2010 | } |
| 1984 | - res = Th_Eval(interp, 0, argv[2], argl[2]); | |
| 2011 | + res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2])); | |
| 1985 | 2012 | if( g.thTrace ){ |
| 1986 | 2013 | int nTrRes; |
| 1987 | 2014 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 1988 | 2015 | Th_Trace("[query_eval] => %h {%#h}<br>\n", |
| 1989 | - Th_ReturnCodeName(res, 0), nTrRes, zTrRes); | |
| 2016 | + Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes); | |
| 1990 | 2017 | } |
| 1991 | 2018 | if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; |
| 1992 | 2019 | } |
| 1993 | 2020 | rc = sqlite3_finalize(pStmt); |
| 1994 | 2021 | if( rc!=SQLITE_OK ){ |
| @@ -2038,11 +2065,11 @@ | ||
| 2038 | 2065 | Th_SetResult(interp, 0, 0); |
| 2039 | 2066 | rc = TH_OK; |
| 2040 | 2067 | } |
| 2041 | 2068 | if( g.thTrace ){ |
| 2042 | 2069 | Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", |
| 2043 | - argl[nArg], argv[nArg], rc); | |
| 2070 | + TH1_LEN(argl[nArg]), argv[nArg], rc); | |
| 2044 | 2071 | } |
| 2045 | 2072 | return rc; |
| 2046 | 2073 | } |
| 2047 | 2074 | |
| 2048 | 2075 | /* |
| @@ -2121,11 +2148,11 @@ | ||
| 2121 | 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2122 | 2149 | } |
| 2123 | 2150 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2124 | 2151 | if( !zErr ){ |
| 2125 | 2152 | Th_SetResultInt(interp, re_match(pRe, |
| 2126 | - (const unsigned char *)argv[nArg+1], argl[nArg+1])); | |
| 2153 | + (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); | |
| 2127 | 2154 | rc = TH_OK; |
| 2128 | 2155 | }else{ |
| 2129 | 2156 | Th_SetResult(interp, zErr, -1); |
| 2130 | 2157 | rc = TH_ERROR; |
| 2131 | 2158 | } |
| @@ -2160,11 +2187,11 @@ | ||
| 2160 | 2187 | UrlData urlData; |
| 2161 | 2188 | |
| 2162 | 2189 | if( argc<2 || argc>5 ){ |
| 2163 | 2190 | return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); |
| 2164 | 2191 | } |
| 2165 | - if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ | |
| 2192 | + if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){ | |
| 2166 | 2193 | fAsynchronous = 1; nArg++; |
| 2167 | 2194 | } |
| 2168 | 2195 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2169 | 2196 | if( nArg+1!=argc && nArg+2!=argc ){ |
| 2170 | 2197 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| @@ -2189,11 +2216,11 @@ | ||
| 2189 | 2216 | return TH_ERROR; |
| 2190 | 2217 | } |
| 2191 | 2218 | re_free(pRe); |
| 2192 | 2219 | blob_zero(&payload); |
| 2193 | 2220 | if( nArg+2==argc ){ |
| 2194 | - blob_append(&payload, argv[nArg+1], argl[nArg+1]); | |
| 2221 | + blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1])); | |
| 2195 | 2222 | zType = "POST"; |
| 2196 | 2223 | }else{ |
| 2197 | 2224 | zType = "GET"; |
| 2198 | 2225 | } |
| 2199 | 2226 | if( fAsynchronous ){ |
| @@ -2268,11 +2295,11 @@ | ||
| 2268 | 2295 | if( argc!=2 ){ |
| 2269 | 2296 | return Th_WrongNumArgs(interp, "captureTh1 STRING"); |
| 2270 | 2297 | } |
| 2271 | 2298 | pOrig = Th_SetOutputBlob(&out); |
| 2272 | 2299 | zStr = argv[1]; |
| 2273 | - nStr = argl[1]; | |
| 2300 | + nStr = TH1_LEN(argl[1]); | |
| 2274 | 2301 | rc = Th_Eval(g.interp, 0, zStr, nStr); |
| 2275 | 2302 | Th_SetOutputBlob(pOrig); |
| 2276 | 2303 | if(0==rc){ |
| 2277 | 2304 | Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); |
| 2278 | 2305 | } |
| @@ -2356,11 +2383,10 @@ | ||
| 2356 | 2383 | {"copybtn", copybtnCmd, 0}, |
| 2357 | 2384 | {"date", dateCmd, 0}, |
| 2358 | 2385 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 2359 | 2386 | {"defHeader", defHeaderCmd, 0}, |
| 2360 | 2387 | {"dir", dirCmd, 0}, |
| 2361 | - {"enable_htmlify",enableHtmlifyCmd, 0}, | |
| 2362 | 2388 | {"enable_output", enableOutputCmd, 0}, |
| 2363 | 2389 | {"encode64", encode64Cmd, 0}, |
| 2364 | 2390 | {"getParameter", getParameterCmd, 0}, |
| 2365 | 2391 | {"glob_match", globMatchCmd, 0}, |
| 2366 | 2392 | {"globalState", globalStateCmd, 0}, |
| @@ -2387,13 +2413,15 @@ | ||
| 2387 | 2413 | {"setting", settingCmd, 0}, |
| 2388 | 2414 | {"styleFooter", styleFooterCmd, 0}, |
| 2389 | 2415 | {"styleHeader", styleHeaderCmd, 0}, |
| 2390 | 2416 | {"styleScript", styleScriptCmd, 0}, |
| 2391 | 2417 | {"submenu", submenuCmd, 0}, |
| 2418 | + {"taint", taintCmd, 0}, | |
| 2392 | 2419 | {"tclReady", tclReadyCmd, 0}, |
| 2393 | 2420 | {"trace", traceCmd, 0}, |
| 2394 | 2421 | {"stime", stimeCmd, 0}, |
| 2422 | + {"untaint", untaintCmd, 0}, | |
| 2395 | 2423 | {"unversioned", unversionedCmd, 0}, |
| 2396 | 2424 | {"utime", utimeCmd, 0}, |
| 2397 | 2425 | {"verifyCsrf", verifyCsrfCmd, 0}, |
| 2398 | 2426 | {"verifyLogin", verifyLoginCmd, 0}, |
| 2399 | 2427 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -2494,10 +2522,26 @@ | ||
| 2494 | 2522 | Th_Trace("set %h {%h}<br>\n", zName, zValue); |
| 2495 | 2523 | } |
| 2496 | 2524 | Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); |
| 2497 | 2525 | } |
| 2498 | 2526 | } |
| 2527 | + | |
| 2528 | +/* | |
| 2529 | +** Store a string value in a variable in the interpreter | |
| 2530 | +** with the "taint" marking, so that TH1 knows that this | |
| 2531 | +** variable contains content under the control of the remote | |
| 2532 | +** user and presents a risk of XSS or SQL-injection attacks. | |
| 2533 | +*/ | |
| 2534 | +void Th_StoreUnsafe(const char *zName, const char *zValue){ | |
| 2535 | + Th_FossilInit(TH_INIT_DEFAULT); | |
| 2536 | + if( zValue ){ | |
| 2537 | + if( g.thTrace ){ | |
| 2538 | + Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue); | |
| 2539 | + } | |
| 2540 | + Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue))); | |
| 2541 | + } | |
| 2542 | +} | |
| 2499 | 2543 | |
| 2500 | 2544 | /* |
| 2501 | 2545 | ** Appends an element to a TH1 list value. This function is called by the |
| 2502 | 2546 | ** transfer subsystem; therefore, it must be very careful to avoid doing |
| 2503 | 2547 | ** any unnecessary work. To that end, the TH1 subsystem will not be called |
| @@ -2680,10 +2724,11 @@ | ||
| 2680 | 2724 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2681 | 2725 | /* |
| 2682 | 2726 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2683 | 2727 | ** command hook handler as that is not actually an error condition. |
| 2684 | 2728 | */ |
| 2729 | + nResult = TH1_LEN(nResult); | |
| 2685 | 2730 | if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ |
| 2686 | 2731 | sendError(0,zResult, nResult, 0); |
| 2687 | 2732 | }else{ |
| 2688 | 2733 | /* |
| 2689 | 2734 | ** There is no command hook handler "installed". This situation |
| @@ -2767,10 +2812,11 @@ | ||
| 2767 | 2812 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2768 | 2813 | /* |
| 2769 | 2814 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2770 | 2815 | ** webpage hook handler as that is not actually an error condition. |
| 2771 | 2816 | */ |
| 2817 | + nResult = TH1_LEN(nResult); | |
| 2772 | 2818 | if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ |
| 2773 | 2819 | sendError(0,zResult, nResult, 1); |
| 2774 | 2820 | }else{ |
| 2775 | 2821 | /* |
| 2776 | 2822 | ** There is no webpage hook handler "installed". This situation |
| @@ -2894,11 +2940,16 @@ | ||
| 2894 | 2940 | } |
| 2895 | 2941 | rc = Th_GetVar(g.interp, (char*)zVar, nVar); |
| 2896 | 2942 | z += i+1+n; |
| 2897 | 2943 | i = 0; |
| 2898 | 2944 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2899 | - sendText(pOut,(char*)zResult, n, encode); | |
| 2945 | + if( !TH1_TAINTED(n) | |
| 2946 | + || encode | |
| 2947 | + || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK | |
| 2948 | + ){ | |
| 2949 | + sendText(pOut,(char*)zResult, n, encode); | |
| 2950 | + } | |
| 2900 | 2951 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 2901 | 2952 | sendText(pOut,z, i, 0); |
| 2902 | 2953 | z += i+5; |
| 2903 | 2954 | for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} |
| 2904 | 2955 | if( g.thTrace ){ |
| @@ -2907,11 +2958,11 @@ | ||
| 2907 | 2958 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 2908 | 2959 | if( g.thTrace ){ |
| 2909 | 2960 | int nTrRes; |
| 2910 | 2961 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2911 | 2962 | Th_Trace("[render_eval] => %h {%#h}<br>\n", |
| 2912 | - Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); | |
| 2963 | + Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes); | |
| 2913 | 2964 | } |
| 2914 | 2965 | if( rc!=TH_OK ) break; |
| 2915 | 2966 | z += i; |
| 2916 | 2967 | if( z[0] ){ z += 6; } |
| 2917 | 2968 | i = 0; |
| @@ -2949,14 +3000,81 @@ | ||
| 2949 | 3000 | ** e.g. via the "render" script function binding, need to use the |
| 2950 | 3001 | ** pThOut blob in order to avoid out-of-order output if |
| 2951 | 3002 | ** Th_SetOutputBlob() has been called. If it has not been called, |
| 2952 | 3003 | ** pThOut will be 0, which will redirect the output to CGI/stdout, |
| 2953 | 3004 | ** as appropriate. We need to pass on g.th1Flags for the case of |
| 2954 | - ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get | |
| 2955 | - ** inadvertently toggled off by a recursive call. | |
| 3005 | + ** recursive calls. | |
| 2956 | 3006 | */; |
| 2957 | 3007 | } |
| 3008 | + | |
| 3009 | +/* | |
| 3010 | +** SETTING: vuln-report width=8 default=log | |
| 3011 | +** | |
| 3012 | +** This setting controls Fossil's behavior when it encounters a potential | |
| 3013 | +** XSS or SQL-injection vulnerability due to misuse of TH1 configuration | |
| 3014 | +** scripts. Choices are: | |
| 3015 | +** | |
| 3016 | +** off Do nothing. Ignore the vulnerability. | |
| 3017 | +** | |
| 3018 | +** log Write a report of the problem into the error log. | |
| 3019 | +** | |
| 3020 | +** block Like "log" but also prevent the offending TH1 command | |
| 3021 | +** from running. | |
| 3022 | +** | |
| 3023 | +** fatal Render an error message page instead of the requested | |
| 3024 | +** page. | |
| 3025 | +*/ | |
| 3026 | + | |
| 3027 | +/* | |
| 3028 | +** Report misuse of a tainted string in TH1. | |
| 3029 | +** | |
| 3030 | +** The behavior depends on the vuln-report setting. If "off", this routine | |
| 3031 | +** is a no-op. Otherwise, right a message into the error log. If | |
| 3032 | +** vuln-report is "log", that is all that happens. But for any other | |
| 3033 | +** value of vuln-report, a fatal error is raised. | |
| 3034 | +*/ | |
| 3035 | +int Th_ReportTaint( | |
| 3036 | + Th_Interp *interp, /* Report error here, if an error is reported */ | |
| 3037 | + const char *zWhere, /* Where the tainted string appears */ | |
| 3038 | + const char *zStr, /* The tainted string */ | |
| 3039 | + int nStr /* Length of the tainted string */ | |
| 3040 | +){ | |
| 3041 | + static const char *zDisp = 0; /* Dispensation; what to do with the error */ | |
| 3042 | + const char *zVulnType; /* Type of vulnerability */ | |
| 3043 | + | |
| 3044 | + if( zDisp==0 ) zDisp = db_get("vuln-report","log"); | |
| 3045 | + if( is_false(zDisp) ) return 0; | |
| 3046 | + if( strstr(zWhere,"SQL")!=0 ){ | |
| 3047 | + zVulnType = "SQL-injection"; | |
| 3048 | + }else{ | |
| 3049 | + zVulnType = "XSS"; | |
| 3050 | + } | |
| 3051 | + nStr = TH1_LEN(nStr); | |
| 3052 | + fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"", | |
| 3053 | + zVulnType, zWhere, nStr, zStr); | |
| 3054 | + if( strcmp(zDisp,"log")==0 ){ | |
| 3055 | + return 0; | |
| 3056 | + } | |
| 3057 | + if( strcmp(zDisp,"block")==0 ){ | |
| 3058 | + char *z = mprintf("tainted %s: \"", zWhere); | |
| 3059 | + Th_ErrorMessage(interp, z, zStr, nStr); | |
| 3060 | + fossil_free(z); | |
| 3061 | + }else{ | |
| 3062 | + char *z = mprintf("%#h", nStr, zStr); | |
| 3063 | + zDisp = "off"; | |
| 3064 | + cgi_reset_content(); | |
| 3065 | + style_submenu_enable(0); | |
| 3066 | + style_set_current_feature("error"); | |
| 3067 | + style_header("Configuration Error"); | |
| 3068 | + @ <p>Error in a TH1 configuration script: | |
| 3069 | + @ tainted %h(zWhere): "%z(z)" | |
| 3070 | + style_finish_page(); | |
| 3071 | + cgi_reply(); | |
| 3072 | + fossil_exit(1); | |
| 3073 | + } | |
| 3074 | + return 1; | |
| 3075 | +} | |
| 2958 | 3076 | |
| 2959 | 3077 | /* |
| 2960 | 3078 | ** COMMAND: test-th-render |
| 2961 | 3079 | ** |
| 2962 | 3080 | ** Usage: %fossil test-th-render FILE |
| @@ -2992,10 +3110,11 @@ | ||
| 2992 | 3110 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 2993 | 3111 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 2994 | 3112 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 2995 | 3113 | g.useLocalauth = 1; |
| 2996 | 3114 | } |
| 3115 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 2997 | 3116 | verify_all_options(); |
| 2998 | 3117 | if( g.argc<3 ){ |
| 2999 | 3118 | usage("FILE"); |
| 3000 | 3119 | } |
| 3001 | 3120 | blob_zero(&in); |
| @@ -3044,10 +3163,11 @@ | ||
| 3044 | 3163 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3045 | 3164 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3046 | 3165 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3047 | 3166 | g.useLocalauth = 1; |
| 3048 | 3167 | } |
| 3168 | + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); | |
| 3049 | 3169 | verify_all_options(); |
| 3050 | 3170 | if( g.argc!=3 ){ |
| 3051 | 3171 | usage("script"); |
| 3052 | 3172 | } |
| 3053 | 3173 | if(file_isfile(g.argv[2], ExtFILE)){ |
| 3054 | 3174 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -31,13 +31,11 @@ | |
| 31 | #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ |
| 32 | #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ |
| 33 | #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ |
| 34 | #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ |
| 35 | #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */ |
| 36 | #define TH_INIT_NO_ENCODE ((u32)0x00000020) /* Do not html-encode sendText()*/ |
| 37 | /* output. */ |
| 38 | #define TH_INIT_MASK ((u32)0x0000003F) /* All possible init flags. */ |
| 39 | |
| 40 | /* |
| 41 | ** Useful and/or "well-known" combinations of flag values. |
| 42 | */ |
| 43 | #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ |
| @@ -262,11 +260,11 @@ | |
| 262 | ){ |
| 263 | char *zOut; |
| 264 | if( argc!=2 ){ |
| 265 | return Th_WrongNumArgs(interp, "httpize STRING"); |
| 266 | } |
| 267 | zOut = httpize((char*)argv[1], argl[1]); |
| 268 | Th_SetResult(interp, zOut, -1); |
| 269 | free(zOut); |
| 270 | return TH_OK; |
| 271 | } |
| 272 | |
| @@ -291,51 +289,12 @@ | |
| 291 | if( argc<2 || argc>3 ){ |
| 292 | return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); |
| 293 | } |
| 294 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); |
| 295 | if( g.thTrace ){ |
| 296 | Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput); |
| 297 | } |
| 298 | return rc; |
| 299 | } |
| 300 | |
| 301 | /* |
| 302 | ** TH1 command: enable_htmlify ?BOOLEAN? |
| 303 | ** |
| 304 | ** Enable or disable the HTML escaping done by all output which |
| 305 | ** originates from TH1 (via sendText()). |
| 306 | ** |
| 307 | ** If passed no arguments it instead returns 0 or 1 to indicate the |
| 308 | ** current state. |
| 309 | */ |
| 310 | static int enableHtmlifyCmd( |
| 311 | Th_Interp *interp, |
| 312 | void *p, |
| 313 | int argc, |
| 314 | const char **argv, |
| 315 | int *argl |
| 316 | ){ |
| 317 | int rc = 0, buul; |
| 318 | if( argc>3 ){ |
| 319 | return Th_WrongNumArgs(interp, |
| 320 | "enable_htmlify [TRACE_LABEL] ?BOOLEAN?"); |
| 321 | } |
| 322 | buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1; |
| 323 | Th_SetResultInt(g.interp, buul); |
| 324 | if(argc>1){ |
| 325 | if( g.thTrace ){ |
| 326 | Th_Trace("enable_htmlify {%.*s} -> %d<br>\n", |
| 327 | argl[1],argv[1],buul); |
| 328 | } |
| 329 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul); |
| 330 | if(!rc){ |
| 331 | if(buul){ |
| 332 | g.th1Flags &= ~TH_INIT_NO_ENCODE; |
| 333 | }else{ |
| 334 | g.th1Flags |= TH_INIT_NO_ENCODE; |
| 335 | } |
| 336 | } |
| 337 | } |
| 338 | return rc; |
| 339 | } |
| 340 | |
| 341 | /* |
| @@ -375,25 +334,25 @@ | |
| 375 | |
| 376 | /* |
| 377 | ** Send text to the appropriate output: If pOut is not NULL, it is |
| 378 | ** appended there, else to the console or to the CGI reply buffer. |
| 379 | ** Escape all characters with special meaning to HTML if the encode |
| 380 | ** parameter is true, with the exception that that flag is ignored if |
| 381 | ** g.th1Flags has the TH_INIT_NO_ENCODE flag. |
| 382 | ** |
| 383 | ** If pOut is NULL and the global pThOut is not then that blob |
| 384 | ** is used for output. |
| 385 | */ |
| 386 | static void sendText(Blob * pOut, const char *z, int n, int encode){ |
| 387 | if(0==pOut && pThOut!=0){ |
| 388 | pOut = pThOut; |
| 389 | } |
| 390 | if(TH_INIT_NO_ENCODE & g.th1Flags){ |
| 391 | encode = 0; |
| 392 | } |
| 393 | if( enableOutput && n ){ |
| 394 | if( n<0 ) n = strlen(z); |
| 395 | if( encode ){ |
| 396 | z = htmlize(z, n); |
| 397 | n = strlen(z); |
| 398 | } |
| 399 | if(pOut!=0){ |
| @@ -525,14 +484,22 @@ | |
| 525 | void *pConvert, |
| 526 | int argc, |
| 527 | const char **argv, |
| 528 | int *argl |
| 529 | ){ |
| 530 | if( argc!=2 ){ |
| 531 | return Th_WrongNumArgs(interp, "puts STRING"); |
| 532 | } |
| 533 | sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert); |
| 534 | return TH_OK; |
| 535 | } |
| 536 | |
| 537 | /* |
| 538 | ** TH1 command: redirect URL ?withMethod? |
| @@ -557,10 +524,15 @@ | |
| 557 | } |
| 558 | if( argc==3 ){ |
| 559 | if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ |
| 560 | return TH_ERROR; |
| 561 | } |
| 562 | } |
| 563 | if( withMethod ){ |
| 564 | cgi_redirect_with_method(argv[1]); |
| 565 | }else{ |
| 566 | cgi_redirect(argv[1]); |
| @@ -660,11 +632,11 @@ | |
| 660 | int nValue = 0; |
| 661 | if( argc!=2 ){ |
| 662 | return Th_WrongNumArgs(interp, "markdown STRING"); |
| 663 | } |
| 664 | blob_zero(&src); |
| 665 | blob_init(&src, (char*)argv[1], argl[1]); |
| 666 | blob_zero(&title); blob_zero(&body); |
| 667 | markdown_to_html(&src, &title, &body); |
| 668 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); |
| 669 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); |
| 670 | Th_SetResult(interp, zValue, nValue); |
| @@ -690,11 +662,11 @@ | |
| 690 | if( argc!=2 ){ |
| 691 | return Th_WrongNumArgs(interp, "wiki STRING"); |
| 692 | } |
| 693 | if( enableOutput ){ |
| 694 | Blob src; |
| 695 | blob_init(&src, (char*)argv[1], argl[1]); |
| 696 | wiki_convert(&src, 0, flags); |
| 697 | blob_reset(&src); |
| 698 | } |
| 699 | return TH_OK; |
| 700 | } |
| @@ -735,11 +707,11 @@ | |
| 735 | ){ |
| 736 | char *zOut; |
| 737 | if( argc!=2 ){ |
| 738 | return Th_WrongNumArgs(interp, "htmlize STRING"); |
| 739 | } |
| 740 | zOut = htmlize((char*)argv[1], argl[1]); |
| 741 | Th_SetResult(interp, zOut, -1); |
| 742 | free(zOut); |
| 743 | return TH_OK; |
| 744 | } |
| 745 | |
| @@ -757,11 +729,11 @@ | |
| 757 | ){ |
| 758 | char *zOut; |
| 759 | if( argc!=2 ){ |
| 760 | return Th_WrongNumArgs(interp, "encode64 STRING"); |
| 761 | } |
| 762 | zOut = encode64((char*)argv[1], argl[1]); |
| 763 | Th_SetResult(interp, zOut, -1); |
| 764 | free(zOut); |
| 765 | return TH_OK; |
| 766 | } |
| 767 | |
| @@ -778,11 +750,11 @@ | |
| 778 | int argc, |
| 779 | const char **argv, |
| 780 | int *argl |
| 781 | ){ |
| 782 | char *zOut; |
| 783 | if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){ |
| 784 | zOut = db_text("??", "SELECT datetime('now',toLocal())"); |
| 785 | }else{ |
| 786 | zOut = db_text("??", "SELECT datetime('now')"); |
| 787 | } |
| 788 | Th_SetResult(interp, zOut, -1); |
| @@ -810,13 +782,13 @@ | |
| 810 | if( argc<2 ){ |
| 811 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 812 | } |
| 813 | for(i=1; rc==1 && i<argc; i++){ |
| 814 | if( g.thTrace ){ |
| 815 | Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]); |
| 816 | } |
| 817 | rc = login_has_capability((char*)argv[i],argl[i],*(int*)p); |
| 818 | } |
| 819 | if( g.thTrace ){ |
| 820 | Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); |
| 821 | Th_Free(interp, zCapList); |
| 822 | } |
| @@ -858,11 +830,11 @@ | |
| 858 | int i; |
| 859 | |
| 860 | if( argc!=2 ){ |
| 861 | return Th_WrongNumArgs(interp, "capexpr EXPR"); |
| 862 | } |
| 863 | rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap); |
| 864 | if( rc ) return rc; |
| 865 | rc = 0; |
| 866 | for(i=0; i<nCap; i++){ |
| 867 | if( azCap[i][0]=='!' ){ |
| 868 | rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); |
| @@ -921,11 +893,12 @@ | |
| 921 | if( argc<2 ){ |
| 922 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 923 | } |
| 924 | for(i=1; i<argc && rc; i++){ |
| 925 | int match = 0; |
| 926 | for(j=0; j<argl[i]; j++){ |
| 927 | switch( argv[i][j] ){ |
| 928 | case 'c': match |= searchCap & SRCH_CKIN; break; |
| 929 | case 'd': match |= searchCap & SRCH_DOC; break; |
| 930 | case 't': match |= searchCap & SRCH_TKT; break; |
| 931 | case 'w': match |= searchCap & SRCH_WIKI; break; |
| @@ -932,11 +905,11 @@ | |
| 932 | } |
| 933 | } |
| 934 | if( !match ) rc = 0; |
| 935 | } |
| 936 | if( g.thTrace ){ |
| 937 | Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc); |
| 938 | } |
| 939 | Th_SetResultInt(interp, rc); |
| 940 | return TH_OK; |
| 941 | } |
| 942 | |
| @@ -1051,11 +1024,11 @@ | |
| 1051 | #endif |
| 1052 | else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ |
| 1053 | rc = 1; |
| 1054 | } |
| 1055 | if( g.thTrace ){ |
| 1056 | Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc); |
| 1057 | } |
| 1058 | Th_SetResultInt(interp, rc); |
| 1059 | return TH_OK; |
| 1060 | } |
| 1061 | |
| @@ -1104,18 +1077,20 @@ | |
| 1104 | const char **argv, |
| 1105 | int *argl |
| 1106 | ){ |
| 1107 | int rc = 0; |
| 1108 | int i; |
| 1109 | if( argc!=2 ){ |
| 1110 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 1111 | } |
| 1112 | for(i=0; rc==0 && i<argl[1]; i++){ |
| 1113 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 1114 | } |
| 1115 | if( g.thTrace ){ |
| 1116 | Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc); |
| 1117 | } |
| 1118 | Th_SetResultInt(interp, rc); |
| 1119 | return TH_OK; |
| 1120 | } |
| 1121 | |
| @@ -1140,22 +1115,23 @@ | |
| 1140 | return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); |
| 1141 | } |
| 1142 | if( enableOutput ){ |
| 1143 | int height; |
| 1144 | Blob name; |
| 1145 | int nValue; |
| 1146 | const char *zValue; |
| 1147 | char *z, *zH; |
| 1148 | int nElem; |
| 1149 | int *aszElem; |
| 1150 | char **azElem; |
| 1151 | int i; |
| 1152 | |
| 1153 | if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; |
| 1154 | Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem); |
| 1155 | blob_init(&name, (char*)argv[1], argl[1]); |
| 1156 | zValue = Th_Fetch(blob_str(&name), &nValue); |
| 1157 | zH = htmlize(blob_buffer(&name), blob_size(&name)); |
| 1158 | z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); |
| 1159 | free(zH); |
| 1160 | sendText(0,z, -1, 0); |
| 1161 | free(z); |
| @@ -1247,11 +1223,11 @@ | |
| 1247 | return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); |
| 1248 | } |
| 1249 | if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; |
| 1250 | if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; |
| 1251 | z = argv[1]; |
| 1252 | size = argl[1]; |
| 1253 | for(n=1, i=0; i<size; i++){ |
| 1254 | if( z[i]=='\n' ){ |
| 1255 | n++; |
| 1256 | if( n>=iMax ) break; |
| 1257 | } |
| @@ -1407,11 +1383,12 @@ | |
| 1407 | return TH_OK; |
| 1408 | }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ |
| 1409 | Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); |
| 1410 | return TH_OK; |
| 1411 | }else{ |
| 1412 | Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]); |
| 1413 | return TH_ERROR; |
| 1414 | } |
| 1415 | } |
| 1416 | |
| 1417 | /* |
| @@ -1426,17 +1403,21 @@ | |
| 1426 | int argc, |
| 1427 | const char **argv, |
| 1428 | int *argl |
| 1429 | ){ |
| 1430 | const char *zDefault = 0; |
| 1431 | if( argc!=2 && argc!=3 ){ |
| 1432 | return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); |
| 1433 | } |
| 1434 | if( argc==3 ){ |
| 1435 | zDefault = argv[2]; |
| 1436 | } |
| 1437 | Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1); |
| 1438 | return TH_OK; |
| 1439 | } |
| 1440 | |
| 1441 | /* |
| 1442 | ** TH1 command: setParameter NAME VALUE |
| @@ -1848,10 +1829,47 @@ | |
| 1848 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); |
| 1849 | Th_SetResult(interp, zUTime, -1); |
| 1850 | return TH_OK; |
| 1851 | } |
| 1852 | |
| 1853 | |
| 1854 | /* |
| 1855 | ** TH1 command: randhex N |
| 1856 | ** |
| 1857 | ** Return N*2 random hexadecimal digits with N<50. If N is omitted, |
| @@ -1923,11 +1941,13 @@ | |
| 1923 | int res = TH_OK; |
| 1924 | int nVar; |
| 1925 | char *zErr = 0; |
| 1926 | int noComplain = 0; |
| 1927 | |
| 1928 | if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){ |
| 1929 | argc--; |
| 1930 | argv++; |
| 1931 | argl++; |
| 1932 | noComplain = 1; |
| 1933 | } |
| @@ -1939,15 +1959,22 @@ | |
| 1939 | Th_ErrorMessage(interp, "database is not open", 0, 0); |
| 1940 | return TH_ERROR; |
| 1941 | } |
| 1942 | zSql = argv[1]; |
| 1943 | nSql = argl[1]; |
| 1944 | while( res==TH_OK && nSql>0 ){ |
| 1945 | zErr = 0; |
| 1946 | report_restrict_sql(&zErr); |
| 1947 | g.dbIgnoreErrors++; |
| 1948 | rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail); |
| 1949 | g.dbIgnoreErrors--; |
| 1950 | report_unrestrict_sql(); |
| 1951 | if( rc!=0 || zErr!=0 ){ |
| 1952 | if( noComplain ) return TH_OK; |
| 1953 | Th_ErrorMessage(interp, "SQL error: ", |
| @@ -1964,31 +1991,31 @@ | |
| 1964 | int szVar = zVar ? th_strlen(zVar) : 0; |
| 1965 | if( szVar>1 && zVar[0]=='$' |
| 1966 | && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ |
| 1967 | int nVal; |
| 1968 | const char *zVal = Th_GetResult(interp, &nVal); |
| 1969 | sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT); |
| 1970 | } |
| 1971 | } |
| 1972 | while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ |
| 1973 | int nCol = sqlite3_column_count(pStmt); |
| 1974 | for(i=0; i<nCol; i++){ |
| 1975 | const char *zCol = sqlite3_column_name(pStmt, i); |
| 1976 | int szCol = th_strlen(zCol); |
| 1977 | const char *zVal = (const char*)sqlite3_column_text(pStmt, i); |
| 1978 | int szVal = sqlite3_column_bytes(pStmt, i); |
| 1979 | Th_SetVar(interp, zCol, szCol, zVal, szVal); |
| 1980 | } |
| 1981 | if( g.thTrace ){ |
| 1982 | Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]); |
| 1983 | } |
| 1984 | res = Th_Eval(interp, 0, argv[2], argl[2]); |
| 1985 | if( g.thTrace ){ |
| 1986 | int nTrRes; |
| 1987 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 1988 | Th_Trace("[query_eval] => %h {%#h}<br>\n", |
| 1989 | Th_ReturnCodeName(res, 0), nTrRes, zTrRes); |
| 1990 | } |
| 1991 | if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; |
| 1992 | } |
| 1993 | rc = sqlite3_finalize(pStmt); |
| 1994 | if( rc!=SQLITE_OK ){ |
| @@ -2038,11 +2065,11 @@ | |
| 2038 | Th_SetResult(interp, 0, 0); |
| 2039 | rc = TH_OK; |
| 2040 | } |
| 2041 | if( g.thTrace ){ |
| 2042 | Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", |
| 2043 | argl[nArg], argv[nArg], rc); |
| 2044 | } |
| 2045 | return rc; |
| 2046 | } |
| 2047 | |
| 2048 | /* |
| @@ -2121,11 +2148,11 @@ | |
| 2121 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2122 | } |
| 2123 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2124 | if( !zErr ){ |
| 2125 | Th_SetResultInt(interp, re_match(pRe, |
| 2126 | (const unsigned char *)argv[nArg+1], argl[nArg+1])); |
| 2127 | rc = TH_OK; |
| 2128 | }else{ |
| 2129 | Th_SetResult(interp, zErr, -1); |
| 2130 | rc = TH_ERROR; |
| 2131 | } |
| @@ -2160,11 +2187,11 @@ | |
| 2160 | UrlData urlData; |
| 2161 | |
| 2162 | if( argc<2 || argc>5 ){ |
| 2163 | return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); |
| 2164 | } |
| 2165 | if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ |
| 2166 | fAsynchronous = 1; nArg++; |
| 2167 | } |
| 2168 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2169 | if( nArg+1!=argc && nArg+2!=argc ){ |
| 2170 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| @@ -2189,11 +2216,11 @@ | |
| 2189 | return TH_ERROR; |
| 2190 | } |
| 2191 | re_free(pRe); |
| 2192 | blob_zero(&payload); |
| 2193 | if( nArg+2==argc ){ |
| 2194 | blob_append(&payload, argv[nArg+1], argl[nArg+1]); |
| 2195 | zType = "POST"; |
| 2196 | }else{ |
| 2197 | zType = "GET"; |
| 2198 | } |
| 2199 | if( fAsynchronous ){ |
| @@ -2268,11 +2295,11 @@ | |
| 2268 | if( argc!=2 ){ |
| 2269 | return Th_WrongNumArgs(interp, "captureTh1 STRING"); |
| 2270 | } |
| 2271 | pOrig = Th_SetOutputBlob(&out); |
| 2272 | zStr = argv[1]; |
| 2273 | nStr = argl[1]; |
| 2274 | rc = Th_Eval(g.interp, 0, zStr, nStr); |
| 2275 | Th_SetOutputBlob(pOrig); |
| 2276 | if(0==rc){ |
| 2277 | Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); |
| 2278 | } |
| @@ -2356,11 +2383,10 @@ | |
| 2356 | {"copybtn", copybtnCmd, 0}, |
| 2357 | {"date", dateCmd, 0}, |
| 2358 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 2359 | {"defHeader", defHeaderCmd, 0}, |
| 2360 | {"dir", dirCmd, 0}, |
| 2361 | {"enable_htmlify",enableHtmlifyCmd, 0}, |
| 2362 | {"enable_output", enableOutputCmd, 0}, |
| 2363 | {"encode64", encode64Cmd, 0}, |
| 2364 | {"getParameter", getParameterCmd, 0}, |
| 2365 | {"glob_match", globMatchCmd, 0}, |
| 2366 | {"globalState", globalStateCmd, 0}, |
| @@ -2387,13 +2413,15 @@ | |
| 2387 | {"setting", settingCmd, 0}, |
| 2388 | {"styleFooter", styleFooterCmd, 0}, |
| 2389 | {"styleHeader", styleHeaderCmd, 0}, |
| 2390 | {"styleScript", styleScriptCmd, 0}, |
| 2391 | {"submenu", submenuCmd, 0}, |
| 2392 | {"tclReady", tclReadyCmd, 0}, |
| 2393 | {"trace", traceCmd, 0}, |
| 2394 | {"stime", stimeCmd, 0}, |
| 2395 | {"unversioned", unversionedCmd, 0}, |
| 2396 | {"utime", utimeCmd, 0}, |
| 2397 | {"verifyCsrf", verifyCsrfCmd, 0}, |
| 2398 | {"verifyLogin", verifyLoginCmd, 0}, |
| 2399 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -2494,10 +2522,26 @@ | |
| 2494 | Th_Trace("set %h {%h}<br>\n", zName, zValue); |
| 2495 | } |
| 2496 | Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); |
| 2497 | } |
| 2498 | } |
| 2499 | |
| 2500 | /* |
| 2501 | ** Appends an element to a TH1 list value. This function is called by the |
| 2502 | ** transfer subsystem; therefore, it must be very careful to avoid doing |
| 2503 | ** any unnecessary work. To that end, the TH1 subsystem will not be called |
| @@ -2680,10 +2724,11 @@ | |
| 2680 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2681 | /* |
| 2682 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2683 | ** command hook handler as that is not actually an error condition. |
| 2684 | */ |
| 2685 | if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ |
| 2686 | sendError(0,zResult, nResult, 0); |
| 2687 | }else{ |
| 2688 | /* |
| 2689 | ** There is no command hook handler "installed". This situation |
| @@ -2767,10 +2812,11 @@ | |
| 2767 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2768 | /* |
| 2769 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2770 | ** webpage hook handler as that is not actually an error condition. |
| 2771 | */ |
| 2772 | if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ |
| 2773 | sendError(0,zResult, nResult, 1); |
| 2774 | }else{ |
| 2775 | /* |
| 2776 | ** There is no webpage hook handler "installed". This situation |
| @@ -2894,11 +2940,16 @@ | |
| 2894 | } |
| 2895 | rc = Th_GetVar(g.interp, (char*)zVar, nVar); |
| 2896 | z += i+1+n; |
| 2897 | i = 0; |
| 2898 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2899 | sendText(pOut,(char*)zResult, n, encode); |
| 2900 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 2901 | sendText(pOut,z, i, 0); |
| 2902 | z += i+5; |
| 2903 | for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} |
| 2904 | if( g.thTrace ){ |
| @@ -2907,11 +2958,11 @@ | |
| 2907 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 2908 | if( g.thTrace ){ |
| 2909 | int nTrRes; |
| 2910 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2911 | Th_Trace("[render_eval] => %h {%#h}<br>\n", |
| 2912 | Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); |
| 2913 | } |
| 2914 | if( rc!=TH_OK ) break; |
| 2915 | z += i; |
| 2916 | if( z[0] ){ z += 6; } |
| 2917 | i = 0; |
| @@ -2949,14 +3000,81 @@ | |
| 2949 | ** e.g. via the "render" script function binding, need to use the |
| 2950 | ** pThOut blob in order to avoid out-of-order output if |
| 2951 | ** Th_SetOutputBlob() has been called. If it has not been called, |
| 2952 | ** pThOut will be 0, which will redirect the output to CGI/stdout, |
| 2953 | ** as appropriate. We need to pass on g.th1Flags for the case of |
| 2954 | ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get |
| 2955 | ** inadvertently toggled off by a recursive call. |
| 2956 | */; |
| 2957 | } |
| 2958 | |
| 2959 | /* |
| 2960 | ** COMMAND: test-th-render |
| 2961 | ** |
| 2962 | ** Usage: %fossil test-th-render FILE |
| @@ -2992,10 +3110,11 @@ | |
| 2992 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 2993 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 2994 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 2995 | g.useLocalauth = 1; |
| 2996 | } |
| 2997 | verify_all_options(); |
| 2998 | if( g.argc<3 ){ |
| 2999 | usage("FILE"); |
| 3000 | } |
| 3001 | blob_zero(&in); |
| @@ -3044,10 +3163,11 @@ | |
| 3044 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3045 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3046 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3047 | g.useLocalauth = 1; |
| 3048 | } |
| 3049 | verify_all_options(); |
| 3050 | if( g.argc!=3 ){ |
| 3051 | usage("script"); |
| 3052 | } |
| 3053 | if(file_isfile(g.argv[2], ExtFILE)){ |
| 3054 |
| --- src/th_main.c | |
| +++ src/th_main.c | |
| @@ -31,13 +31,11 @@ | |
| 31 | #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ |
| 32 | #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ |
| 33 | #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ |
| 34 | #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ |
| 35 | #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */ |
| 36 | #define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */ |
| 37 | |
| 38 | /* |
| 39 | ** Useful and/or "well-known" combinations of flag values. |
| 40 | */ |
| 41 | #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ |
| @@ -262,11 +260,11 @@ | |
| 260 | ){ |
| 261 | char *zOut; |
| 262 | if( argc!=2 ){ |
| 263 | return Th_WrongNumArgs(interp, "httpize STRING"); |
| 264 | } |
| 265 | zOut = httpize((char*)argv[1], TH1_LEN(argl[1])); |
| 266 | Th_SetResult(interp, zOut, -1); |
| 267 | free(zOut); |
| 268 | return TH_OK; |
| 269 | } |
| 270 | |
| @@ -291,51 +289,12 @@ | |
| 289 | if( argc<2 || argc>3 ){ |
| 290 | return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); |
| 291 | } |
| 292 | rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); |
| 293 | if( g.thTrace ){ |
| 294 | Th_Trace("enable_output {%.*s} -> %d<br>\n", |
| 295 | TH1_LEN(argl[1]),argv[1],enableOutput); |
| 296 | } |
| 297 | return rc; |
| 298 | } |
| 299 | |
| 300 | /* |
| @@ -375,25 +334,25 @@ | |
| 334 | |
| 335 | /* |
| 336 | ** Send text to the appropriate output: If pOut is not NULL, it is |
| 337 | ** appended there, else to the console or to the CGI reply buffer. |
| 338 | ** Escape all characters with special meaning to HTML if the encode |
| 339 | ** parameter is true. |
| 340 | ** |
| 341 | ** If pOut is NULL and the global pThOut is not then that blob |
| 342 | ** is used for output. |
| 343 | */ |
| 344 | static void sendText(Blob *pOut, const char *z, int n, int encode){ |
| 345 | if(0==pOut && pThOut!=0){ |
| 346 | pOut = pThOut; |
| 347 | } |
| 348 | if( enableOutput && n ){ |
| 349 | if( n<0 ){ |
| 350 | n = strlen(z); |
| 351 | }else{ |
| 352 | n = TH1_LEN(n); |
| 353 | } |
| 354 | if( encode ){ |
| 355 | z = htmlize(z, n); |
| 356 | n = strlen(z); |
| 357 | } |
| 358 | if(pOut!=0){ |
| @@ -525,14 +484,22 @@ | |
| 484 | void *pConvert, |
| 485 | int argc, |
| 486 | const char **argv, |
| 487 | int *argl |
| 488 | ){ |
| 489 | int encode = *(unsigned int*)pConvert; |
| 490 | int n; |
| 491 | if( argc!=2 ){ |
| 492 | return Th_WrongNumArgs(interp, "puts STRING"); |
| 493 | } |
| 494 | n = argl[1]; |
| 495 | if( encode==0 && n>0 && TH1_TAINTED(n) ){ |
| 496 | if( Th_ReportTaint(interp, "output string", argv[1], n) ){ |
| 497 | return TH_ERROR; |
| 498 | } |
| 499 | } |
| 500 | sendText(0,(char*)argv[1], TH1_LEN(n), encode); |
| 501 | return TH_OK; |
| 502 | } |
| 503 | |
| 504 | /* |
| 505 | ** TH1 command: redirect URL ?withMethod? |
| @@ -557,10 +524,15 @@ | |
| 524 | } |
| 525 | if( argc==3 ){ |
| 526 | if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ |
| 527 | return TH_ERROR; |
| 528 | } |
| 529 | } |
| 530 | if( TH1_TAINTED(argl[1]) |
| 531 | && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1]) |
| 532 | ){ |
| 533 | return TH_ERROR; |
| 534 | } |
| 535 | if( withMethod ){ |
| 536 | cgi_redirect_with_method(argv[1]); |
| 537 | }else{ |
| 538 | cgi_redirect(argv[1]); |
| @@ -660,11 +632,11 @@ | |
| 632 | int nValue = 0; |
| 633 | if( argc!=2 ){ |
| 634 | return Th_WrongNumArgs(interp, "markdown STRING"); |
| 635 | } |
| 636 | blob_zero(&src); |
| 637 | blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); |
| 638 | blob_zero(&title); blob_zero(&body); |
| 639 | markdown_to_html(&src, &title, &body); |
| 640 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); |
| 641 | Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); |
| 642 | Th_SetResult(interp, zValue, nValue); |
| @@ -690,11 +662,11 @@ | |
| 662 | if( argc!=2 ){ |
| 663 | return Th_WrongNumArgs(interp, "wiki STRING"); |
| 664 | } |
| 665 | if( enableOutput ){ |
| 666 | Blob src; |
| 667 | blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); |
| 668 | wiki_convert(&src, 0, flags); |
| 669 | blob_reset(&src); |
| 670 | } |
| 671 | return TH_OK; |
| 672 | } |
| @@ -735,11 +707,11 @@ | |
| 707 | ){ |
| 708 | char *zOut; |
| 709 | if( argc!=2 ){ |
| 710 | return Th_WrongNumArgs(interp, "htmlize STRING"); |
| 711 | } |
| 712 | zOut = htmlize((char*)argv[1], TH1_LEN(argl[1])); |
| 713 | Th_SetResult(interp, zOut, -1); |
| 714 | free(zOut); |
| 715 | return TH_OK; |
| 716 | } |
| 717 | |
| @@ -757,11 +729,11 @@ | |
| 729 | ){ |
| 730 | char *zOut; |
| 731 | if( argc!=2 ){ |
| 732 | return Th_WrongNumArgs(interp, "encode64 STRING"); |
| 733 | } |
| 734 | zOut = encode64((char*)argv[1], TH1_LEN(argl[1])); |
| 735 | Th_SetResult(interp, zOut, -1); |
| 736 | free(zOut); |
| 737 | return TH_OK; |
| 738 | } |
| 739 | |
| @@ -778,11 +750,11 @@ | |
| 750 | int argc, |
| 751 | const char **argv, |
| 752 | int *argl |
| 753 | ){ |
| 754 | char *zOut; |
| 755 | if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){ |
| 756 | zOut = db_text("??", "SELECT datetime('now',toLocal())"); |
| 757 | }else{ |
| 758 | zOut = db_text("??", "SELECT datetime('now')"); |
| 759 | } |
| 760 | Th_SetResult(interp, zOut, -1); |
| @@ -810,13 +782,13 @@ | |
| 782 | if( argc<2 ){ |
| 783 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 784 | } |
| 785 | for(i=1; rc==1 && i<argc; i++){ |
| 786 | if( g.thTrace ){ |
| 787 | Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i])); |
| 788 | } |
| 789 | rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p); |
| 790 | } |
| 791 | if( g.thTrace ){ |
| 792 | Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); |
| 793 | Th_Free(interp, zCapList); |
| 794 | } |
| @@ -858,11 +830,11 @@ | |
| 830 | int i; |
| 831 | |
| 832 | if( argc!=2 ){ |
| 833 | return Th_WrongNumArgs(interp, "capexpr EXPR"); |
| 834 | } |
| 835 | rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap); |
| 836 | if( rc ) return rc; |
| 837 | rc = 0; |
| 838 | for(i=0; i<nCap; i++){ |
| 839 | if( azCap[i][0]=='!' ){ |
| 840 | rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); |
| @@ -921,11 +893,12 @@ | |
| 893 | if( argc<2 ){ |
| 894 | return Th_WrongNumArgs(interp, "hascap STRING ..."); |
| 895 | } |
| 896 | for(i=1; i<argc && rc; i++){ |
| 897 | int match = 0; |
| 898 | int nn = TH1_LEN(argl[i]); |
| 899 | for(j=0; j<nn; j++){ |
| 900 | switch( argv[i][j] ){ |
| 901 | case 'c': match |= searchCap & SRCH_CKIN; break; |
| 902 | case 'd': match |= searchCap & SRCH_DOC; break; |
| 903 | case 't': match |= searchCap & SRCH_TKT; break; |
| 904 | case 'w': match |= searchCap & SRCH_WIKI; break; |
| @@ -932,11 +905,11 @@ | |
| 905 | } |
| 906 | } |
| 907 | if( !match ) rc = 0; |
| 908 | } |
| 909 | if( g.thTrace ){ |
| 910 | Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); |
| 911 | } |
| 912 | Th_SetResultInt(interp, rc); |
| 913 | return TH_OK; |
| 914 | } |
| 915 | |
| @@ -1051,11 +1024,11 @@ | |
| 1024 | #endif |
| 1025 | else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ |
| 1026 | rc = 1; |
| 1027 | } |
| 1028 | if( g.thTrace ){ |
| 1029 | Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc); |
| 1030 | } |
| 1031 | Th_SetResultInt(interp, rc); |
| 1032 | return TH_OK; |
| 1033 | } |
| 1034 | |
| @@ -1104,18 +1077,20 @@ | |
| 1077 | const char **argv, |
| 1078 | int *argl |
| 1079 | ){ |
| 1080 | int rc = 0; |
| 1081 | int i; |
| 1082 | int nn; |
| 1083 | if( argc!=2 ){ |
| 1084 | return Th_WrongNumArgs(interp, "anycap STRING"); |
| 1085 | } |
| 1086 | nn = TH1_LEN(argl[1]); |
| 1087 | for(i=0; rc==0 && i<nn; i++){ |
| 1088 | rc = login_has_capability((char*)&argv[1][i],1,0); |
| 1089 | } |
| 1090 | if( g.thTrace ){ |
| 1091 | Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); |
| 1092 | } |
| 1093 | Th_SetResultInt(interp, rc); |
| 1094 | return TH_OK; |
| 1095 | } |
| 1096 | |
| @@ -1140,22 +1115,23 @@ | |
| 1115 | return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); |
| 1116 | } |
| 1117 | if( enableOutput ){ |
| 1118 | int height; |
| 1119 | Blob name; |
| 1120 | int nValue = 0; |
| 1121 | const char *zValue; |
| 1122 | char *z, *zH; |
| 1123 | int nElem; |
| 1124 | int *aszElem; |
| 1125 | char **azElem; |
| 1126 | int i; |
| 1127 | |
| 1128 | if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; |
| 1129 | Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem); |
| 1130 | blob_init(&name, (char*)argv[1], TH1_LEN(argl[1])); |
| 1131 | zValue = Th_Fetch(blob_str(&name), &nValue); |
| 1132 | nValue = TH1_LEN(nValue); |
| 1133 | zH = htmlize(blob_buffer(&name), blob_size(&name)); |
| 1134 | z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); |
| 1135 | free(zH); |
| 1136 | sendText(0,z, -1, 0); |
| 1137 | free(z); |
| @@ -1247,11 +1223,11 @@ | |
| 1223 | return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); |
| 1224 | } |
| 1225 | if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; |
| 1226 | if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; |
| 1227 | z = argv[1]; |
| 1228 | size = TH1_LEN(argl[1]); |
| 1229 | for(n=1, i=0; i<size; i++){ |
| 1230 | if( z[i]=='\n' ){ |
| 1231 | n++; |
| 1232 | if( n>=iMax ) break; |
| 1233 | } |
| @@ -1407,11 +1383,12 @@ | |
| 1383 | return TH_OK; |
| 1384 | }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ |
| 1385 | Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); |
| 1386 | return TH_OK; |
| 1387 | }else{ |
| 1388 | Th_ErrorMessage(interp, "unsupported global state:", |
| 1389 | argv[1], TH1_LEN(argl[1])); |
| 1390 | return TH_ERROR; |
| 1391 | } |
| 1392 | } |
| 1393 | |
| 1394 | /* |
| @@ -1426,17 +1403,21 @@ | |
| 1403 | int argc, |
| 1404 | const char **argv, |
| 1405 | int *argl |
| 1406 | ){ |
| 1407 | const char *zDefault = 0; |
| 1408 | const char *zVal; |
| 1409 | int sz; |
| 1410 | if( argc!=2 && argc!=3 ){ |
| 1411 | return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?"); |
| 1412 | } |
| 1413 | if( argc==3 ){ |
| 1414 | zDefault = argv[2]; |
| 1415 | } |
| 1416 | zVal = cgi_parameter(argv[1], zDefault); |
| 1417 | sz = th_strlen(zVal); |
| 1418 | Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz)); |
| 1419 | return TH_OK; |
| 1420 | } |
| 1421 | |
| 1422 | /* |
| 1423 | ** TH1 command: setParameter NAME VALUE |
| @@ -1848,10 +1829,47 @@ | |
| 1829 | sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); |
| 1830 | Th_SetResult(interp, zUTime, -1); |
| 1831 | return TH_OK; |
| 1832 | } |
| 1833 | |
| 1834 | /* |
| 1835 | ** TH1 command: taint STRING |
| 1836 | ** |
| 1837 | ** Return a copy of STRING that is marked as tainted. |
| 1838 | */ |
| 1839 | static int taintCmd( |
| 1840 | Th_Interp *interp, |
| 1841 | void *p, |
| 1842 | int argc, |
| 1843 | const char **argv, |
| 1844 | int *argl |
| 1845 | ){ |
| 1846 | if( argc!=2 ){ |
| 1847 | return Th_WrongNumArgs(interp, "STRING"); |
| 1848 | } |
| 1849 | Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1])); |
| 1850 | return TH_OK; |
| 1851 | } |
| 1852 | |
| 1853 | /* |
| 1854 | ** TH1 command: untaint STRING |
| 1855 | ** |
| 1856 | ** Return a copy of STRING that is marked as untainted. |
| 1857 | */ |
| 1858 | static int untaintCmd( |
| 1859 | Th_Interp *interp, |
| 1860 | void *p, |
| 1861 | int argc, |
| 1862 | const char **argv, |
| 1863 | int *argl |
| 1864 | ){ |
| 1865 | if( argc!=2 ){ |
| 1866 | return Th_WrongNumArgs(interp, "STRING"); |
| 1867 | } |
| 1868 | Th_SetResult(interp, argv[1], TH1_LEN(argl[1])); |
| 1869 | return TH_OK; |
| 1870 | } |
| 1871 | |
| 1872 | /* |
| 1873 | ** TH1 command: randhex N |
| 1874 | ** |
| 1875 | ** Return N*2 random hexadecimal digits with N<50. If N is omitted, |
| @@ -1923,11 +1941,13 @@ | |
| 1941 | int res = TH_OK; |
| 1942 | int nVar; |
| 1943 | char *zErr = 0; |
| 1944 | int noComplain = 0; |
| 1945 | |
| 1946 | if( argc>3 && TH1_LEN(argl[1])==11 |
| 1947 | && strncmp(argv[1], "-nocomplain", 11)==0 |
| 1948 | ){ |
| 1949 | argc--; |
| 1950 | argv++; |
| 1951 | argl++; |
| 1952 | noComplain = 1; |
| 1953 | } |
| @@ -1939,15 +1959,22 @@ | |
| 1959 | Th_ErrorMessage(interp, "database is not open", 0, 0); |
| 1960 | return TH_ERROR; |
| 1961 | } |
| 1962 | zSql = argv[1]; |
| 1963 | nSql = argl[1]; |
| 1964 | if( TH1_TAINTED(nSql) ){ |
| 1965 | if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){ |
| 1966 | return TH_ERROR; |
| 1967 | } |
| 1968 | nSql = TH1_LEN(nSql); |
| 1969 | } |
| 1970 | |
| 1971 | while( res==TH_OK && nSql>0 ){ |
| 1972 | zErr = 0; |
| 1973 | report_restrict_sql(&zErr); |
| 1974 | g.dbIgnoreErrors++; |
| 1975 | rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail); |
| 1976 | g.dbIgnoreErrors--; |
| 1977 | report_unrestrict_sql(); |
| 1978 | if( rc!=0 || zErr!=0 ){ |
| 1979 | if( noComplain ) return TH_OK; |
| 1980 | Th_ErrorMessage(interp, "SQL error: ", |
| @@ -1964,31 +1991,31 @@ | |
| 1991 | int szVar = zVar ? th_strlen(zVar) : 0; |
| 1992 | if( szVar>1 && zVar[0]=='$' |
| 1993 | && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ |
| 1994 | int nVal; |
| 1995 | const char *zVal = Th_GetResult(interp, &nVal); |
| 1996 | sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT); |
| 1997 | } |
| 1998 | } |
| 1999 | while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ |
| 2000 | int nCol = sqlite3_column_count(pStmt); |
| 2001 | for(i=0; i<nCol; i++){ |
| 2002 | const char *zCol = sqlite3_column_name(pStmt, i); |
| 2003 | int szCol = th_strlen(zCol); |
| 2004 | const char *zVal = (const char*)sqlite3_column_text(pStmt, i); |
| 2005 | int szVal = sqlite3_column_bytes(pStmt, i); |
| 2006 | Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal)); |
| 2007 | } |
| 2008 | if( g.thTrace ){ |
| 2009 | Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]); |
| 2010 | } |
| 2011 | res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2])); |
| 2012 | if( g.thTrace ){ |
| 2013 | int nTrRes; |
| 2014 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2015 | Th_Trace("[query_eval] => %h {%#h}<br>\n", |
| 2016 | Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes); |
| 2017 | } |
| 2018 | if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; |
| 2019 | } |
| 2020 | rc = sqlite3_finalize(pStmt); |
| 2021 | if( rc!=SQLITE_OK ){ |
| @@ -2038,11 +2065,11 @@ | |
| 2065 | Th_SetResult(interp, 0, 0); |
| 2066 | rc = TH_OK; |
| 2067 | } |
| 2068 | if( g.thTrace ){ |
| 2069 | Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", |
| 2070 | TH1_LEN(argl[nArg]), argv[nArg], rc); |
| 2071 | } |
| 2072 | return rc; |
| 2073 | } |
| 2074 | |
| 2075 | /* |
| @@ -2121,11 +2148,11 @@ | |
| 2148 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| 2149 | } |
| 2150 | zErr = re_compile(&pRe, argv[nArg], noCase); |
| 2151 | if( !zErr ){ |
| 2152 | Th_SetResultInt(interp, re_match(pRe, |
| 2153 | (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); |
| 2154 | rc = TH_OK; |
| 2155 | }else{ |
| 2156 | Th_SetResult(interp, zErr, -1); |
| 2157 | rc = TH_ERROR; |
| 2158 | } |
| @@ -2160,11 +2187,11 @@ | |
| 2187 | UrlData urlData; |
| 2188 | |
| 2189 | if( argc<2 || argc>5 ){ |
| 2190 | return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); |
| 2191 | } |
| 2192 | if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){ |
| 2193 | fAsynchronous = 1; nArg++; |
| 2194 | } |
| 2195 | if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; |
| 2196 | if( nArg+1!=argc && nArg+2!=argc ){ |
| 2197 | return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); |
| @@ -2189,11 +2216,11 @@ | |
| 2216 | return TH_ERROR; |
| 2217 | } |
| 2218 | re_free(pRe); |
| 2219 | blob_zero(&payload); |
| 2220 | if( nArg+2==argc ){ |
| 2221 | blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1])); |
| 2222 | zType = "POST"; |
| 2223 | }else{ |
| 2224 | zType = "GET"; |
| 2225 | } |
| 2226 | if( fAsynchronous ){ |
| @@ -2268,11 +2295,11 @@ | |
| 2295 | if( argc!=2 ){ |
| 2296 | return Th_WrongNumArgs(interp, "captureTh1 STRING"); |
| 2297 | } |
| 2298 | pOrig = Th_SetOutputBlob(&out); |
| 2299 | zStr = argv[1]; |
| 2300 | nStr = TH1_LEN(argl[1]); |
| 2301 | rc = Th_Eval(g.interp, 0, zStr, nStr); |
| 2302 | Th_SetOutputBlob(pOrig); |
| 2303 | if(0==rc){ |
| 2304 | Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); |
| 2305 | } |
| @@ -2356,11 +2383,10 @@ | |
| 2383 | {"copybtn", copybtnCmd, 0}, |
| 2384 | {"date", dateCmd, 0}, |
| 2385 | {"decorate", wikiCmd, (void*)&aFlags[2]}, |
| 2386 | {"defHeader", defHeaderCmd, 0}, |
| 2387 | {"dir", dirCmd, 0}, |
| 2388 | {"enable_output", enableOutputCmd, 0}, |
| 2389 | {"encode64", encode64Cmd, 0}, |
| 2390 | {"getParameter", getParameterCmd, 0}, |
| 2391 | {"glob_match", globMatchCmd, 0}, |
| 2392 | {"globalState", globalStateCmd, 0}, |
| @@ -2387,13 +2413,15 @@ | |
| 2413 | {"setting", settingCmd, 0}, |
| 2414 | {"styleFooter", styleFooterCmd, 0}, |
| 2415 | {"styleHeader", styleHeaderCmd, 0}, |
| 2416 | {"styleScript", styleScriptCmd, 0}, |
| 2417 | {"submenu", submenuCmd, 0}, |
| 2418 | {"taint", taintCmd, 0}, |
| 2419 | {"tclReady", tclReadyCmd, 0}, |
| 2420 | {"trace", traceCmd, 0}, |
| 2421 | {"stime", stimeCmd, 0}, |
| 2422 | {"untaint", untaintCmd, 0}, |
| 2423 | {"unversioned", unversionedCmd, 0}, |
| 2424 | {"utime", utimeCmd, 0}, |
| 2425 | {"verifyCsrf", verifyCsrfCmd, 0}, |
| 2426 | {"verifyLogin", verifyLoginCmd, 0}, |
| 2427 | {"wiki", wikiCmd, (void*)&aFlags[0]}, |
| @@ -2494,10 +2522,26 @@ | |
| 2522 | Th_Trace("set %h {%h}<br>\n", zName, zValue); |
| 2523 | } |
| 2524 | Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); |
| 2525 | } |
| 2526 | } |
| 2527 | |
| 2528 | /* |
| 2529 | ** Store a string value in a variable in the interpreter |
| 2530 | ** with the "taint" marking, so that TH1 knows that this |
| 2531 | ** variable contains content under the control of the remote |
| 2532 | ** user and presents a risk of XSS or SQL-injection attacks. |
| 2533 | */ |
| 2534 | void Th_StoreUnsafe(const char *zName, const char *zValue){ |
| 2535 | Th_FossilInit(TH_INIT_DEFAULT); |
| 2536 | if( zValue ){ |
| 2537 | if( g.thTrace ){ |
| 2538 | Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue); |
| 2539 | } |
| 2540 | Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue))); |
| 2541 | } |
| 2542 | } |
| 2543 | |
| 2544 | /* |
| 2545 | ** Appends an element to a TH1 list value. This function is called by the |
| 2546 | ** transfer subsystem; therefore, it must be very careful to avoid doing |
| 2547 | ** any unnecessary work. To that end, the TH1 subsystem will not be called |
| @@ -2680,10 +2724,11 @@ | |
| 2724 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2725 | /* |
| 2726 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2727 | ** command hook handler as that is not actually an error condition. |
| 2728 | */ |
| 2729 | nResult = TH1_LEN(nResult); |
| 2730 | if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ |
| 2731 | sendError(0,zResult, nResult, 0); |
| 2732 | }else{ |
| 2733 | /* |
| 2734 | ** There is no command hook handler "installed". This situation |
| @@ -2767,10 +2812,11 @@ | |
| 2812 | char *zResult = (char*)Th_GetResult(g.interp, &nResult); |
| 2813 | /* |
| 2814 | ** Make sure that the TH1 script error was not caused by a "missing" |
| 2815 | ** webpage hook handler as that is not actually an error condition. |
| 2816 | */ |
| 2817 | nResult = TH1_LEN(nResult); |
| 2818 | if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ |
| 2819 | sendError(0,zResult, nResult, 1); |
| 2820 | }else{ |
| 2821 | /* |
| 2822 | ** There is no webpage hook handler "installed". This situation |
| @@ -2894,11 +2940,16 @@ | |
| 2940 | } |
| 2941 | rc = Th_GetVar(g.interp, (char*)zVar, nVar); |
| 2942 | z += i+1+n; |
| 2943 | i = 0; |
| 2944 | zResult = (char*)Th_GetResult(g.interp, &n); |
| 2945 | if( !TH1_TAINTED(n) |
| 2946 | || encode |
| 2947 | || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK |
| 2948 | ){ |
| 2949 | sendText(pOut,(char*)zResult, n, encode); |
| 2950 | } |
| 2951 | }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ |
| 2952 | sendText(pOut,z, i, 0); |
| 2953 | z += i+5; |
| 2954 | for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} |
| 2955 | if( g.thTrace ){ |
| @@ -2907,11 +2958,11 @@ | |
| 2958 | rc = Th_Eval(g.interp, 0, (const char*)z, i); |
| 2959 | if( g.thTrace ){ |
| 2960 | int nTrRes; |
| 2961 | char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); |
| 2962 | Th_Trace("[render_eval] => %h {%#h}<br>\n", |
| 2963 | Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes); |
| 2964 | } |
| 2965 | if( rc!=TH_OK ) break; |
| 2966 | z += i; |
| 2967 | if( z[0] ){ z += 6; } |
| 2968 | i = 0; |
| @@ -2949,14 +3000,81 @@ | |
| 3000 | ** e.g. via the "render" script function binding, need to use the |
| 3001 | ** pThOut blob in order to avoid out-of-order output if |
| 3002 | ** Th_SetOutputBlob() has been called. If it has not been called, |
| 3003 | ** pThOut will be 0, which will redirect the output to CGI/stdout, |
| 3004 | ** as appropriate. We need to pass on g.th1Flags for the case of |
| 3005 | ** recursive calls. |
| 3006 | */; |
| 3007 | } |
| 3008 | |
| 3009 | /* |
| 3010 | ** SETTING: vuln-report width=8 default=log |
| 3011 | ** |
| 3012 | ** This setting controls Fossil's behavior when it encounters a potential |
| 3013 | ** XSS or SQL-injection vulnerability due to misuse of TH1 configuration |
| 3014 | ** scripts. Choices are: |
| 3015 | ** |
| 3016 | ** off Do nothing. Ignore the vulnerability. |
| 3017 | ** |
| 3018 | ** log Write a report of the problem into the error log. |
| 3019 | ** |
| 3020 | ** block Like "log" but also prevent the offending TH1 command |
| 3021 | ** from running. |
| 3022 | ** |
| 3023 | ** fatal Render an error message page instead of the requested |
| 3024 | ** page. |
| 3025 | */ |
| 3026 | |
| 3027 | /* |
| 3028 | ** Report misuse of a tainted string in TH1. |
| 3029 | ** |
| 3030 | ** The behavior depends on the vuln-report setting. If "off", this routine |
| 3031 | ** is a no-op. Otherwise, right a message into the error log. If |
| 3032 | ** vuln-report is "log", that is all that happens. But for any other |
| 3033 | ** value of vuln-report, a fatal error is raised. |
| 3034 | */ |
| 3035 | int Th_ReportTaint( |
| 3036 | Th_Interp *interp, /* Report error here, if an error is reported */ |
| 3037 | const char *zWhere, /* Where the tainted string appears */ |
| 3038 | const char *zStr, /* The tainted string */ |
| 3039 | int nStr /* Length of the tainted string */ |
| 3040 | ){ |
| 3041 | static const char *zDisp = 0; /* Dispensation; what to do with the error */ |
| 3042 | const char *zVulnType; /* Type of vulnerability */ |
| 3043 | |
| 3044 | if( zDisp==0 ) zDisp = db_get("vuln-report","log"); |
| 3045 | if( is_false(zDisp) ) return 0; |
| 3046 | if( strstr(zWhere,"SQL")!=0 ){ |
| 3047 | zVulnType = "SQL-injection"; |
| 3048 | }else{ |
| 3049 | zVulnType = "XSS"; |
| 3050 | } |
| 3051 | nStr = TH1_LEN(nStr); |
| 3052 | fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"", |
| 3053 | zVulnType, zWhere, nStr, zStr); |
| 3054 | if( strcmp(zDisp,"log")==0 ){ |
| 3055 | return 0; |
| 3056 | } |
| 3057 | if( strcmp(zDisp,"block")==0 ){ |
| 3058 | char *z = mprintf("tainted %s: \"", zWhere); |
| 3059 | Th_ErrorMessage(interp, z, zStr, nStr); |
| 3060 | fossil_free(z); |
| 3061 | }else{ |
| 3062 | char *z = mprintf("%#h", nStr, zStr); |
| 3063 | zDisp = "off"; |
| 3064 | cgi_reset_content(); |
| 3065 | style_submenu_enable(0); |
| 3066 | style_set_current_feature("error"); |
| 3067 | style_header("Configuration Error"); |
| 3068 | @ <p>Error in a TH1 configuration script: |
| 3069 | @ tainted %h(zWhere): "%z(z)" |
| 3070 | style_finish_page(); |
| 3071 | cgi_reply(); |
| 3072 | fossil_exit(1); |
| 3073 | } |
| 3074 | return 1; |
| 3075 | } |
| 3076 | |
| 3077 | /* |
| 3078 | ** COMMAND: test-th-render |
| 3079 | ** |
| 3080 | ** Usage: %fossil test-th-render FILE |
| @@ -2992,10 +3110,11 @@ | |
| 3110 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3111 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3112 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3113 | g.useLocalauth = 1; |
| 3114 | } |
| 3115 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 3116 | verify_all_options(); |
| 3117 | if( g.argc<3 ){ |
| 3118 | usage("FILE"); |
| 3119 | } |
| 3120 | blob_zero(&in); |
| @@ -3044,10 +3163,11 @@ | |
| 3163 | if( find_option("set-user-caps", 0, 0)!=0 ){ |
| 3164 | const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); |
| 3165 | login_set_capabilities(zCap ? zCap : "sx", 0); |
| 3166 | g.useLocalauth = 1; |
| 3167 | } |
| 3168 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 3169 | verify_all_options(); |
| 3170 | if( g.argc!=3 ){ |
| 3171 | usage("script"); |
| 3172 | } |
| 3173 | if(file_isfile(g.argv[2], ExtFILE)){ |
| 3174 |
+34
-88
| --- src/th_tcl.c | ||
| +++ src/th_tcl.c | ||
| @@ -24,10 +24,14 @@ | ||
| 24 | 24 | |
| 25 | 25 | #include "sqlite3.h" |
| 26 | 26 | #include "th.h" |
| 27 | 27 | #include "tcl.h" |
| 28 | 28 | |
| 29 | +#if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size) | |
| 30 | +# define Tcl_Size int | |
| 31 | +#endif | |
| 32 | + | |
| 29 | 33 | /* |
| 30 | 34 | ** This macro is used to verify that the header version of Tcl meets some |
| 31 | 35 | ** minimum requirement. |
| 32 | 36 | */ |
| 33 | 37 | #define MINIMUM_TCL_VERSION(major, minor) \ |
| @@ -41,16 +45,16 @@ | ||
| 41 | 45 | #define USE_ARGV_TO_OBJV() \ |
| 42 | 46 | int objc; \ |
| 43 | 47 | Tcl_Obj **objv; \ |
| 44 | 48 | int obji; |
| 45 | 49 | |
| 46 | -#define COPY_ARGV_TO_OBJV() \ | |
| 47 | - objc = argc-1; \ | |
| 48 | - objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ | |
| 49 | - for(obji=1; obji<argc; obji++){ \ | |
| 50 | - objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \ | |
| 51 | - Tcl_IncrRefCount(objv[obji-1]); \ | |
| 50 | +#define COPY_ARGV_TO_OBJV() \ | |
| 51 | + objc = argc-1; \ | |
| 52 | + objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ | |
| 53 | + for(obji=1; obji<argc; obji++){ \ | |
| 54 | + objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \ | |
| 55 | + Tcl_IncrRefCount(objv[obji-1]); \ | |
| 52 | 56 | } |
| 53 | 57 | |
| 54 | 58 | #define FREE_ARGV_TO_OBJV() \ |
| 55 | 59 | for(obji=1; obji<argc; obji++){ \ |
| 56 | 60 | Tcl_DecrRefCount(objv[obji-1]); \ |
| @@ -183,11 +187,15 @@ | ||
| 183 | 187 | ** the only Tcl API functions that MUST be called prior to being able to call |
| 184 | 188 | ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete |
| 185 | 189 | ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp |
| 186 | 190 | ** and Tcl_Finalize function types are also required. |
| 187 | 191 | */ |
| 192 | +#if TCL_MAJOR_VERSION>=9 | |
| 193 | +typedef const char *(tcl_FindExecutableProc) (const char *); | |
| 194 | +#else | |
| 188 | 195 | typedef void (tcl_FindExecutableProc) (const char *); |
| 196 | +#endif | |
| 189 | 197 | typedef Tcl_Interp *(tcl_CreateInterpProc) (void); |
| 190 | 198 | typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); |
| 191 | 199 | typedef void (tcl_FinalizeProc) (void); |
| 192 | 200 | |
| 193 | 201 | /* |
| @@ -321,27 +329,10 @@ | ||
| 321 | 329 | ** by the caller. This must be declared here because quite a few functions in |
| 322 | 330 | ** this file need to use it before it can be defined. |
| 323 | 331 | */ |
| 324 | 332 | static int createTclInterp(Th_Interp *interp, void *pContext); |
| 325 | 333 | |
| 326 | -/* | |
| 327 | -** Returns the TH1 return code corresponding to the specified Tcl | |
| 328 | -** return code. | |
| 329 | -*/ | |
| 330 | -static int getTh1ReturnCode( | |
| 331 | - int rc /* The Tcl return code value to convert. */ | |
| 332 | -){ | |
| 333 | - switch( rc ){ | |
| 334 | - case /*0*/ TCL_OK: return /*0*/ TH_OK; | |
| 335 | - case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR; | |
| 336 | - case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN; | |
| 337 | - case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK; | |
| 338 | - case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE; | |
| 339 | - default /*?*/: return /*?*/ rc; | |
| 340 | - } | |
| 341 | -} | |
| 342 | - | |
| 343 | 334 | /* |
| 344 | 335 | ** Returns the Tcl return code corresponding to the specified TH1 |
| 345 | 336 | ** return code. |
| 346 | 337 | */ |
| 347 | 338 | static int getTclReturnCode( |
| @@ -387,10 +378,12 @@ | ||
| 387 | 378 | static char *getTclResult( |
| 388 | 379 | Tcl_Interp *pInterp, |
| 389 | 380 | int *pN |
| 390 | 381 | ){ |
| 391 | 382 | Tcl_Obj *resultPtr; |
| 383 | + Tcl_Size n; | |
| 384 | + char *zRes; | |
| 392 | 385 | |
| 393 | 386 | if( !pInterp ){ /* This should not happen. */ |
| 394 | 387 | if( pN ) *pN = 0; |
| 395 | 388 | return 0; |
| 396 | 389 | } |
| @@ -397,11 +390,13 @@ | ||
| 397 | 390 | resultPtr = Tcl_GetObjResult(pInterp); |
| 398 | 391 | if( !resultPtr ){ /* This should not happen either? */ |
| 399 | 392 | if( pN ) *pN = 0; |
| 400 | 393 | return 0; |
| 401 | 394 | } |
| 402 | - return Tcl_GetStringFromObj(resultPtr, pN); | |
| 395 | + zRes = Tcl_GetStringFromObj(resultPtr, &n); | |
| 396 | + *pN = (int)n; | |
| 397 | + return zRes; | |
| 403 | 398 | } |
| 404 | 399 | |
| 405 | 400 | /* |
| 406 | 401 | ** Tcl context information used by TH1. This structure definition has been |
| 407 | 402 | ** copied from and should be kept in sync with the one in "main.c". |
| @@ -416,48 +411,12 @@ | ||
| 416 | 411 | tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */ |
| 417 | 412 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 418 | 413 | int useObjProc; /* Non-zero if an objProc can be called directly. */ |
| 419 | 414 | int useTip285; /* Non-zero if TIP #285 is available. */ |
| 420 | 415 | const char *setup; /* The optional Tcl setup script. */ |
| 421 | - tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */ | |
| 422 | - void *pPreContext; /* Optional, provided to xPreEval(). */ | |
| 423 | - tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */ | |
| 424 | - void *pPostContext; /* Optional, provided to xPostEval(). */ | |
| 425 | 416 | }; |
| 426 | 417 | |
| 427 | -/* | |
| 428 | -** This function calls the configured xPreEval or xPostEval functions, if any. | |
| 429 | -** May have arbitrary side-effects. This function returns the result of the | |
| 430 | -** called notification function or the value of the rc argument if there is no | |
| 431 | -** notification function configured. | |
| 432 | -*/ | |
| 433 | -static int notifyPreOrPostEval( | |
| 434 | - int bIsPost, | |
| 435 | - Th_Interp *interp, | |
| 436 | - void *ctx, | |
| 437 | - int argc, | |
| 438 | - const char **argv, | |
| 439 | - int *argl, | |
| 440 | - int rc | |
| 441 | -){ | |
| 442 | - struct TclContext *tclContext = (struct TclContext *)ctx; | |
| 443 | - tcl_NotifyProc *xNotifyProc; | |
| 444 | - | |
| 445 | - if( !tclContext ){ | |
| 446 | - Th_ErrorMessage(interp, | |
| 447 | - "invalid Tcl context", (const char *)"", 0); | |
| 448 | - return TH_ERROR; | |
| 449 | - } | |
| 450 | - xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; | |
| 451 | - if( xNotifyProc ){ | |
| 452 | - rc = xNotifyProc(bIsPost ? | |
| 453 | - tclContext->pPostContext : tclContext->pPreContext, | |
| 454 | - interp, ctx, argc, argv, argl, rc); | |
| 455 | - } | |
| 456 | - return rc; | |
| 457 | -} | |
| 458 | - | |
| 459 | 418 | /* |
| 460 | 419 | ** TH1 command: tclEval arg ?arg ...? |
| 461 | 420 | ** |
| 462 | 421 | ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script |
| 463 | 422 | ** error is generated, it will be transformed into a TH1 script error. The |
| @@ -485,17 +444,13 @@ | ||
| 485 | 444 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 486 | 445 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 487 | 446 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 488 | 447 | return TH_ERROR; |
| 489 | 448 | } |
| 490 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 491 | - if( rc!=TH_OK ){ | |
| 492 | - return rc; | |
| 493 | - } | |
| 494 | 449 | Tcl_Preserve((ClientData)tclInterp); |
| 495 | 450 | if( argc==2 ){ |
| 496 | - objPtr = Tcl_NewStringObj(argv[1], argl[1]); | |
| 451 | + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); | |
| 497 | 452 | Tcl_IncrRefCount(objPtr); |
| 498 | 453 | rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); |
| 499 | 454 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 500 | 455 | }else{ |
| 501 | 456 | USE_ARGV_TO_OBJV(); |
| @@ -507,12 +462,10 @@ | ||
| 507 | 462 | FREE_ARGV_TO_OBJV(); |
| 508 | 463 | } |
| 509 | 464 | zResult = getTclResult(tclInterp, &nResult); |
| 510 | 465 | Th_SetResult(interp, zResult, nResult); |
| 511 | 466 | Tcl_Release((ClientData)tclInterp); |
| 512 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 513 | - getTh1ReturnCode(rc)); | |
| 514 | 467 | return rc; |
| 515 | 468 | } |
| 516 | 469 | |
| 517 | 470 | /* |
| 518 | 471 | ** TH1 command: tclExpr arg ?arg ...? |
| @@ -545,17 +498,13 @@ | ||
| 545 | 498 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 546 | 499 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 547 | 500 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 548 | 501 | return TH_ERROR; |
| 549 | 502 | } |
| 550 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 551 | - if( rc!=TH_OK ){ | |
| 552 | - return rc; | |
| 553 | - } | |
| 554 | 503 | Tcl_Preserve((ClientData)tclInterp); |
| 555 | 504 | if( argc==2 ){ |
| 556 | - objPtr = Tcl_NewStringObj(argv[1], argl[1]); | |
| 505 | + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); | |
| 557 | 506 | Tcl_IncrRefCount(objPtr); |
| 558 | 507 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 559 | 508 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 560 | 509 | }else{ |
| 561 | 510 | USE_ARGV_TO_OBJV(); |
| @@ -565,21 +514,21 @@ | ||
| 565 | 514 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 566 | 515 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 567 | 516 | FREE_ARGV_TO_OBJV(); |
| 568 | 517 | } |
| 569 | 518 | if( rc==TCL_OK ){ |
| 570 | - zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult); | |
| 519 | + Tcl_Size szResult = 0; | |
| 520 | + zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult); | |
| 521 | + nResult = (int)szResult; | |
| 571 | 522 | }else{ |
| 572 | 523 | zResult = getTclResult(tclInterp, &nResult); |
| 573 | 524 | } |
| 574 | - Th_SetResult(interp, zResult, nResult); | |
| 525 | + Th_SetResult(interp, zResult, (int)nResult); | |
| 575 | 526 | if( rc==TCL_OK ){ |
| 576 | 527 | Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; |
| 577 | 528 | } |
| 578 | 529 | Tcl_Release((ClientData)tclInterp); |
| 579 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 580 | - getTh1ReturnCode(rc)); | |
| 581 | 530 | return rc; |
| 582 | 531 | } |
| 583 | 532 | |
| 584 | 533 | /* |
| 585 | 534 | ** TH1 command: tclInvoke command ?arg ...? |
| @@ -610,20 +559,16 @@ | ||
| 610 | 559 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 611 | 560 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 612 | 561 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 613 | 562 | return TH_ERROR; |
| 614 | 563 | } |
| 615 | - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); | |
| 616 | - if( rc!=TH_OK ){ | |
| 617 | - return rc; | |
| 618 | - } | |
| 619 | 564 | Tcl_Preserve((ClientData)tclInterp); |
| 620 | 565 | #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV |
| 621 | 566 | if( GET_CTX_TCL_USEOBJPROC(ctx) ){ |
| 622 | 567 | Tcl_Command command; |
| 623 | 568 | Tcl_CmdInfo cmdInfo; |
| 624 | - Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]); | |
| 569 | + Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); | |
| 625 | 570 | Tcl_IncrRefCount(objPtr); |
| 626 | 571 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 627 | 572 | if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ |
| 628 | 573 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| 629 | 574 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| @@ -649,12 +594,10 @@ | ||
| 649 | 594 | FREE_ARGV_TO_OBJV(); |
| 650 | 595 | } |
| 651 | 596 | zResult = getTclResult(tclInterp, &nResult); |
| 652 | 597 | Th_SetResult(interp, zResult, nResult); |
| 653 | 598 | Tcl_Release((ClientData)tclInterp); |
| 654 | - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, | |
| 655 | - getTh1ReturnCode(rc)); | |
| 656 | 599 | return rc; |
| 657 | 600 | } |
| 658 | 601 | |
| 659 | 602 | /* |
| 660 | 603 | ** TH1 command: tclIsSafe |
| @@ -767,10 +710,11 @@ | ||
| 767 | 710 | int objc, |
| 768 | 711 | Tcl_Obj *const objv[] |
| 769 | 712 | ){ |
| 770 | 713 | Th_Interp *th1Interp; |
| 771 | 714 | int nArg; |
| 715 | + Tcl_Size szArg; | |
| 772 | 716 | const char *arg; |
| 773 | 717 | int rc; |
| 774 | 718 | |
| 775 | 719 | if( objc!=2 ){ |
| 776 | 720 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -779,14 +723,15 @@ | ||
| 779 | 723 | th1Interp = (Th_Interp *)clientData; |
| 780 | 724 | if( !th1Interp ){ |
| 781 | 725 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 782 | 726 | return TCL_ERROR; |
| 783 | 727 | } |
| 784 | - arg = Tcl_GetStringFromObj(objv[1], &nArg); | |
| 728 | + arg = Tcl_GetStringFromObj(objv[1], &szArg); | |
| 729 | + nArg = (int)szArg; | |
| 785 | 730 | rc = Th_Eval(th1Interp, 0, arg, nArg); |
| 786 | 731 | arg = Th_GetResult(th1Interp, &nArg); |
| 787 | - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); | |
| 732 | + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); | |
| 788 | 733 | return getTclReturnCode(rc); |
| 789 | 734 | } |
| 790 | 735 | |
| 791 | 736 | /* |
| 792 | 737 | ** Tcl command: th1Expr arg |
| @@ -800,10 +745,11 @@ | ||
| 800 | 745 | int objc, |
| 801 | 746 | Tcl_Obj *const objv[] |
| 802 | 747 | ){ |
| 803 | 748 | Th_Interp *th1Interp; |
| 804 | 749 | int nArg; |
| 750 | + Tcl_Size szArg; | |
| 805 | 751 | const char *arg; |
| 806 | 752 | int rc; |
| 807 | 753 | |
| 808 | 754 | if( objc!=2 ){ |
| 809 | 755 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -812,14 +758,14 @@ | ||
| 812 | 758 | th1Interp = (Th_Interp *)clientData; |
| 813 | 759 | if( !th1Interp ){ |
| 814 | 760 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 815 | 761 | return TCL_ERROR; |
| 816 | 762 | } |
| 817 | - arg = Tcl_GetStringFromObj(objv[1], &nArg); | |
| 818 | - rc = Th_Expr(th1Interp, arg, nArg); | |
| 763 | + arg = Tcl_GetStringFromObj(objv[1], &szArg); | |
| 764 | + rc = Th_Expr(th1Interp, arg, (int)szArg); | |
| 819 | 765 | arg = Th_GetResult(th1Interp, &nArg); |
| 820 | - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); | |
| 766 | + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); | |
| 821 | 767 | return getTclReturnCode(rc); |
| 822 | 768 | } |
| 823 | 769 | |
| 824 | 770 | /* |
| 825 | 771 | ** Array of Tcl integration commands. Used when adding or removing the Tcl |
| 826 | 772 |
| --- src/th_tcl.c | |
| +++ src/th_tcl.c | |
| @@ -24,10 +24,14 @@ | |
| 24 | |
| 25 | #include "sqlite3.h" |
| 26 | #include "th.h" |
| 27 | #include "tcl.h" |
| 28 | |
| 29 | /* |
| 30 | ** This macro is used to verify that the header version of Tcl meets some |
| 31 | ** minimum requirement. |
| 32 | */ |
| 33 | #define MINIMUM_TCL_VERSION(major, minor) \ |
| @@ -41,16 +45,16 @@ | |
| 41 | #define USE_ARGV_TO_OBJV() \ |
| 42 | int objc; \ |
| 43 | Tcl_Obj **objv; \ |
| 44 | int obji; |
| 45 | |
| 46 | #define COPY_ARGV_TO_OBJV() \ |
| 47 | objc = argc-1; \ |
| 48 | objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ |
| 49 | for(obji=1; obji<argc; obji++){ \ |
| 50 | objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \ |
| 51 | Tcl_IncrRefCount(objv[obji-1]); \ |
| 52 | } |
| 53 | |
| 54 | #define FREE_ARGV_TO_OBJV() \ |
| 55 | for(obji=1; obji<argc; obji++){ \ |
| 56 | Tcl_DecrRefCount(objv[obji-1]); \ |
| @@ -183,11 +187,15 @@ | |
| 183 | ** the only Tcl API functions that MUST be called prior to being able to call |
| 184 | ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete |
| 185 | ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp |
| 186 | ** and Tcl_Finalize function types are also required. |
| 187 | */ |
| 188 | typedef void (tcl_FindExecutableProc) (const char *); |
| 189 | typedef Tcl_Interp *(tcl_CreateInterpProc) (void); |
| 190 | typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); |
| 191 | typedef void (tcl_FinalizeProc) (void); |
| 192 | |
| 193 | /* |
| @@ -321,27 +329,10 @@ | |
| 321 | ** by the caller. This must be declared here because quite a few functions in |
| 322 | ** this file need to use it before it can be defined. |
| 323 | */ |
| 324 | static int createTclInterp(Th_Interp *interp, void *pContext); |
| 325 | |
| 326 | /* |
| 327 | ** Returns the TH1 return code corresponding to the specified Tcl |
| 328 | ** return code. |
| 329 | */ |
| 330 | static int getTh1ReturnCode( |
| 331 | int rc /* The Tcl return code value to convert. */ |
| 332 | ){ |
| 333 | switch( rc ){ |
| 334 | case /*0*/ TCL_OK: return /*0*/ TH_OK; |
| 335 | case /*1*/ TCL_ERROR: return /*1*/ TH_ERROR; |
| 336 | case /*2*/ TCL_RETURN: return /*3*/ TH_RETURN; |
| 337 | case /*3*/ TCL_BREAK: return /*2*/ TH_BREAK; |
| 338 | case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE; |
| 339 | default /*?*/: return /*?*/ rc; |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | /* |
| 344 | ** Returns the Tcl return code corresponding to the specified TH1 |
| 345 | ** return code. |
| 346 | */ |
| 347 | static int getTclReturnCode( |
| @@ -387,10 +378,12 @@ | |
| 387 | static char *getTclResult( |
| 388 | Tcl_Interp *pInterp, |
| 389 | int *pN |
| 390 | ){ |
| 391 | Tcl_Obj *resultPtr; |
| 392 | |
| 393 | if( !pInterp ){ /* This should not happen. */ |
| 394 | if( pN ) *pN = 0; |
| 395 | return 0; |
| 396 | } |
| @@ -397,11 +390,13 @@ | |
| 397 | resultPtr = Tcl_GetObjResult(pInterp); |
| 398 | if( !resultPtr ){ /* This should not happen either? */ |
| 399 | if( pN ) *pN = 0; |
| 400 | return 0; |
| 401 | } |
| 402 | return Tcl_GetStringFromObj(resultPtr, pN); |
| 403 | } |
| 404 | |
| 405 | /* |
| 406 | ** Tcl context information used by TH1. This structure definition has been |
| 407 | ** copied from and should be kept in sync with the one in "main.c". |
| @@ -416,48 +411,12 @@ | |
| 416 | tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */ |
| 417 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 418 | int useObjProc; /* Non-zero if an objProc can be called directly. */ |
| 419 | int useTip285; /* Non-zero if TIP #285 is available. */ |
| 420 | const char *setup; /* The optional Tcl setup script. */ |
| 421 | tcl_NotifyProc *xPreEval; /* Optional, called before Tcl_Eval*(). */ |
| 422 | void *pPreContext; /* Optional, provided to xPreEval(). */ |
| 423 | tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */ |
| 424 | void *pPostContext; /* Optional, provided to xPostEval(). */ |
| 425 | }; |
| 426 | |
| 427 | /* |
| 428 | ** This function calls the configured xPreEval or xPostEval functions, if any. |
| 429 | ** May have arbitrary side-effects. This function returns the result of the |
| 430 | ** called notification function or the value of the rc argument if there is no |
| 431 | ** notification function configured. |
| 432 | */ |
| 433 | static int notifyPreOrPostEval( |
| 434 | int bIsPost, |
| 435 | Th_Interp *interp, |
| 436 | void *ctx, |
| 437 | int argc, |
| 438 | const char **argv, |
| 439 | int *argl, |
| 440 | int rc |
| 441 | ){ |
| 442 | struct TclContext *tclContext = (struct TclContext *)ctx; |
| 443 | tcl_NotifyProc *xNotifyProc; |
| 444 | |
| 445 | if( !tclContext ){ |
| 446 | Th_ErrorMessage(interp, |
| 447 | "invalid Tcl context", (const char *)"", 0); |
| 448 | return TH_ERROR; |
| 449 | } |
| 450 | xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; |
| 451 | if( xNotifyProc ){ |
| 452 | rc = xNotifyProc(bIsPost ? |
| 453 | tclContext->pPostContext : tclContext->pPreContext, |
| 454 | interp, ctx, argc, argv, argl, rc); |
| 455 | } |
| 456 | return rc; |
| 457 | } |
| 458 | |
| 459 | /* |
| 460 | ** TH1 command: tclEval arg ?arg ...? |
| 461 | ** |
| 462 | ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script |
| 463 | ** error is generated, it will be transformed into a TH1 script error. The |
| @@ -485,17 +444,13 @@ | |
| 485 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 486 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 487 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 488 | return TH_ERROR; |
| 489 | } |
| 490 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 491 | if( rc!=TH_OK ){ |
| 492 | return rc; |
| 493 | } |
| 494 | Tcl_Preserve((ClientData)tclInterp); |
| 495 | if( argc==2 ){ |
| 496 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 497 | Tcl_IncrRefCount(objPtr); |
| 498 | rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); |
| 499 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 500 | }else{ |
| 501 | USE_ARGV_TO_OBJV(); |
| @@ -507,12 +462,10 @@ | |
| 507 | FREE_ARGV_TO_OBJV(); |
| 508 | } |
| 509 | zResult = getTclResult(tclInterp, &nResult); |
| 510 | Th_SetResult(interp, zResult, nResult); |
| 511 | Tcl_Release((ClientData)tclInterp); |
| 512 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, |
| 513 | getTh1ReturnCode(rc)); |
| 514 | return rc; |
| 515 | } |
| 516 | |
| 517 | /* |
| 518 | ** TH1 command: tclExpr arg ?arg ...? |
| @@ -545,17 +498,13 @@ | |
| 545 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 546 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 547 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 548 | return TH_ERROR; |
| 549 | } |
| 550 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 551 | if( rc!=TH_OK ){ |
| 552 | return rc; |
| 553 | } |
| 554 | Tcl_Preserve((ClientData)tclInterp); |
| 555 | if( argc==2 ){ |
| 556 | objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 557 | Tcl_IncrRefCount(objPtr); |
| 558 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 559 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 560 | }else{ |
| 561 | USE_ARGV_TO_OBJV(); |
| @@ -565,21 +514,21 @@ | |
| 565 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 566 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 567 | FREE_ARGV_TO_OBJV(); |
| 568 | } |
| 569 | if( rc==TCL_OK ){ |
| 570 | zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult); |
| 571 | }else{ |
| 572 | zResult = getTclResult(tclInterp, &nResult); |
| 573 | } |
| 574 | Th_SetResult(interp, zResult, nResult); |
| 575 | if( rc==TCL_OK ){ |
| 576 | Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; |
| 577 | } |
| 578 | Tcl_Release((ClientData)tclInterp); |
| 579 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, |
| 580 | getTh1ReturnCode(rc)); |
| 581 | return rc; |
| 582 | } |
| 583 | |
| 584 | /* |
| 585 | ** TH1 command: tclInvoke command ?arg ...? |
| @@ -610,20 +559,16 @@ | |
| 610 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 611 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 612 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 613 | return TH_ERROR; |
| 614 | } |
| 615 | rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); |
| 616 | if( rc!=TH_OK ){ |
| 617 | return rc; |
| 618 | } |
| 619 | Tcl_Preserve((ClientData)tclInterp); |
| 620 | #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV |
| 621 | if( GET_CTX_TCL_USEOBJPROC(ctx) ){ |
| 622 | Tcl_Command command; |
| 623 | Tcl_CmdInfo cmdInfo; |
| 624 | Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]); |
| 625 | Tcl_IncrRefCount(objPtr); |
| 626 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 627 | if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ |
| 628 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| 629 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| @@ -649,12 +594,10 @@ | |
| 649 | FREE_ARGV_TO_OBJV(); |
| 650 | } |
| 651 | zResult = getTclResult(tclInterp, &nResult); |
| 652 | Th_SetResult(interp, zResult, nResult); |
| 653 | Tcl_Release((ClientData)tclInterp); |
| 654 | rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, |
| 655 | getTh1ReturnCode(rc)); |
| 656 | return rc; |
| 657 | } |
| 658 | |
| 659 | /* |
| 660 | ** TH1 command: tclIsSafe |
| @@ -767,10 +710,11 @@ | |
| 767 | int objc, |
| 768 | Tcl_Obj *const objv[] |
| 769 | ){ |
| 770 | Th_Interp *th1Interp; |
| 771 | int nArg; |
| 772 | const char *arg; |
| 773 | int rc; |
| 774 | |
| 775 | if( objc!=2 ){ |
| 776 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -779,14 +723,15 @@ | |
| 779 | th1Interp = (Th_Interp *)clientData; |
| 780 | if( !th1Interp ){ |
| 781 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 782 | return TCL_ERROR; |
| 783 | } |
| 784 | arg = Tcl_GetStringFromObj(objv[1], &nArg); |
| 785 | rc = Th_Eval(th1Interp, 0, arg, nArg); |
| 786 | arg = Th_GetResult(th1Interp, &nArg); |
| 787 | Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); |
| 788 | return getTclReturnCode(rc); |
| 789 | } |
| 790 | |
| 791 | /* |
| 792 | ** Tcl command: th1Expr arg |
| @@ -800,10 +745,11 @@ | |
| 800 | int objc, |
| 801 | Tcl_Obj *const objv[] |
| 802 | ){ |
| 803 | Th_Interp *th1Interp; |
| 804 | int nArg; |
| 805 | const char *arg; |
| 806 | int rc; |
| 807 | |
| 808 | if( objc!=2 ){ |
| 809 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -812,14 +758,14 @@ | |
| 812 | th1Interp = (Th_Interp *)clientData; |
| 813 | if( !th1Interp ){ |
| 814 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 815 | return TCL_ERROR; |
| 816 | } |
| 817 | arg = Tcl_GetStringFromObj(objv[1], &nArg); |
| 818 | rc = Th_Expr(th1Interp, arg, nArg); |
| 819 | arg = Th_GetResult(th1Interp, &nArg); |
| 820 | Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); |
| 821 | return getTclReturnCode(rc); |
| 822 | } |
| 823 | |
| 824 | /* |
| 825 | ** Array of Tcl integration commands. Used when adding or removing the Tcl |
| 826 |
| --- src/th_tcl.c | |
| +++ src/th_tcl.c | |
| @@ -24,10 +24,14 @@ | |
| 24 | |
| 25 | #include "sqlite3.h" |
| 26 | #include "th.h" |
| 27 | #include "tcl.h" |
| 28 | |
| 29 | #if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size) |
| 30 | # define Tcl_Size int |
| 31 | #endif |
| 32 | |
| 33 | /* |
| 34 | ** This macro is used to verify that the header version of Tcl meets some |
| 35 | ** minimum requirement. |
| 36 | */ |
| 37 | #define MINIMUM_TCL_VERSION(major, minor) \ |
| @@ -41,16 +45,16 @@ | |
| 45 | #define USE_ARGV_TO_OBJV() \ |
| 46 | int objc; \ |
| 47 | Tcl_Obj **objv; \ |
| 48 | int obji; |
| 49 | |
| 50 | #define COPY_ARGV_TO_OBJV() \ |
| 51 | objc = argc-1; \ |
| 52 | objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ |
| 53 | for(obji=1; obji<argc; obji++){ \ |
| 54 | objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \ |
| 55 | Tcl_IncrRefCount(objv[obji-1]); \ |
| 56 | } |
| 57 | |
| 58 | #define FREE_ARGV_TO_OBJV() \ |
| 59 | for(obji=1; obji<argc; obji++){ \ |
| 60 | Tcl_DecrRefCount(objv[obji-1]); \ |
| @@ -183,11 +187,15 @@ | |
| 187 | ** the only Tcl API functions that MUST be called prior to being able to call |
| 188 | ** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete |
| 189 | ** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp |
| 190 | ** and Tcl_Finalize function types are also required. |
| 191 | */ |
| 192 | #if TCL_MAJOR_VERSION>=9 |
| 193 | typedef const char *(tcl_FindExecutableProc) (const char *); |
| 194 | #else |
| 195 | typedef void (tcl_FindExecutableProc) (const char *); |
| 196 | #endif |
| 197 | typedef Tcl_Interp *(tcl_CreateInterpProc) (void); |
| 198 | typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); |
| 199 | typedef void (tcl_FinalizeProc) (void); |
| 200 | |
| 201 | /* |
| @@ -321,27 +329,10 @@ | |
| 329 | ** by the caller. This must be declared here because quite a few functions in |
| 330 | ** this file need to use it before it can be defined. |
| 331 | */ |
| 332 | static int createTclInterp(Th_Interp *interp, void *pContext); |
| 333 | |
| 334 | /* |
| 335 | ** Returns the Tcl return code corresponding to the specified TH1 |
| 336 | ** return code. |
| 337 | */ |
| 338 | static int getTclReturnCode( |
| @@ -387,10 +378,12 @@ | |
| 378 | static char *getTclResult( |
| 379 | Tcl_Interp *pInterp, |
| 380 | int *pN |
| 381 | ){ |
| 382 | Tcl_Obj *resultPtr; |
| 383 | Tcl_Size n; |
| 384 | char *zRes; |
| 385 | |
| 386 | if( !pInterp ){ /* This should not happen. */ |
| 387 | if( pN ) *pN = 0; |
| 388 | return 0; |
| 389 | } |
| @@ -397,11 +390,13 @@ | |
| 390 | resultPtr = Tcl_GetObjResult(pInterp); |
| 391 | if( !resultPtr ){ /* This should not happen either? */ |
| 392 | if( pN ) *pN = 0; |
| 393 | return 0; |
| 394 | } |
| 395 | zRes = Tcl_GetStringFromObj(resultPtr, &n); |
| 396 | *pN = (int)n; |
| 397 | return zRes; |
| 398 | } |
| 399 | |
| 400 | /* |
| 401 | ** Tcl context information used by TH1. This structure definition has been |
| 402 | ** copied from and should be kept in sync with the one in "main.c". |
| @@ -416,48 +411,12 @@ | |
| 411 | tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */ |
| 412 | Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ |
| 413 | int useObjProc; /* Non-zero if an objProc can be called directly. */ |
| 414 | int useTip285; /* Non-zero if TIP #285 is available. */ |
| 415 | const char *setup; /* The optional Tcl setup script. */ |
| 416 | }; |
| 417 | |
| 418 | /* |
| 419 | ** TH1 command: tclEval arg ?arg ...? |
| 420 | ** |
| 421 | ** Evaluates the Tcl script and returns its result verbatim. If a Tcl script |
| 422 | ** error is generated, it will be transformed into a TH1 script error. The |
| @@ -485,17 +444,13 @@ | |
| 444 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 445 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 446 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 447 | return TH_ERROR; |
| 448 | } |
| 449 | Tcl_Preserve((ClientData)tclInterp); |
| 450 | if( argc==2 ){ |
| 451 | objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); |
| 452 | Tcl_IncrRefCount(objPtr); |
| 453 | rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); |
| 454 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 455 | }else{ |
| 456 | USE_ARGV_TO_OBJV(); |
| @@ -507,12 +462,10 @@ | |
| 462 | FREE_ARGV_TO_OBJV(); |
| 463 | } |
| 464 | zResult = getTclResult(tclInterp, &nResult); |
| 465 | Th_SetResult(interp, zResult, nResult); |
| 466 | Tcl_Release((ClientData)tclInterp); |
| 467 | return rc; |
| 468 | } |
| 469 | |
| 470 | /* |
| 471 | ** TH1 command: tclExpr arg ?arg ...? |
| @@ -545,17 +498,13 @@ | |
| 498 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 499 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 500 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 501 | return TH_ERROR; |
| 502 | } |
| 503 | Tcl_Preserve((ClientData)tclInterp); |
| 504 | if( argc==2 ){ |
| 505 | objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); |
| 506 | Tcl_IncrRefCount(objPtr); |
| 507 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 508 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 509 | }else{ |
| 510 | USE_ARGV_TO_OBJV(); |
| @@ -565,21 +514,21 @@ | |
| 514 | rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); |
| 515 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| 516 | FREE_ARGV_TO_OBJV(); |
| 517 | } |
| 518 | if( rc==TCL_OK ){ |
| 519 | Tcl_Size szResult = 0; |
| 520 | zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult); |
| 521 | nResult = (int)szResult; |
| 522 | }else{ |
| 523 | zResult = getTclResult(tclInterp, &nResult); |
| 524 | } |
| 525 | Th_SetResult(interp, zResult, (int)nResult); |
| 526 | if( rc==TCL_OK ){ |
| 527 | Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; |
| 528 | } |
| 529 | Tcl_Release((ClientData)tclInterp); |
| 530 | return rc; |
| 531 | } |
| 532 | |
| 533 | /* |
| 534 | ** TH1 command: tclInvoke command ?arg ...? |
| @@ -610,20 +559,16 @@ | |
| 559 | tclInterp = GET_CTX_TCL_INTERP(ctx); |
| 560 | if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ |
| 561 | Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); |
| 562 | return TH_ERROR; |
| 563 | } |
| 564 | Tcl_Preserve((ClientData)tclInterp); |
| 565 | #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV |
| 566 | if( GET_CTX_TCL_USEOBJPROC(ctx) ){ |
| 567 | Tcl_Command command; |
| 568 | Tcl_CmdInfo cmdInfo; |
| 569 | Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); |
| 570 | Tcl_IncrRefCount(objPtr); |
| 571 | command = Tcl_GetCommandFromObj(tclInterp, objPtr); |
| 572 | if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ |
| 573 | Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); |
| 574 | Tcl_DecrRefCount(objPtr); objPtr = 0; |
| @@ -649,12 +594,10 @@ | |
| 594 | FREE_ARGV_TO_OBJV(); |
| 595 | } |
| 596 | zResult = getTclResult(tclInterp, &nResult); |
| 597 | Th_SetResult(interp, zResult, nResult); |
| 598 | Tcl_Release((ClientData)tclInterp); |
| 599 | return rc; |
| 600 | } |
| 601 | |
| 602 | /* |
| 603 | ** TH1 command: tclIsSafe |
| @@ -767,10 +710,11 @@ | |
| 710 | int objc, |
| 711 | Tcl_Obj *const objv[] |
| 712 | ){ |
| 713 | Th_Interp *th1Interp; |
| 714 | int nArg; |
| 715 | Tcl_Size szArg; |
| 716 | const char *arg; |
| 717 | int rc; |
| 718 | |
| 719 | if( objc!=2 ){ |
| 720 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -779,14 +723,15 @@ | |
| 723 | th1Interp = (Th_Interp *)clientData; |
| 724 | if( !th1Interp ){ |
| 725 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 726 | return TCL_ERROR; |
| 727 | } |
| 728 | arg = Tcl_GetStringFromObj(objv[1], &szArg); |
| 729 | nArg = (int)szArg; |
| 730 | rc = Th_Eval(th1Interp, 0, arg, nArg); |
| 731 | arg = Th_GetResult(th1Interp, &nArg); |
| 732 | Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); |
| 733 | return getTclReturnCode(rc); |
| 734 | } |
| 735 | |
| 736 | /* |
| 737 | ** Tcl command: th1Expr arg |
| @@ -800,10 +745,11 @@ | |
| 745 | int objc, |
| 746 | Tcl_Obj *const objv[] |
| 747 | ){ |
| 748 | Th_Interp *th1Interp; |
| 749 | int nArg; |
| 750 | Tcl_Size szArg; |
| 751 | const char *arg; |
| 752 | int rc; |
| 753 | |
| 754 | if( objc!=2 ){ |
| 755 | Tcl_WrongNumArgs(interp, 1, objv, "arg"); |
| @@ -812,14 +758,14 @@ | |
| 758 | th1Interp = (Th_Interp *)clientData; |
| 759 | if( !th1Interp ){ |
| 760 | Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); |
| 761 | return TCL_ERROR; |
| 762 | } |
| 763 | arg = Tcl_GetStringFromObj(objv[1], &szArg); |
| 764 | rc = Th_Expr(th1Interp, arg, (int)szArg); |
| 765 | arg = Th_GetResult(th1Interp, &nArg); |
| 766 | Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); |
| 767 | return getTclReturnCode(rc); |
| 768 | } |
| 769 | |
| 770 | /* |
| 771 | ** Array of Tcl integration commands. Used when adding or removing the Tcl |
| 772 |
+2
-2
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -1888,11 +1888,11 @@ | ||
| 1888 | 1888 | if( zTagName ){ |
| 1889 | 1889 | zType = "ci"; |
| 1890 | 1890 | if( matchStyle==MS_EXACT ){ |
| 1891 | 1891 | /* For exact maching, inhibit links to the selected tag. */ |
| 1892 | 1892 | zThisTag = zTagName; |
| 1893 | - Th_Store("current_checkin", zTagName); | |
| 1893 | + Th_StoreUnsafe("current_checkin", zTagName); | |
| 1894 | 1894 | } |
| 1895 | 1895 | |
| 1896 | 1896 | /* Display a checkbox to enable/disable display of related check-ins. */ |
| 1897 | 1897 | if( advancedMenu ){ |
| 1898 | 1898 | style_submenu_checkbox("rel", "Related", 0, 0); |
| @@ -3847,11 +3847,11 @@ | ||
| 3847 | 3847 | ** Query parameters: |
| 3848 | 3848 | ** |
| 3849 | 3849 | ** today=DATE Use DATE as today's date |
| 3850 | 3850 | */ |
| 3851 | 3851 | void thisdayinhistory_page(void){ |
| 3852 | - static int aYearsAgo[] = { 1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 75, 100 }; | |
| 3852 | + static int aYearsAgo[] = { 1,2,3,4,5,10,15,20,25,30,40,50,75,100 }; | |
| 3853 | 3853 | const char *zToday; |
| 3854 | 3854 | char *zStartOfProject; |
| 3855 | 3855 | int i; |
| 3856 | 3856 | Stmt q; |
| 3857 | 3857 | char *z; |
| 3858 | 3858 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1888,11 +1888,11 @@ | |
| 1888 | if( zTagName ){ |
| 1889 | zType = "ci"; |
| 1890 | if( matchStyle==MS_EXACT ){ |
| 1891 | /* For exact maching, inhibit links to the selected tag. */ |
| 1892 | zThisTag = zTagName; |
| 1893 | Th_Store("current_checkin", zTagName); |
| 1894 | } |
| 1895 | |
| 1896 | /* Display a checkbox to enable/disable display of related check-ins. */ |
| 1897 | if( advancedMenu ){ |
| 1898 | style_submenu_checkbox("rel", "Related", 0, 0); |
| @@ -3847,11 +3847,11 @@ | |
| 3847 | ** Query parameters: |
| 3848 | ** |
| 3849 | ** today=DATE Use DATE as today's date |
| 3850 | */ |
| 3851 | void thisdayinhistory_page(void){ |
| 3852 | static int aYearsAgo[] = { 1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 75, 100 }; |
| 3853 | const char *zToday; |
| 3854 | char *zStartOfProject; |
| 3855 | int i; |
| 3856 | Stmt q; |
| 3857 | char *z; |
| 3858 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1888,11 +1888,11 @@ | |
| 1888 | if( zTagName ){ |
| 1889 | zType = "ci"; |
| 1890 | if( matchStyle==MS_EXACT ){ |
| 1891 | /* For exact maching, inhibit links to the selected tag. */ |
| 1892 | zThisTag = zTagName; |
| 1893 | Th_StoreUnsafe("current_checkin", zTagName); |
| 1894 | } |
| 1895 | |
| 1896 | /* Display a checkbox to enable/disable display of related check-ins. */ |
| 1897 | if( advancedMenu ){ |
| 1898 | style_submenu_checkbox("rel", "Related", 0, 0); |
| @@ -3847,11 +3847,11 @@ | |
| 3847 | ** Query parameters: |
| 3848 | ** |
| 3849 | ** today=DATE Use DATE as today's date |
| 3850 | */ |
| 3851 | void thisdayinhistory_page(void){ |
| 3852 | static int aYearsAgo[] = { 1,2,3,4,5,10,15,20,25,30,40,50,75,100 }; |
| 3853 | const char *zToday; |
| 3854 | char *zStartOfProject; |
| 3855 | int i; |
| 3856 | Stmt q; |
| 3857 | char *z; |
| 3858 |
+18
-12
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -188,14 +188,19 @@ | ||
| 188 | 188 | */ |
| 189 | 189 | static void initializeVariablesFromDb(void){ |
| 190 | 190 | const char *zName; |
| 191 | 191 | Stmt q; |
| 192 | 192 | int i, n, size, j; |
| 193 | + const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime"; | |
| 193 | 194 | |
| 194 | 195 | zName = PD("name","-none-"); |
| 195 | - db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, *" | |
| 196 | + db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, " | |
| 197 | + "datetime(%s,toLocal()) AS tkt_datetime_creation, " | |
| 198 | + "julianday('now') - tkt_mtime, " | |
| 199 | + "julianday('now') - %s, *" | |
| 196 | 200 | " FROM ticket WHERE tkt_uuid GLOB '%q*'", |
| 201 | + zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/, | |
| 197 | 202 | zName); |
| 198 | 203 | if( db_step(&q)==SQLITE_ROW ){ |
| 199 | 204 | n = db_column_count(&q); |
| 200 | 205 | for(i=0; i<n; i++){ |
| 201 | 206 | const char *zVal = db_column_text(&q, i); |
| @@ -207,19 +212,22 @@ | ||
| 207 | 212 | zVal = zRevealed = db_reveal(zVal); |
| 208 | 213 | } |
| 209 | 214 | if( (j = fieldId(zName))>=0 ){ |
| 210 | 215 | aField[j].zValue = mprintf("%s", zVal); |
| 211 | 216 | }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){ |
| 217 | + /* TICKET table columns that begin with "tkt_" are always safe */ | |
| 212 | 218 | Th_Store(zName, zVal); |
| 213 | 219 | } |
| 214 | 220 | free(zRevealed); |
| 215 | 221 | } |
| 222 | + Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2))); | |
| 223 | + Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3))); | |
| 216 | 224 | } |
| 217 | 225 | db_finalize(&q); |
| 218 | 226 | for(i=0; i<nField; i++){ |
| 219 | 227 | if( Th_Fetch(aField[i].zName, &size)==0 ){ |
| 220 | - Th_Store(aField[i].zName, aField[i].zValue); | |
| 228 | + Th_StoreUnsafe(aField[i].zName, aField[i].zValue); | |
| 221 | 229 | } |
| 222 | 230 | } |
| 223 | 231 | } |
| 224 | 232 | |
| 225 | 233 | /* |
| @@ -228,11 +236,11 @@ | ||
| 228 | 236 | static void initializeVariablesFromCGI(void){ |
| 229 | 237 | int i; |
| 230 | 238 | const char *z; |
| 231 | 239 | |
| 232 | 240 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 233 | - Th_Store(z, P(z)); | |
| 241 | + Th_StoreUnsafe(z, P(z)); | |
| 234 | 242 | } |
| 235 | 243 | } |
| 236 | 244 | |
| 237 | 245 | /* |
| 238 | 246 | ** Information about a single J-card |
| @@ -768,13 +776,10 @@ | ||
| 768 | 776 | style_submenu_element("Timeline", "%R/info/%T", zUuid); |
| 769 | 777 | } |
| 770 | 778 | zFullName = db_text(0, |
| 771 | 779 | "SELECT tkt_uuid FROM ticket" |
| 772 | 780 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 773 | - if( g.perm.WrWiki && g.perm.WrTkt ){ | |
| 774 | - style_submenu_element("Edit Description", "%R/wikiedit?name=ticket/%T", zFullName); | |
| 775 | - } | |
| 776 | 781 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1); |
| 777 | 782 | ticket_init(); |
| 778 | 783 | initializeVariablesFromCGI(); |
| 779 | 784 | getAllTicketFields(); |
| 780 | 785 | initializeVariablesFromDb(); |
| @@ -812,15 +817,15 @@ | ||
| 812 | 817 | if( argc!=3 ){ |
| 813 | 818 | return Th_WrongNumArgs(interp, "append_field FIELD STRING"); |
| 814 | 819 | } |
| 815 | 820 | if( g.thTrace ){ |
| 816 | 821 | Th_Trace("append_field %#h {%#h}<br>\n", |
| 817 | - argl[1], argv[1], argl[2], argv[2]); | |
| 822 | + TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]); | |
| 818 | 823 | } |
| 819 | 824 | for(idx=0; idx<nField; idx++){ |
| 820 | - if( memcmp(aField[idx].zName, argv[1], argl[1])==0 | |
| 821 | - && aField[idx].zName[argl[1]]==0 ){ | |
| 825 | + if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0 | |
| 826 | + && aField[idx].zName[TH1_LEN(argl[1])]==0 ){ | |
| 822 | 827 | break; |
| 823 | 828 | } |
| 824 | 829 | } |
| 825 | 830 | if( idx>=nField ){ |
| 826 | 831 | Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); |
| @@ -932,10 +937,11 @@ | ||
| 932 | 937 | const char *zValue; |
| 933 | 938 | int nValue; |
| 934 | 939 | if( aField[i].zAppend ) continue; |
| 935 | 940 | zValue = Th_Fetch(aField[i].zName, &nValue); |
| 936 | 941 | if( zValue ){ |
| 942 | + nValue = TH1_LEN(nValue); | |
| 937 | 943 | while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } |
| 938 | 944 | if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) |
| 939 | 945 | || memcmp(zValue, aField[i].zValue, nValue)!=0 |
| 940 | 946 | ||(int)strlen(aField[i].zValue)!=nValue |
| 941 | 947 | ){ |
| @@ -1034,16 +1040,16 @@ | ||
| 1034 | 1040 | if( uid ){ |
| 1035 | 1041 | char * zEmail = |
| 1036 | 1042 | db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d", |
| 1037 | 1043 | uid); |
| 1038 | 1044 | if( zEmail ){ |
| 1039 | - Th_Store("private_contact", zEmail); | |
| 1045 | + Th_StoreUnsafe("private_contact", zEmail); | |
| 1040 | 1046 | fossil_free(zEmail); |
| 1041 | 1047 | } |
| 1042 | 1048 | } |
| 1043 | 1049 | } |
| 1044 | - Th_Store("login", login_name()); | |
| 1050 | + Th_StoreUnsafe("login", login_name()); | |
| 1045 | 1051 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1046 | 1052 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, |
| 1047 | 1053 | (void*)&zNewUuid, 0); |
| 1048 | 1054 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1); |
| 1049 | 1055 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ |
| @@ -1114,11 +1120,11 @@ | ||
| 1114 | 1120 | initializeVariablesFromDb(); |
| 1115 | 1121 | if( g.zPath[0]=='d' ) showAllFields(); |
| 1116 | 1122 | form_begin(0, "%R/%s", g.zPath); |
| 1117 | 1123 | @ <input type="hidden" name="name" value="%s(zName)"> |
| 1118 | 1124 | zScript = ticket_editpage_code(); |
| 1119 | - Th_Store("login", login_name()); | |
| 1125 | + Th_StoreUnsafe("login", login_name()); | |
| 1120 | 1126 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1121 | 1127 | Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); |
| 1122 | 1128 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); |
| 1123 | 1129 | if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1); |
| 1124 | 1130 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ |
| 1125 | 1131 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -188,14 +188,19 @@ | |
| 188 | */ |
| 189 | static void initializeVariablesFromDb(void){ |
| 190 | const char *zName; |
| 191 | Stmt q; |
| 192 | int i, n, size, j; |
| 193 | |
| 194 | zName = PD("name","-none-"); |
| 195 | db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, *" |
| 196 | " FROM ticket WHERE tkt_uuid GLOB '%q*'", |
| 197 | zName); |
| 198 | if( db_step(&q)==SQLITE_ROW ){ |
| 199 | n = db_column_count(&q); |
| 200 | for(i=0; i<n; i++){ |
| 201 | const char *zVal = db_column_text(&q, i); |
| @@ -207,19 +212,22 @@ | |
| 207 | zVal = zRevealed = db_reveal(zVal); |
| 208 | } |
| 209 | if( (j = fieldId(zName))>=0 ){ |
| 210 | aField[j].zValue = mprintf("%s", zVal); |
| 211 | }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){ |
| 212 | Th_Store(zName, zVal); |
| 213 | } |
| 214 | free(zRevealed); |
| 215 | } |
| 216 | } |
| 217 | db_finalize(&q); |
| 218 | for(i=0; i<nField; i++){ |
| 219 | if( Th_Fetch(aField[i].zName, &size)==0 ){ |
| 220 | Th_Store(aField[i].zName, aField[i].zValue); |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | /* |
| @@ -228,11 +236,11 @@ | |
| 228 | static void initializeVariablesFromCGI(void){ |
| 229 | int i; |
| 230 | const char *z; |
| 231 | |
| 232 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 233 | Th_Store(z, P(z)); |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | /* |
| 238 | ** Information about a single J-card |
| @@ -768,13 +776,10 @@ | |
| 768 | style_submenu_element("Timeline", "%R/info/%T", zUuid); |
| 769 | } |
| 770 | zFullName = db_text(0, |
| 771 | "SELECT tkt_uuid FROM ticket" |
| 772 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 773 | if( g.perm.WrWiki && g.perm.WrTkt ){ |
| 774 | style_submenu_element("Edit Description", "%R/wikiedit?name=ticket/%T", zFullName); |
| 775 | } |
| 776 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1); |
| 777 | ticket_init(); |
| 778 | initializeVariablesFromCGI(); |
| 779 | getAllTicketFields(); |
| 780 | initializeVariablesFromDb(); |
| @@ -812,15 +817,15 @@ | |
| 812 | if( argc!=3 ){ |
| 813 | return Th_WrongNumArgs(interp, "append_field FIELD STRING"); |
| 814 | } |
| 815 | if( g.thTrace ){ |
| 816 | Th_Trace("append_field %#h {%#h}<br>\n", |
| 817 | argl[1], argv[1], argl[2], argv[2]); |
| 818 | } |
| 819 | for(idx=0; idx<nField; idx++){ |
| 820 | if( memcmp(aField[idx].zName, argv[1], argl[1])==0 |
| 821 | && aField[idx].zName[argl[1]]==0 ){ |
| 822 | break; |
| 823 | } |
| 824 | } |
| 825 | if( idx>=nField ){ |
| 826 | Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); |
| @@ -932,10 +937,11 @@ | |
| 932 | const char *zValue; |
| 933 | int nValue; |
| 934 | if( aField[i].zAppend ) continue; |
| 935 | zValue = Th_Fetch(aField[i].zName, &nValue); |
| 936 | if( zValue ){ |
| 937 | while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } |
| 938 | if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) |
| 939 | || memcmp(zValue, aField[i].zValue, nValue)!=0 |
| 940 | ||(int)strlen(aField[i].zValue)!=nValue |
| 941 | ){ |
| @@ -1034,16 +1040,16 @@ | |
| 1034 | if( uid ){ |
| 1035 | char * zEmail = |
| 1036 | db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d", |
| 1037 | uid); |
| 1038 | if( zEmail ){ |
| 1039 | Th_Store("private_contact", zEmail); |
| 1040 | fossil_free(zEmail); |
| 1041 | } |
| 1042 | } |
| 1043 | } |
| 1044 | Th_Store("login", login_name()); |
| 1045 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1046 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, |
| 1047 | (void*)&zNewUuid, 0); |
| 1048 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1); |
| 1049 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ |
| @@ -1114,11 +1120,11 @@ | |
| 1114 | initializeVariablesFromDb(); |
| 1115 | if( g.zPath[0]=='d' ) showAllFields(); |
| 1116 | form_begin(0, "%R/%s", g.zPath); |
| 1117 | @ <input type="hidden" name="name" value="%s(zName)"> |
| 1118 | zScript = ticket_editpage_code(); |
| 1119 | Th_Store("login", login_name()); |
| 1120 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1121 | Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); |
| 1122 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); |
| 1123 | if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1); |
| 1124 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ |
| 1125 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -188,14 +188,19 @@ | |
| 188 | */ |
| 189 | static void initializeVariablesFromDb(void){ |
| 190 | const char *zName; |
| 191 | Stmt q; |
| 192 | int i, n, size, j; |
| 193 | const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime"; |
| 194 | |
| 195 | zName = PD("name","-none-"); |
| 196 | db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, " |
| 197 | "datetime(%s,toLocal()) AS tkt_datetime_creation, " |
| 198 | "julianday('now') - tkt_mtime, " |
| 199 | "julianday('now') - %s, *" |
| 200 | " FROM ticket WHERE tkt_uuid GLOB '%q*'", |
| 201 | zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/, |
| 202 | zName); |
| 203 | if( db_step(&q)==SQLITE_ROW ){ |
| 204 | n = db_column_count(&q); |
| 205 | for(i=0; i<n; i++){ |
| 206 | const char *zVal = db_column_text(&q, i); |
| @@ -207,19 +212,22 @@ | |
| 212 | zVal = zRevealed = db_reveal(zVal); |
| 213 | } |
| 214 | if( (j = fieldId(zName))>=0 ){ |
| 215 | aField[j].zValue = mprintf("%s", zVal); |
| 216 | }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){ |
| 217 | /* TICKET table columns that begin with "tkt_" are always safe */ |
| 218 | Th_Store(zName, zVal); |
| 219 | } |
| 220 | free(zRevealed); |
| 221 | } |
| 222 | Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2))); |
| 223 | Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3))); |
| 224 | } |
| 225 | db_finalize(&q); |
| 226 | for(i=0; i<nField; i++){ |
| 227 | if( Th_Fetch(aField[i].zName, &size)==0 ){ |
| 228 | Th_StoreUnsafe(aField[i].zName, aField[i].zValue); |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | /* |
| @@ -228,11 +236,11 @@ | |
| 236 | static void initializeVariablesFromCGI(void){ |
| 237 | int i; |
| 238 | const char *z; |
| 239 | |
| 240 | for(i=0; (z = cgi_parameter_name(i))!=0; i++){ |
| 241 | Th_StoreUnsafe(z, P(z)); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | ** Information about a single J-card |
| @@ -768,13 +776,10 @@ | |
| 776 | style_submenu_element("Timeline", "%R/info/%T", zUuid); |
| 777 | } |
| 778 | zFullName = db_text(0, |
| 779 | "SELECT tkt_uuid FROM ticket" |
| 780 | " WHERE tkt_uuid GLOB '%q*'", zUuid); |
| 781 | if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br>\n", -1); |
| 782 | ticket_init(); |
| 783 | initializeVariablesFromCGI(); |
| 784 | getAllTicketFields(); |
| 785 | initializeVariablesFromDb(); |
| @@ -812,15 +817,15 @@ | |
| 817 | if( argc!=3 ){ |
| 818 | return Th_WrongNumArgs(interp, "append_field FIELD STRING"); |
| 819 | } |
| 820 | if( g.thTrace ){ |
| 821 | Th_Trace("append_field %#h {%#h}<br>\n", |
| 822 | TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]); |
| 823 | } |
| 824 | for(idx=0; idx<nField; idx++){ |
| 825 | if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0 |
| 826 | && aField[idx].zName[TH1_LEN(argl[1])]==0 ){ |
| 827 | break; |
| 828 | } |
| 829 | } |
| 830 | if( idx>=nField ){ |
| 831 | Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); |
| @@ -932,10 +937,11 @@ | |
| 937 | const char *zValue; |
| 938 | int nValue; |
| 939 | if( aField[i].zAppend ) continue; |
| 940 | zValue = Th_Fetch(aField[i].zName, &nValue); |
| 941 | if( zValue ){ |
| 942 | nValue = TH1_LEN(nValue); |
| 943 | while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } |
| 944 | if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) |
| 945 | || memcmp(zValue, aField[i].zValue, nValue)!=0 |
| 946 | ||(int)strlen(aField[i].zValue)!=nValue |
| 947 | ){ |
| @@ -1034,16 +1040,16 @@ | |
| 1040 | if( uid ){ |
| 1041 | char * zEmail = |
| 1042 | db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d", |
| 1043 | uid); |
| 1044 | if( zEmail ){ |
| 1045 | Th_StoreUnsafe("private_contact", zEmail); |
| 1046 | fossil_free(zEmail); |
| 1047 | } |
| 1048 | } |
| 1049 | } |
| 1050 | Th_StoreUnsafe("login", login_name()); |
| 1051 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1052 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, |
| 1053 | (void*)&zNewUuid, 0); |
| 1054 | if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1); |
| 1055 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ |
| @@ -1114,11 +1120,11 @@ | |
| 1120 | initializeVariablesFromDb(); |
| 1121 | if( g.zPath[0]=='d' ) showAllFields(); |
| 1122 | form_begin(0, "%R/%s", g.zPath); |
| 1123 | @ <input type="hidden" name="name" value="%s(zName)"> |
| 1124 | zScript = ticket_editpage_code(); |
| 1125 | Th_StoreUnsafe("login", login_name()); |
| 1126 | Th_Store("date", db_text(0, "SELECT datetime('now')")); |
| 1127 | Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); |
| 1128 | Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); |
| 1129 | if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1); |
| 1130 | if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ |
| 1131 |
+101
-12
| --- src/tktsetup.c | ||
| +++ src/tktsetup.c | ||
| @@ -125,11 +125,11 @@ | ||
| 125 | 125 | if( !g.perm.Setup ){ |
| 126 | 126 | login_needed(0); |
| 127 | 127 | return; |
| 128 | 128 | } |
| 129 | 129 | style_set_current_feature("tktsetup"); |
| 130 | - if( PB("setup") ){ | |
| 130 | + if( P("setup") ){ | |
| 131 | 131 | cgi_redirect("tktsetup"); |
| 132 | 132 | } |
| 133 | 133 | isSubmit = P("submit")!=0; |
| 134 | 134 | z = P("x"); |
| 135 | 135 | if( z==0 ){ |
| @@ -164,10 +164,11 @@ | ||
| 164 | 164 | @ <hr> |
| 165 | 165 | @ <h2>Default %s(zTitle)</h2> |
| 166 | 166 | @ <blockquote><pre> |
| 167 | 167 | @ %h(zDfltValue) |
| 168 | 168 | @ </pre></blockquote> |
| 169 | + style_submenu_element("Back", "%R/tktsetup"); | |
| 169 | 170 | style_finish_page(); |
| 170 | 171 | } |
| 171 | 172 | |
| 172 | 173 | /* |
| 173 | 174 | ** WEBPAGE: tktsetup_tab |
| @@ -480,11 +481,11 @@ | ||
| 480 | 481 | @ <th1> |
| 481 | 482 | @ if {[info exists tkt_uuid]} { |
| 482 | 483 | @ html "<td class='tktDspValue' colspan='3'>" |
| 483 | 484 | @ copybtn hash-tk 0 $tkt_uuid 2 |
| 484 | 485 | @ if {[hascap s]} { |
| 485 | -@ html " ($tkt_id)" | |
| 486 | +@ puts " ($tkt_id)" | |
| 486 | 487 | @ } |
| 487 | 488 | @ html "</td></tr>\n" |
| 488 | 489 | @ } else { |
| 489 | 490 | @ if {[hascap s]} { |
| 490 | 491 | @ html "<td class='tktDspValue' colspan='3'>Deleted " |
| @@ -495,10 +496,13 @@ | ||
| 495 | 496 | @ } |
| 496 | 497 | @ |
| 497 | 498 | @ if {[capexpr {n}]} { |
| 498 | 499 | @ submenu link "Copy Ticket" /tktnew/$tkt_uuid |
| 499 | 500 | @ } |
| 501 | +@ if {[capexpr {nk}]} { | |
| 502 | +@ submenu link "Edit Wiki" /wikiedit?name=ticket/$tkt_uuid | |
| 503 | +@ } | |
| 500 | 504 | @ </th1> |
| 501 | 505 | @ <tr><td class="tktDspLabel">Title:</td> |
| 502 | 506 | @ <td class="tktDspValue" colspan="3"> |
| 503 | 507 | @ $<title> |
| 504 | 508 | @ </td></tr> |
| @@ -521,23 +525,63 @@ | ||
| 521 | 525 | @ $<resolution> |
| 522 | 526 | @ </td></tr> |
| 523 | 527 | @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> |
| 524 | 528 | @ <th1> |
| 525 | 529 | @ if {[info exists tkt_datetime]} { |
| 526 | -@ html $tkt_datetime | |
| 530 | +@ puts $tkt_datetime | |
| 531 | +@ } | |
| 532 | +@ if {[info exists tkt_mage]} { | |
| 533 | +@ html "<br>[htmlize $tkt_mage] ago" | |
| 527 | 534 | @ } |
| 528 | 535 | @ </th1> |
| 529 | 536 | @ </td> |
| 537 | +@ <td class="tktDspLabel">Created:</td><td class="tktDspValue"> | |
| 538 | +@ <th1> | |
| 539 | +@ if {[info exists tkt_datetime_creation]} { | |
| 540 | +@ puts $tkt_datetime_creation | |
| 541 | +@ } | |
| 542 | +@ if {[info exists tkt_cage]} { | |
| 543 | +@ html "<br>[htmlize $tkt_cage] ago" | |
| 544 | +@ } | |
| 545 | +@ </th1> | |
| 546 | +@ </td></tr> | |
| 530 | 547 | @ <th1>enable_output [hascap e]</th1> |
| 531 | -@ <td class="tktDspLabel">Contact:</td><td class="tktDspValue"> | |
| 548 | +@ <tr> | |
| 549 | +@ <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3"> | |
| 532 | 550 | @ $<private_contact> |
| 533 | 551 | @ </td> |
| 552 | +@ </tr> | |
| 534 | 553 | @ <th1>enable_output 1</th1> |
| 535 | -@ </tr> | |
| 536 | 554 | @ <tr><td class="tktDspLabel">Version Found In:</td> |
| 537 | 555 | @ <td colspan="3" valign="top" class="tktDspValue"> |
| 538 | -@ $<foundin> | |
| 556 | +@ <th1> | |
| 557 | +@ set versionlink "" | |
| 558 | +@ set urlfoundin [httpize $foundin] | |
| 559 | +@ set tagpattern {^[-0-9A-Za-z_\\.]+$} | |
| 560 | +@ if [regexp $tagpattern $foundin] { | |
| 561 | +@ query {SELECT count(*) AS match FROM tag | |
| 562 | +@ WHERE tagname=concat('sym-',$foundin)} { | |
| 563 | +@ if {$match} {set versionlink "timeline?t=$urlfoundin"} | |
| 564 | +@ } | |
| 565 | +@ } | |
| 566 | +@ set hashpattern {^[0-9a-f]+$} | |
| 567 | +@ if [regexp $hashpattern $foundin] { | |
| 568 | +@ set pattern $foundin* | |
| 569 | +@ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} { | |
| 570 | +@ if {$match} {set versionlink "info/$urlfoundin"} | |
| 571 | +@ } | |
| 572 | +@ } | |
| 573 | +@ if {$versionlink eq ""} { | |
| 574 | +@ puts $foundin | |
| 575 | +@ } else { | |
| 576 | +@ html "<a href=\"" | |
| 577 | +@ puts $versionlink | |
| 578 | +@ html "\">" | |
| 579 | +@ puts $foundin | |
| 580 | +@ html "</a>" | |
| 581 | +@ } | |
| 582 | +@ </th1> | |
| 539 | 583 | @ </td></tr> |
| 540 | 584 | @ </table> |
| 541 | 585 | @ |
| 542 | 586 | @ <th1> |
| 543 | 587 | @ wiki_assoc "ticket" $tkt_uuid |
| @@ -567,24 +611,25 @@ | ||
| 567 | 611 | @ FROM ticketchng |
| 568 | 612 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 569 | 613 | @ if {$seenRow} { |
| 570 | 614 | @ html "<hr>\n" |
| 571 | 615 | @ } else { |
| 572 | -@ html "<tr><td class='tktDspLabel' style='text-align:left'>User Comments:</td></tr>\n" | |
| 616 | +@ html "<tr><td class='tktDspLabel' style='text-align:left'>\n" | |
| 617 | +@ html "User Comments:</td></tr>\n" | |
| 573 | 618 | @ html "<tr><td colspan='5' class='tktDspValue'>\n" |
| 574 | 619 | @ set seenRow 1 |
| 575 | 620 | @ } |
| 576 | 621 | @ html "<span class='tktDspCommenter'>" |
| 577 | -@ html "[htmlize $xlogin]" | |
| 622 | +@ puts $xlogin | |
| 578 | 623 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 579 | -@ html " (claiming to be [htmlize $xusername])" | |
| 624 | +@ puts " (claiming to be $xusername)" | |
| 580 | 625 | @ } |
| 581 | -@ html " added on $xdate:" | |
| 626 | +@ puts " added on $xdate:" | |
| 582 | 627 | @ html "</span>\n" |
| 583 | 628 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 584 | 629 | @ set r [randhex] |
| 585 | -@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} | |
| 630 | +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} | |
| 586 | 631 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 587 | 632 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 588 | 633 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 589 | 634 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 590 | 635 | @ html [lindex [markdown $xcomment] 1] |
| @@ -741,10 +786,52 @@ | ||
| 741 | 786 | @ <input type="submit" name="cancel" value="Cancel"> |
| 742 | 787 | @ </td> |
| 743 | 788 | @ <td>Abandon this edit</td> |
| 744 | 789 | @ </tr> |
| 745 | 790 | @ |
| 791 | +@ <th1> | |
| 792 | +@ set seenRow 0 | |
| 793 | +@ set alwaysPlaintext [info exists plaintext] | |
| 794 | +@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, | |
| 795 | +@ mimetype as xmimetype, icomment AS xcomment, | |
| 796 | +@ username AS xusername | |
| 797 | +@ FROM ticketchng | |
| 798 | +@ WHERE tkt_id=$tkt_id AND length(icomment)>0} { | |
| 799 | +@ if {$seenRow} { | |
| 800 | +@ html "<hr>\n" | |
| 801 | +@ } else { | |
| 802 | +@ html "<tr><td colspan='2'><hr></td></tr>\n" | |
| 803 | +@ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n" | |
| 804 | +@ html "Previous User Comments:</td></tr>\n" | |
| 805 | +@ html "<tr><td colspan='2' class='tktDspValue'>\n" | |
| 806 | +@ set seenRow 1 | |
| 807 | +@ } | |
| 808 | +@ html "<span class='tktDspCommenter'>" | |
| 809 | +@ puts $xlogin | |
| 810 | +@ if {$xlogin ne $xusername && [string length $xusername]>0} { | |
| 811 | +@ puts " (claiming to be $xusername)" | |
| 812 | +@ } | |
| 813 | +@ puts " added on $xdate:" | |
| 814 | +@ html "</span>\n" | |
| 815 | +@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { | |
| 816 | +@ set r [randhex] | |
| 817 | +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} | |
| 818 | +@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" | |
| 819 | +@ } elseif {$xmimetype eq "text/x-fossil-wiki"} { | |
| 820 | +@ wiki "<p>\n[string trimright $xcomment]\n</p>\n" | |
| 821 | +@ } elseif {$xmimetype eq "text/x-markdown"} { | |
| 822 | +@ html [lindex [markdown $xcomment] 1] | |
| 823 | +@ } elseif {$xmimetype eq "text/html"} { | |
| 824 | +@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" | |
| 825 | +@ } else { | |
| 826 | +@ set r [randhex] | |
| 827 | +@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" | |
| 828 | +@ } | |
| 829 | +@ } | |
| 830 | +@ if {$seenRow} {html "</td></tr>\n"} | |
| 831 | +@ </th1> | |
| 832 | +@ | |
| 746 | 833 | @ </table> |
| 747 | 834 | ; |
| 748 | 835 | |
| 749 | 836 | /* |
| 750 | 837 | ** Return the code used to generate the edit ticket page |
| @@ -839,11 +926,12 @@ | ||
| 839 | 926 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 840 | 927 | @ WHEN status='Tested' THEN '#bde5d6' |
| 841 | 928 | @ WHEN status='Deferred' THEN '#cacae5' |
| 842 | 929 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 843 | 930 | @ substr(tkt_uuid,1,10) AS '#', |
| 844 | -@ datetime(tkt_mtime) AS 'mtime', | |
| 931 | +@ datetime(tkt_ctime) AS 'created', | |
| 932 | +@ datetime(tkt_mtime) AS 'modified', | |
| 845 | 933 | @ type, |
| 846 | 934 | @ status, |
| 847 | 935 | @ subsystem, |
| 848 | 936 | @ title, |
| 849 | 937 | @ comment AS '_comments' |
| @@ -973,8 +1061,9 @@ | ||
| 973 | 1061 | @ <input type="submit" name="submit" value="Apply Changes"> |
| 974 | 1062 | @ <input type="submit" name="setup" value="Cancel"> |
| 975 | 1063 | @ </p> |
| 976 | 1064 | @ </div></form> |
| 977 | 1065 | db_end_transaction(0); |
| 1066 | + style_submenu_element("Back", "%R/tktsetup"); | |
| 978 | 1067 | style_finish_page(); |
| 979 | 1068 | |
| 980 | 1069 | } |
| 981 | 1070 |
| --- src/tktsetup.c | |
| +++ src/tktsetup.c | |
| @@ -125,11 +125,11 @@ | |
| 125 | if( !g.perm.Setup ){ |
| 126 | login_needed(0); |
| 127 | return; |
| 128 | } |
| 129 | style_set_current_feature("tktsetup"); |
| 130 | if( PB("setup") ){ |
| 131 | cgi_redirect("tktsetup"); |
| 132 | } |
| 133 | isSubmit = P("submit")!=0; |
| 134 | z = P("x"); |
| 135 | if( z==0 ){ |
| @@ -164,10 +164,11 @@ | |
| 164 | @ <hr> |
| 165 | @ <h2>Default %s(zTitle)</h2> |
| 166 | @ <blockquote><pre> |
| 167 | @ %h(zDfltValue) |
| 168 | @ </pre></blockquote> |
| 169 | style_finish_page(); |
| 170 | } |
| 171 | |
| 172 | /* |
| 173 | ** WEBPAGE: tktsetup_tab |
| @@ -480,11 +481,11 @@ | |
| 480 | @ <th1> |
| 481 | @ if {[info exists tkt_uuid]} { |
| 482 | @ html "<td class='tktDspValue' colspan='3'>" |
| 483 | @ copybtn hash-tk 0 $tkt_uuid 2 |
| 484 | @ if {[hascap s]} { |
| 485 | @ html " ($tkt_id)" |
| 486 | @ } |
| 487 | @ html "</td></tr>\n" |
| 488 | @ } else { |
| 489 | @ if {[hascap s]} { |
| 490 | @ html "<td class='tktDspValue' colspan='3'>Deleted " |
| @@ -495,10 +496,13 @@ | |
| 495 | @ } |
| 496 | @ |
| 497 | @ if {[capexpr {n}]} { |
| 498 | @ submenu link "Copy Ticket" /tktnew/$tkt_uuid |
| 499 | @ } |
| 500 | @ </th1> |
| 501 | @ <tr><td class="tktDspLabel">Title:</td> |
| 502 | @ <td class="tktDspValue" colspan="3"> |
| 503 | @ $<title> |
| 504 | @ </td></tr> |
| @@ -521,23 +525,63 @@ | |
| 521 | @ $<resolution> |
| 522 | @ </td></tr> |
| 523 | @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> |
| 524 | @ <th1> |
| 525 | @ if {[info exists tkt_datetime]} { |
| 526 | @ html $tkt_datetime |
| 527 | @ } |
| 528 | @ </th1> |
| 529 | @ </td> |
| 530 | @ <th1>enable_output [hascap e]</th1> |
| 531 | @ <td class="tktDspLabel">Contact:</td><td class="tktDspValue"> |
| 532 | @ $<private_contact> |
| 533 | @ </td> |
| 534 | @ <th1>enable_output 1</th1> |
| 535 | @ </tr> |
| 536 | @ <tr><td class="tktDspLabel">Version Found In:</td> |
| 537 | @ <td colspan="3" valign="top" class="tktDspValue"> |
| 538 | @ $<foundin> |
| 539 | @ </td></tr> |
| 540 | @ </table> |
| 541 | @ |
| 542 | @ <th1> |
| 543 | @ wiki_assoc "ticket" $tkt_uuid |
| @@ -567,24 +611,25 @@ | |
| 567 | @ FROM ticketchng |
| 568 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 569 | @ if {$seenRow} { |
| 570 | @ html "<hr>\n" |
| 571 | @ } else { |
| 572 | @ html "<tr><td class='tktDspLabel' style='text-align:left'>User Comments:</td></tr>\n" |
| 573 | @ html "<tr><td colspan='5' class='tktDspValue'>\n" |
| 574 | @ set seenRow 1 |
| 575 | @ } |
| 576 | @ html "<span class='tktDspCommenter'>" |
| 577 | @ html "[htmlize $xlogin]" |
| 578 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 579 | @ html " (claiming to be [htmlize $xusername])" |
| 580 | @ } |
| 581 | @ html " added on $xdate:" |
| 582 | @ html "</span>\n" |
| 583 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 584 | @ set r [randhex] |
| 585 | @ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} |
| 586 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 587 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 588 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 589 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 590 | @ html [lindex [markdown $xcomment] 1] |
| @@ -741,10 +786,52 @@ | |
| 741 | @ <input type="submit" name="cancel" value="Cancel"> |
| 742 | @ </td> |
| 743 | @ <td>Abandon this edit</td> |
| 744 | @ </tr> |
| 745 | @ |
| 746 | @ </table> |
| 747 | ; |
| 748 | |
| 749 | /* |
| 750 | ** Return the code used to generate the edit ticket page |
| @@ -839,11 +926,12 @@ | |
| 839 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 840 | @ WHEN status='Tested' THEN '#bde5d6' |
| 841 | @ WHEN status='Deferred' THEN '#cacae5' |
| 842 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 843 | @ substr(tkt_uuid,1,10) AS '#', |
| 844 | @ datetime(tkt_mtime) AS 'mtime', |
| 845 | @ type, |
| 846 | @ status, |
| 847 | @ subsystem, |
| 848 | @ title, |
| 849 | @ comment AS '_comments' |
| @@ -973,8 +1061,9 @@ | |
| 973 | @ <input type="submit" name="submit" value="Apply Changes"> |
| 974 | @ <input type="submit" name="setup" value="Cancel"> |
| 975 | @ </p> |
| 976 | @ </div></form> |
| 977 | db_end_transaction(0); |
| 978 | style_finish_page(); |
| 979 | |
| 980 | } |
| 981 |
| --- src/tktsetup.c | |
| +++ src/tktsetup.c | |
| @@ -125,11 +125,11 @@ | |
| 125 | if( !g.perm.Setup ){ |
| 126 | login_needed(0); |
| 127 | return; |
| 128 | } |
| 129 | style_set_current_feature("tktsetup"); |
| 130 | if( P("setup") ){ |
| 131 | cgi_redirect("tktsetup"); |
| 132 | } |
| 133 | isSubmit = P("submit")!=0; |
| 134 | z = P("x"); |
| 135 | if( z==0 ){ |
| @@ -164,10 +164,11 @@ | |
| 164 | @ <hr> |
| 165 | @ <h2>Default %s(zTitle)</h2> |
| 166 | @ <blockquote><pre> |
| 167 | @ %h(zDfltValue) |
| 168 | @ </pre></blockquote> |
| 169 | style_submenu_element("Back", "%R/tktsetup"); |
| 170 | style_finish_page(); |
| 171 | } |
| 172 | |
| 173 | /* |
| 174 | ** WEBPAGE: tktsetup_tab |
| @@ -480,11 +481,11 @@ | |
| 481 | @ <th1> |
| 482 | @ if {[info exists tkt_uuid]} { |
| 483 | @ html "<td class='tktDspValue' colspan='3'>" |
| 484 | @ copybtn hash-tk 0 $tkt_uuid 2 |
| 485 | @ if {[hascap s]} { |
| 486 | @ puts " ($tkt_id)" |
| 487 | @ } |
| 488 | @ html "</td></tr>\n" |
| 489 | @ } else { |
| 490 | @ if {[hascap s]} { |
| 491 | @ html "<td class='tktDspValue' colspan='3'>Deleted " |
| @@ -495,10 +496,13 @@ | |
| 496 | @ } |
| 497 | @ |
| 498 | @ if {[capexpr {n}]} { |
| 499 | @ submenu link "Copy Ticket" /tktnew/$tkt_uuid |
| 500 | @ } |
| 501 | @ if {[capexpr {nk}]} { |
| 502 | @ submenu link "Edit Wiki" /wikiedit?name=ticket/$tkt_uuid |
| 503 | @ } |
| 504 | @ </th1> |
| 505 | @ <tr><td class="tktDspLabel">Title:</td> |
| 506 | @ <td class="tktDspValue" colspan="3"> |
| 507 | @ $<title> |
| 508 | @ </td></tr> |
| @@ -521,23 +525,63 @@ | |
| 525 | @ $<resolution> |
| 526 | @ </td></tr> |
| 527 | @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> |
| 528 | @ <th1> |
| 529 | @ if {[info exists tkt_datetime]} { |
| 530 | @ puts $tkt_datetime |
| 531 | @ } |
| 532 | @ if {[info exists tkt_mage]} { |
| 533 | @ html "<br>[htmlize $tkt_mage] ago" |
| 534 | @ } |
| 535 | @ </th1> |
| 536 | @ </td> |
| 537 | @ <td class="tktDspLabel">Created:</td><td class="tktDspValue"> |
| 538 | @ <th1> |
| 539 | @ if {[info exists tkt_datetime_creation]} { |
| 540 | @ puts $tkt_datetime_creation |
| 541 | @ } |
| 542 | @ if {[info exists tkt_cage]} { |
| 543 | @ html "<br>[htmlize $tkt_cage] ago" |
| 544 | @ } |
| 545 | @ </th1> |
| 546 | @ </td></tr> |
| 547 | @ <th1>enable_output [hascap e]</th1> |
| 548 | @ <tr> |
| 549 | @ <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3"> |
| 550 | @ $<private_contact> |
| 551 | @ </td> |
| 552 | @ </tr> |
| 553 | @ <th1>enable_output 1</th1> |
| 554 | @ <tr><td class="tktDspLabel">Version Found In:</td> |
| 555 | @ <td colspan="3" valign="top" class="tktDspValue"> |
| 556 | @ <th1> |
| 557 | @ set versionlink "" |
| 558 | @ set urlfoundin [httpize $foundin] |
| 559 | @ set tagpattern {^[-0-9A-Za-z_\\.]+$} |
| 560 | @ if [regexp $tagpattern $foundin] { |
| 561 | @ query {SELECT count(*) AS match FROM tag |
| 562 | @ WHERE tagname=concat('sym-',$foundin)} { |
| 563 | @ if {$match} {set versionlink "timeline?t=$urlfoundin"} |
| 564 | @ } |
| 565 | @ } |
| 566 | @ set hashpattern {^[0-9a-f]+$} |
| 567 | @ if [regexp $hashpattern $foundin] { |
| 568 | @ set pattern $foundin* |
| 569 | @ query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} { |
| 570 | @ if {$match} {set versionlink "info/$urlfoundin"} |
| 571 | @ } |
| 572 | @ } |
| 573 | @ if {$versionlink eq ""} { |
| 574 | @ puts $foundin |
| 575 | @ } else { |
| 576 | @ html "<a href=\"" |
| 577 | @ puts $versionlink |
| 578 | @ html "\">" |
| 579 | @ puts $foundin |
| 580 | @ html "</a>" |
| 581 | @ } |
| 582 | @ </th1> |
| 583 | @ </td></tr> |
| 584 | @ </table> |
| 585 | @ |
| 586 | @ <th1> |
| 587 | @ wiki_assoc "ticket" $tkt_uuid |
| @@ -567,24 +611,25 @@ | |
| 611 | @ FROM ticketchng |
| 612 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 613 | @ if {$seenRow} { |
| 614 | @ html "<hr>\n" |
| 615 | @ } else { |
| 616 | @ html "<tr><td class='tktDspLabel' style='text-align:left'>\n" |
| 617 | @ html "User Comments:</td></tr>\n" |
| 618 | @ html "<tr><td colspan='5' class='tktDspValue'>\n" |
| 619 | @ set seenRow 1 |
| 620 | @ } |
| 621 | @ html "<span class='tktDspCommenter'>" |
| 622 | @ puts $xlogin |
| 623 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 624 | @ puts " (claiming to be $xusername)" |
| 625 | @ } |
| 626 | @ puts " added on $xdate:" |
| 627 | @ html "</span>\n" |
| 628 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 629 | @ set r [randhex] |
| 630 | @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} |
| 631 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 632 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 633 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 634 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 635 | @ html [lindex [markdown $xcomment] 1] |
| @@ -741,10 +786,52 @@ | |
| 786 | @ <input type="submit" name="cancel" value="Cancel"> |
| 787 | @ </td> |
| 788 | @ <td>Abandon this edit</td> |
| 789 | @ </tr> |
| 790 | @ |
| 791 | @ <th1> |
| 792 | @ set seenRow 0 |
| 793 | @ set alwaysPlaintext [info exists plaintext] |
| 794 | @ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin, |
| 795 | @ mimetype as xmimetype, icomment AS xcomment, |
| 796 | @ username AS xusername |
| 797 | @ FROM ticketchng |
| 798 | @ WHERE tkt_id=$tkt_id AND length(icomment)>0} { |
| 799 | @ if {$seenRow} { |
| 800 | @ html "<hr>\n" |
| 801 | @ } else { |
| 802 | @ html "<tr><td colspan='2'><hr></td></tr>\n" |
| 803 | @ html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n" |
| 804 | @ html "Previous User Comments:</td></tr>\n" |
| 805 | @ html "<tr><td colspan='2' class='tktDspValue'>\n" |
| 806 | @ set seenRow 1 |
| 807 | @ } |
| 808 | @ html "<span class='tktDspCommenter'>" |
| 809 | @ puts $xlogin |
| 810 | @ if {$xlogin ne $xusername && [string length $xusername]>0} { |
| 811 | @ puts " (claiming to be $xusername)" |
| 812 | @ } |
| 813 | @ puts " added on $xdate:" |
| 814 | @ html "</span>\n" |
| 815 | @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { |
| 816 | @ set r [randhex] |
| 817 | @ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} |
| 818 | @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" |
| 819 | @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { |
| 820 | @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" |
| 821 | @ } elseif {$xmimetype eq "text/x-markdown"} { |
| 822 | @ html [lindex [markdown $xcomment] 1] |
| 823 | @ } elseif {$xmimetype eq "text/html"} { |
| 824 | @ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n" |
| 825 | @ } else { |
| 826 | @ set r [randhex] |
| 827 | @ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n" |
| 828 | @ } |
| 829 | @ } |
| 830 | @ if {$seenRow} {html "</td></tr>\n"} |
| 831 | @ </th1> |
| 832 | @ |
| 833 | @ </table> |
| 834 | ; |
| 835 | |
| 836 | /* |
| 837 | ** Return the code used to generate the edit ticket page |
| @@ -839,11 +926,12 @@ | |
| 926 | @ WHEN status='Fixed' THEN '#cfe8bd' |
| 927 | @ WHEN status='Tested' THEN '#bde5d6' |
| 928 | @ WHEN status='Deferred' THEN '#cacae5' |
| 929 | @ ELSE '#c8c8c8' END AS 'bgcolor', |
| 930 | @ substr(tkt_uuid,1,10) AS '#', |
| 931 | @ datetime(tkt_ctime) AS 'created', |
| 932 | @ datetime(tkt_mtime) AS 'modified', |
| 933 | @ type, |
| 934 | @ status, |
| 935 | @ subsystem, |
| 936 | @ title, |
| 937 | @ comment AS '_comments' |
| @@ -973,8 +1061,9 @@ | |
| 1061 | @ <input type="submit" name="submit" value="Apply Changes"> |
| 1062 | @ <input type="submit" name="setup" value="Cancel"> |
| 1063 | @ </p> |
| 1064 | @ </div></form> |
| 1065 | db_end_transaction(0); |
| 1066 | style_submenu_element("Back", "%R/tktsetup"); |
| 1067 | style_finish_page(); |
| 1068 | |
| 1069 | } |
| 1070 |
+5
-3
| --- src/unversioned.c | ||
| +++ src/unversioned.c | ||
| @@ -246,10 +246,12 @@ | ||
| 246 | 246 | ** a single file at a time. |
| 247 | 247 | ** |
| 248 | 248 | ** cat FILE ... Concatenate the content of FILEs to stdout. |
| 249 | 249 | ** |
| 250 | 250 | ** edit FILE Bring up FILE in a text editor for modification. |
| 251 | +** Options: | |
| 252 | +** --editor NAME Name of the text editor to use | |
| 251 | 253 | ** |
| 252 | 254 | ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk |
| 253 | 255 | ** |
| 254 | 256 | ** list | ls Show all unversioned files held in the local |
| 255 | 257 | ** repository. |
| @@ -361,17 +363,17 @@ | ||
| 361 | 363 | const char *zTFile; /* Temporary file */ |
| 362 | 364 | const char *zUVFile; /* Name of the unversioned file */ |
| 363 | 365 | char *zCmd; /* Command to run the text editor */ |
| 364 | 366 | Blob content; /* Content of the unversioned file */ |
| 365 | 367 | |
| 366 | - verify_all_options(); | |
| 367 | - if( g.argc!=4) usage("edit UVFILE"); | |
| 368 | - zUVFile = g.argv[3]; | |
| 369 | 368 | zEditor = fossil_text_editor(); |
| 370 | 369 | if( zEditor==0 ){ |
| 371 | 370 | fossil_fatal("no text editor - set the VISUAL env variable"); |
| 372 | 371 | } |
| 372 | + verify_all_options(); | |
| 373 | + if( g.argc!=4) usage("edit UVFILE"); | |
| 374 | + zUVFile = g.argv[3]; | |
| 373 | 375 | zTFile = fossil_temp_filename(); |
| 374 | 376 | if( zTFile==0 ) fossil_fatal("cannot find a temporary filename"); |
| 375 | 377 | db_begin_transaction(); |
| 376 | 378 | content_rcvid_init("#!fossil unversioned edit"); |
| 377 | 379 | if( unversioned_content(zUVFile, &content)==0 ){ |
| 378 | 380 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -246,10 +246,12 @@ | |
| 246 | ** a single file at a time. |
| 247 | ** |
| 248 | ** cat FILE ... Concatenate the content of FILEs to stdout. |
| 249 | ** |
| 250 | ** edit FILE Bring up FILE in a text editor for modification. |
| 251 | ** |
| 252 | ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk |
| 253 | ** |
| 254 | ** list | ls Show all unversioned files held in the local |
| 255 | ** repository. |
| @@ -361,17 +363,17 @@ | |
| 361 | const char *zTFile; /* Temporary file */ |
| 362 | const char *zUVFile; /* Name of the unversioned file */ |
| 363 | char *zCmd; /* Command to run the text editor */ |
| 364 | Blob content; /* Content of the unversioned file */ |
| 365 | |
| 366 | verify_all_options(); |
| 367 | if( g.argc!=4) usage("edit UVFILE"); |
| 368 | zUVFile = g.argv[3]; |
| 369 | zEditor = fossil_text_editor(); |
| 370 | if( zEditor==0 ){ |
| 371 | fossil_fatal("no text editor - set the VISUAL env variable"); |
| 372 | } |
| 373 | zTFile = fossil_temp_filename(); |
| 374 | if( zTFile==0 ) fossil_fatal("cannot find a temporary filename"); |
| 375 | db_begin_transaction(); |
| 376 | content_rcvid_init("#!fossil unversioned edit"); |
| 377 | if( unversioned_content(zUVFile, &content)==0 ){ |
| 378 |
| --- src/unversioned.c | |
| +++ src/unversioned.c | |
| @@ -246,10 +246,12 @@ | |
| 246 | ** a single file at a time. |
| 247 | ** |
| 248 | ** cat FILE ... Concatenate the content of FILEs to stdout. |
| 249 | ** |
| 250 | ** edit FILE Bring up FILE in a text editor for modification. |
| 251 | ** Options: |
| 252 | ** --editor NAME Name of the text editor to use |
| 253 | ** |
| 254 | ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk |
| 255 | ** |
| 256 | ** list | ls Show all unversioned files held in the local |
| 257 | ** repository. |
| @@ -361,17 +363,17 @@ | |
| 363 | const char *zTFile; /* Temporary file */ |
| 364 | const char *zUVFile; /* Name of the unversioned file */ |
| 365 | char *zCmd; /* Command to run the text editor */ |
| 366 | Blob content; /* Content of the unversioned file */ |
| 367 | |
| 368 | zEditor = fossil_text_editor(); |
| 369 | if( zEditor==0 ){ |
| 370 | fossil_fatal("no text editor - set the VISUAL env variable"); |
| 371 | } |
| 372 | verify_all_options(); |
| 373 | if( g.argc!=4) usage("edit UVFILE"); |
| 374 | zUVFile = g.argv[3]; |
| 375 | zTFile = fossil_temp_filename(); |
| 376 | if( zTFile==0 ) fossil_fatal("cannot find a temporary filename"); |
| 377 | db_begin_transaction(); |
| 378 | content_rcvid_init("#!fossil unversioned edit"); |
| 379 | if( unversioned_content(zUVFile, &content)==0 ){ |
| 380 |
+79
-41
| --- src/user.c | ||
| +++ src/user.c | ||
| @@ -326,14 +326,21 @@ | ||
| 326 | 326 | ** |
| 327 | 327 | ** > fossil user contact USERNAME ?CONTACT-INFO? |
| 328 | 328 | ** |
| 329 | 329 | ** Query or set contact information for user USERNAME |
| 330 | 330 | ** |
| 331 | -** > fossil user default ?USERNAME? | |
| 331 | +** > fossil user default ?OPTIONS? ?USERNAME? | |
| 332 | 332 | ** |
| 333 | 333 | ** Query or set the default user. The default user is the |
| 334 | -** user for command-line interaction. | |
| 334 | +** user for command-line interaction. If USERNAME is an | |
| 335 | +** empty string, then the default user is unset from the | |
| 336 | +** repository and will subsequently be determined by the -U | |
| 337 | +** command-line option or by environment variables | |
| 338 | +** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order. | |
| 339 | +** OPTIONS: | |
| 340 | +** | |
| 341 | +** -v|--verbose Show how the default user is computed | |
| 335 | 342 | ** |
| 336 | 343 | ** > fossil user list | ls |
| 337 | 344 | ** |
| 338 | 345 | ** List all users known to the repository |
| 339 | 346 | ** |
| @@ -385,21 +392,50 @@ | ||
| 385 | 392 | &login, zPw, &caps, &contact |
| 386 | 393 | ); |
| 387 | 394 | db_protect_pop(); |
| 388 | 395 | free(zPw); |
| 389 | 396 | }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){ |
| 390 | - if( g.argc==3 ){ | |
| 391 | - user_select(); | |
| 392 | - fossil_print("%s\n", g.zLogin); | |
| 393 | - }else{ | |
| 394 | - if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){ | |
| 395 | - fossil_fatal("no such user: %s", g.argv[3]); | |
| 396 | - } | |
| 397 | - if( g.localOpen ){ | |
| 398 | - db_lset("default-user", g.argv[3]); | |
| 399 | - }else{ | |
| 400 | - db_set("default-user", g.argv[3], 0); | |
| 397 | + int eVerbose = find_option("verbose","v",0)!=0; | |
| 398 | + verify_all_options(); | |
| 399 | + if( g.argc>3 ){ | |
| 400 | + const char *zUser = g.argv[3]; | |
| 401 | + if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){ | |
| 402 | + db_begin_transaction(); | |
| 403 | + if( g.localOpen ){ | |
| 404 | + db_multi_exec("DELETE FROM vvar WHERE name='default-user'"); | |
| 405 | + } | |
| 406 | + db_unset("default-user",0); | |
| 407 | + db_commit_transaction(); | |
| 408 | + }else{ | |
| 409 | + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){ | |
| 410 | + fossil_fatal("no such user: %s", g.argv[3]); | |
| 411 | + } | |
| 412 | + if( g.localOpen ){ | |
| 413 | + db_lset("default-user", g.argv[3]); | |
| 414 | + }else{ | |
| 415 | + db_set("default-user", g.argv[3], 0); | |
| 416 | + } | |
| 417 | + } | |
| 418 | + } | |
| 419 | + if( g.argc==3 || eVerbose ){ | |
| 420 | + int eHow = user_select(); | |
| 421 | + const char *zHow = "???"; | |
| 422 | + switch( eHow ){ | |
| 423 | + case 1: zHow = "-U option"; break; | |
| 424 | + case 2: zHow = "previously set"; break; | |
| 425 | + case 3: zHow = "local check-out"; break; | |
| 426 | + case 4: zHow = "repository"; break; | |
| 427 | + case 5: zHow = "FOSSIL_USER"; break; | |
| 428 | + case 6: zHow = "USER"; break; | |
| 429 | + case 7: zHow = "LOGNAME"; break; | |
| 430 | + case 8: zHow = "USERNAME"; break; | |
| 431 | + case 9: zHow = "URL"; break; | |
| 432 | + } | |
| 433 | + if( eVerbose ){ | |
| 434 | + fossil_print("%s (determined by %s)\n", g.zLogin, zHow); | |
| 435 | + }else{ | |
| 436 | + fossil_print("%s\n", g.zLogin); | |
| 401 | 437 | } |
| 402 | 438 | } |
| 403 | 439 | }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) || |
| 404 | 440 | ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){ |
| 405 | 441 | Stmt q; |
| @@ -496,52 +532,54 @@ | ||
| 496 | 532 | /* |
| 497 | 533 | ** Figure out what user is at the controls. |
| 498 | 534 | ** |
| 499 | 535 | ** (1) Use the --user and -U command-line options. |
| 500 | 536 | ** |
| 501 | -** (2) If the local database is open, check in VVAR. | |
| 502 | -** | |
| 503 | -** (3) Check the default user in the repository | |
| 504 | -** | |
| 505 | -** (4) Try the FOSSIL_USER environment variable. | |
| 506 | -** | |
| 507 | -** (5) Try the USER environment variable. | |
| 508 | -** | |
| 509 | -** (6) Try the LOGNAME environment variable. | |
| 510 | -** | |
| 511 | -** (7) Try the USERNAME environment variable. | |
| 512 | -** | |
| 513 | -** (8) Check if the user can be extracted from the remote URL. | |
| 537 | +** (2) The name used for login (if there was a login). | |
| 538 | +** | |
| 539 | +** (3) If the local database is open, check in VVAR. | |
| 540 | +** | |
| 541 | +** (4) Check the default-user in the repository | |
| 542 | +** | |
| 543 | +** (5) Try the FOSSIL_USER environment variable. | |
| 544 | +** | |
| 545 | +** (6) Try the USER environment variable. | |
| 546 | +** | |
| 547 | +** (7) Try the LOGNAME environment variable. | |
| 548 | +** | |
| 549 | +** (8) Try the USERNAME environment variable. | |
| 550 | +** | |
| 551 | +** (9) Check if the user can be extracted from the remote URL. | |
| 514 | 552 | ** |
| 515 | 553 | ** The user name is stored in g.zLogin. The uid is in g.userUid. |
| 516 | 554 | */ |
| 517 | -void user_select(void){ | |
| 555 | +int user_select(void){ | |
| 518 | 556 | UrlData url; |
| 519 | - if( g.userUid ) return; | |
| 557 | + if( g.userUid ) return 1; | |
| 520 | 558 | if( g.zLogin ){ |
| 521 | 559 | if( attempt_user(g.zLogin)==0 ){ |
| 522 | 560 | fossil_fatal("no such user: %s", g.zLogin); |
| 523 | 561 | }else{ |
| 524 | - return; | |
| 562 | + return 2; | |
| 525 | 563 | } |
| 526 | 564 | } |
| 527 | 565 | |
| 528 | - if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return; | |
| 529 | - | |
| 530 | - if( attempt_user(db_get("default-user", 0)) ) return; | |
| 531 | - | |
| 532 | - if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return; | |
| 533 | - | |
| 534 | - if( attempt_user(fossil_getenv("USER")) ) return; | |
| 535 | - | |
| 536 | - if( attempt_user(fossil_getenv("LOGNAME")) ) return; | |
| 537 | - | |
| 538 | - if( attempt_user(fossil_getenv("USERNAME")) ) return; | |
| 566 | + if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3; | |
| 567 | + | |
| 568 | + if( attempt_user(db_get("default-user", 0)) ) return 4; | |
| 569 | + | |
| 570 | + if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5; | |
| 571 | + | |
| 572 | + if( attempt_user(fossil_getenv("USER")) ) return 6; | |
| 573 | + | |
| 574 | + if( attempt_user(fossil_getenv("LOGNAME")) ) return 7; | |
| 575 | + | |
| 576 | + if( attempt_user(fossil_getenv("USERNAME")) ) return 8; | |
| 539 | 577 | |
| 540 | 578 | memset(&url, 0, sizeof(url)); |
| 541 | 579 | url_parse_local(0, URL_USE_CONFIG, &url); |
| 542 | - if( url.user && attempt_user(url.user) ) return; | |
| 580 | + if( url.user && attempt_user(url.user) ) return 9; | |
| 543 | 581 | |
| 544 | 582 | fossil_print( |
| 545 | 583 | "Cannot figure out who you are! Consider using the --user\n" |
| 546 | 584 | "command line option, setting your USER environment variable,\n" |
| 547 | 585 | "or setting a default user with \"fossil user default USER\".\n" |
| 548 | 586 |
| --- src/user.c | |
| +++ src/user.c | |
| @@ -326,14 +326,21 @@ | |
| 326 | ** |
| 327 | ** > fossil user contact USERNAME ?CONTACT-INFO? |
| 328 | ** |
| 329 | ** Query or set contact information for user USERNAME |
| 330 | ** |
| 331 | ** > fossil user default ?USERNAME? |
| 332 | ** |
| 333 | ** Query or set the default user. The default user is the |
| 334 | ** user for command-line interaction. |
| 335 | ** |
| 336 | ** > fossil user list | ls |
| 337 | ** |
| 338 | ** List all users known to the repository |
| 339 | ** |
| @@ -385,21 +392,50 @@ | |
| 385 | &login, zPw, &caps, &contact |
| 386 | ); |
| 387 | db_protect_pop(); |
| 388 | free(zPw); |
| 389 | }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){ |
| 390 | if( g.argc==3 ){ |
| 391 | user_select(); |
| 392 | fossil_print("%s\n", g.zLogin); |
| 393 | }else{ |
| 394 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){ |
| 395 | fossil_fatal("no such user: %s", g.argv[3]); |
| 396 | } |
| 397 | if( g.localOpen ){ |
| 398 | db_lset("default-user", g.argv[3]); |
| 399 | }else{ |
| 400 | db_set("default-user", g.argv[3], 0); |
| 401 | } |
| 402 | } |
| 403 | }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) || |
| 404 | ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){ |
| 405 | Stmt q; |
| @@ -496,52 +532,54 @@ | |
| 496 | /* |
| 497 | ** Figure out what user is at the controls. |
| 498 | ** |
| 499 | ** (1) Use the --user and -U command-line options. |
| 500 | ** |
| 501 | ** (2) If the local database is open, check in VVAR. |
| 502 | ** |
| 503 | ** (3) Check the default user in the repository |
| 504 | ** |
| 505 | ** (4) Try the FOSSIL_USER environment variable. |
| 506 | ** |
| 507 | ** (5) Try the USER environment variable. |
| 508 | ** |
| 509 | ** (6) Try the LOGNAME environment variable. |
| 510 | ** |
| 511 | ** (7) Try the USERNAME environment variable. |
| 512 | ** |
| 513 | ** (8) Check if the user can be extracted from the remote URL. |
| 514 | ** |
| 515 | ** The user name is stored in g.zLogin. The uid is in g.userUid. |
| 516 | */ |
| 517 | void user_select(void){ |
| 518 | UrlData url; |
| 519 | if( g.userUid ) return; |
| 520 | if( g.zLogin ){ |
| 521 | if( attempt_user(g.zLogin)==0 ){ |
| 522 | fossil_fatal("no such user: %s", g.zLogin); |
| 523 | }else{ |
| 524 | return; |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return; |
| 529 | |
| 530 | if( attempt_user(db_get("default-user", 0)) ) return; |
| 531 | |
| 532 | if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return; |
| 533 | |
| 534 | if( attempt_user(fossil_getenv("USER")) ) return; |
| 535 | |
| 536 | if( attempt_user(fossil_getenv("LOGNAME")) ) return; |
| 537 | |
| 538 | if( attempt_user(fossil_getenv("USERNAME")) ) return; |
| 539 | |
| 540 | memset(&url, 0, sizeof(url)); |
| 541 | url_parse_local(0, URL_USE_CONFIG, &url); |
| 542 | if( url.user && attempt_user(url.user) ) return; |
| 543 | |
| 544 | fossil_print( |
| 545 | "Cannot figure out who you are! Consider using the --user\n" |
| 546 | "command line option, setting your USER environment variable,\n" |
| 547 | "or setting a default user with \"fossil user default USER\".\n" |
| 548 |
| --- src/user.c | |
| +++ src/user.c | |
| @@ -326,14 +326,21 @@ | |
| 326 | ** |
| 327 | ** > fossil user contact USERNAME ?CONTACT-INFO? |
| 328 | ** |
| 329 | ** Query or set contact information for user USERNAME |
| 330 | ** |
| 331 | ** > fossil user default ?OPTIONS? ?USERNAME? |
| 332 | ** |
| 333 | ** Query or set the default user. The default user is the |
| 334 | ** user for command-line interaction. If USERNAME is an |
| 335 | ** empty string, then the default user is unset from the |
| 336 | ** repository and will subsequently be determined by the -U |
| 337 | ** command-line option or by environment variables |
| 338 | ** FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order. |
| 339 | ** OPTIONS: |
| 340 | ** |
| 341 | ** -v|--verbose Show how the default user is computed |
| 342 | ** |
| 343 | ** > fossil user list | ls |
| 344 | ** |
| 345 | ** List all users known to the repository |
| 346 | ** |
| @@ -385,21 +392,50 @@ | |
| 392 | &login, zPw, &caps, &contact |
| 393 | ); |
| 394 | db_protect_pop(); |
| 395 | free(zPw); |
| 396 | }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){ |
| 397 | int eVerbose = find_option("verbose","v",0)!=0; |
| 398 | verify_all_options(); |
| 399 | if( g.argc>3 ){ |
| 400 | const char *zUser = g.argv[3]; |
| 401 | if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){ |
| 402 | db_begin_transaction(); |
| 403 | if( g.localOpen ){ |
| 404 | db_multi_exec("DELETE FROM vvar WHERE name='default-user'"); |
| 405 | } |
| 406 | db_unset("default-user",0); |
| 407 | db_commit_transaction(); |
| 408 | }else{ |
| 409 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){ |
| 410 | fossil_fatal("no such user: %s", g.argv[3]); |
| 411 | } |
| 412 | if( g.localOpen ){ |
| 413 | db_lset("default-user", g.argv[3]); |
| 414 | }else{ |
| 415 | db_set("default-user", g.argv[3], 0); |
| 416 | } |
| 417 | } |
| 418 | } |
| 419 | if( g.argc==3 || eVerbose ){ |
| 420 | int eHow = user_select(); |
| 421 | const char *zHow = "???"; |
| 422 | switch( eHow ){ |
| 423 | case 1: zHow = "-U option"; break; |
| 424 | case 2: zHow = "previously set"; break; |
| 425 | case 3: zHow = "local check-out"; break; |
| 426 | case 4: zHow = "repository"; break; |
| 427 | case 5: zHow = "FOSSIL_USER"; break; |
| 428 | case 6: zHow = "USER"; break; |
| 429 | case 7: zHow = "LOGNAME"; break; |
| 430 | case 8: zHow = "USERNAME"; break; |
| 431 | case 9: zHow = "URL"; break; |
| 432 | } |
| 433 | if( eVerbose ){ |
| 434 | fossil_print("%s (determined by %s)\n", g.zLogin, zHow); |
| 435 | }else{ |
| 436 | fossil_print("%s\n", g.zLogin); |
| 437 | } |
| 438 | } |
| 439 | }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) || |
| 440 | ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){ |
| 441 | Stmt q; |
| @@ -496,52 +532,54 @@ | |
| 532 | /* |
| 533 | ** Figure out what user is at the controls. |
| 534 | ** |
| 535 | ** (1) Use the --user and -U command-line options. |
| 536 | ** |
| 537 | ** (2) The name used for login (if there was a login). |
| 538 | ** |
| 539 | ** (3) If the local database is open, check in VVAR. |
| 540 | ** |
| 541 | ** (4) Check the default-user in the repository |
| 542 | ** |
| 543 | ** (5) Try the FOSSIL_USER environment variable. |
| 544 | ** |
| 545 | ** (6) Try the USER environment variable. |
| 546 | ** |
| 547 | ** (7) Try the LOGNAME environment variable. |
| 548 | ** |
| 549 | ** (8) Try the USERNAME environment variable. |
| 550 | ** |
| 551 | ** (9) Check if the user can be extracted from the remote URL. |
| 552 | ** |
| 553 | ** The user name is stored in g.zLogin. The uid is in g.userUid. |
| 554 | */ |
| 555 | int user_select(void){ |
| 556 | UrlData url; |
| 557 | if( g.userUid ) return 1; |
| 558 | if( g.zLogin ){ |
| 559 | if( attempt_user(g.zLogin)==0 ){ |
| 560 | fossil_fatal("no such user: %s", g.zLogin); |
| 561 | }else{ |
| 562 | return 2; |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3; |
| 567 | |
| 568 | if( attempt_user(db_get("default-user", 0)) ) return 4; |
| 569 | |
| 570 | if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5; |
| 571 | |
| 572 | if( attempt_user(fossil_getenv("USER")) ) return 6; |
| 573 | |
| 574 | if( attempt_user(fossil_getenv("LOGNAME")) ) return 7; |
| 575 | |
| 576 | if( attempt_user(fossil_getenv("USERNAME")) ) return 8; |
| 577 | |
| 578 | memset(&url, 0, sizeof(url)); |
| 579 | url_parse_local(0, URL_USE_CONFIG, &url); |
| 580 | if( url.user && attempt_user(url.user) ) return 9; |
| 581 | |
| 582 | fossil_print( |
| 583 | "Cannot figure out who you are! Consider using the --user\n" |
| 584 | "command line option, setting your USER environment variable,\n" |
| 585 | "or setting a default user with \"fossil user default USER\".\n" |
| 586 |
+16
-6
| --- src/util.c | ||
| +++ src/util.c | ||
| @@ -666,23 +666,33 @@ | ||
| 666 | 666 | /* |
| 667 | 667 | ** Return the name of the users preferred text editor. Return NULL if |
| 668 | 668 | ** not found. |
| 669 | 669 | ** |
| 670 | 670 | ** Search algorithm: |
| 671 | -** (1) The local "editor" setting | |
| 672 | -** (2) The global "editor" setting | |
| 673 | -** (3) The VISUAL environment variable | |
| 674 | -** (4) The EDITOR environment variable | |
| 675 | -** (5) Any of the following programs that are available: | |
| 671 | +** (1) The value of the --editor command-line argument | |
| 672 | +** (2) The local "editor" setting | |
| 673 | +** (3) The global "editor" setting | |
| 674 | +** (4) The VISUAL environment variable | |
| 675 | +** (5) The EDITOR environment variable | |
| 676 | +** (6) Any of the following programs that are available: | |
| 676 | 677 | ** notepad, nano, pico, jove, edit, vi, vim, ed, |
| 678 | +** | |
| 679 | +** The search only occurs once, the first time this routine is called. | |
| 680 | +** Second and subsequent invocations always return the same value. | |
| 677 | 681 | */ |
| 678 | 682 | const char *fossil_text_editor(void){ |
| 679 | - const char *zEditor = db_get("editor", 0); | |
| 683 | + static const char *zEditor = 0; | |
| 680 | 684 | const char *azStdEd[] = { |
| 681 | 685 | "notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed" |
| 682 | 686 | }; |
| 683 | 687 | int i = 0; |
| 688 | + if( zEditor==0 ){ | |
| 689 | + zEditor = find_option("editor",0,1); | |
| 690 | + } | |
| 691 | + if( zEditor==0 ){ | |
| 692 | + zEditor = db_get("editor", 0); | |
| 693 | + } | |
| 684 | 694 | if( zEditor==0 ){ |
| 685 | 695 | zEditor = fossil_getenv("VISUAL"); |
| 686 | 696 | } |
| 687 | 697 | if( zEditor==0 ){ |
| 688 | 698 | zEditor = fossil_getenv("EDITOR"); |
| 689 | 699 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -666,23 +666,33 @@ | |
| 666 | /* |
| 667 | ** Return the name of the users preferred text editor. Return NULL if |
| 668 | ** not found. |
| 669 | ** |
| 670 | ** Search algorithm: |
| 671 | ** (1) The local "editor" setting |
| 672 | ** (2) The global "editor" setting |
| 673 | ** (3) The VISUAL environment variable |
| 674 | ** (4) The EDITOR environment variable |
| 675 | ** (5) Any of the following programs that are available: |
| 676 | ** notepad, nano, pico, jove, edit, vi, vim, ed, |
| 677 | */ |
| 678 | const char *fossil_text_editor(void){ |
| 679 | const char *zEditor = db_get("editor", 0); |
| 680 | const char *azStdEd[] = { |
| 681 | "notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed" |
| 682 | }; |
| 683 | int i = 0; |
| 684 | if( zEditor==0 ){ |
| 685 | zEditor = fossil_getenv("VISUAL"); |
| 686 | } |
| 687 | if( zEditor==0 ){ |
| 688 | zEditor = fossil_getenv("EDITOR"); |
| 689 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -666,23 +666,33 @@ | |
| 666 | /* |
| 667 | ** Return the name of the users preferred text editor. Return NULL if |
| 668 | ** not found. |
| 669 | ** |
| 670 | ** Search algorithm: |
| 671 | ** (1) The value of the --editor command-line argument |
| 672 | ** (2) The local "editor" setting |
| 673 | ** (3) The global "editor" setting |
| 674 | ** (4) The VISUAL environment variable |
| 675 | ** (5) The EDITOR environment variable |
| 676 | ** (6) Any of the following programs that are available: |
| 677 | ** notepad, nano, pico, jove, edit, vi, vim, ed, |
| 678 | ** |
| 679 | ** The search only occurs once, the first time this routine is called. |
| 680 | ** Second and subsequent invocations always return the same value. |
| 681 | */ |
| 682 | const char *fossil_text_editor(void){ |
| 683 | static const char *zEditor = 0; |
| 684 | const char *azStdEd[] = { |
| 685 | "notepad", "nano", "pico", "jove", "edit", "vi", "vim", "ed" |
| 686 | }; |
| 687 | int i = 0; |
| 688 | if( zEditor==0 ){ |
| 689 | zEditor = find_option("editor",0,1); |
| 690 | } |
| 691 | if( zEditor==0 ){ |
| 692 | zEditor = db_get("editor", 0); |
| 693 | } |
| 694 | if( zEditor==0 ){ |
| 695 | zEditor = fossil_getenv("VISUAL"); |
| 696 | } |
| 697 | if( zEditor==0 ){ |
| 698 | zEditor = fossil_getenv("EDITOR"); |
| 699 |
+34
-19
| --- src/winfile.c | ||
| +++ src/winfile.c | ||
| @@ -304,39 +304,47 @@ | ||
| 304 | 304 | */ |
| 305 | 305 | int win32_filenames_equal_nocase( |
| 306 | 306 | const wchar_t *fn1, |
| 307 | 307 | const wchar_t *fn2 |
| 308 | 308 | ){ |
| 309 | - static FARPROC fnCompareStringOrdinal; | |
| 310 | - static FARPROC fnRtlInitUnicodeString; | |
| 311 | - static FARPROC fnRtlEqualUnicodeString; | |
| 309 | + /* ---- Data types used by dynamically loaded API functions. -------------- */ | |
| 310 | + typedef struct { /* UNICODE_STRING from <ntdef.h> */ | |
| 311 | + USHORT Length; | |
| 312 | + USHORT MaximumLength; | |
| 313 | + PWSTR Buffer; | |
| 314 | + } MY_UNICODE_STRING; | |
| 315 | + /* ---- Prototypes for dynamically loaded API functions. ------------------ */ | |
| 316 | + typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL); | |
| 317 | + typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR); | |
| 318 | + typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING) | |
| 319 | + (MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN); | |
| 320 | + /* ------------------------------------------------------------------------ */ | |
| 321 | + static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal; | |
| 322 | + static FNRTLINITUNICODESTRING fnRtlInitUnicodeString; | |
| 323 | + static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString; | |
| 312 | 324 | static int loaded_CompareStringOrdinal; |
| 313 | 325 | static int loaded_RtlUnicodeStringAPIs; |
| 314 | 326 | if( !loaded_CompareStringOrdinal ){ |
| 315 | - fnCompareStringOrdinal = | |
| 327 | + fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL) | |
| 316 | 328 | GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal"); |
| 317 | 329 | loaded_CompareStringOrdinal = 1; |
| 318 | 330 | } |
| 319 | 331 | if( fnCompareStringOrdinal ){ |
| 320 | 332 | return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0; |
| 321 | 333 | } |
| 322 | 334 | if( !loaded_RtlUnicodeStringAPIs ){ |
| 323 | - fnRtlInitUnicodeString = | |
| 335 | + fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING) | |
| 324 | 336 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString"); |
| 325 | - fnRtlEqualUnicodeString = | |
| 337 | + fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING) | |
| 326 | 338 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString"); |
| 327 | 339 | loaded_RtlUnicodeStringAPIs = 1; |
| 328 | 340 | } |
| 329 | 341 | if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){ |
| 330 | - struct { /* UNICODE_STRING from <ntdef.h> */ | |
| 331 | - unsigned short Length; | |
| 332 | - unsigned short MaximumLength; | |
| 333 | - wchar_t *Buffer; | |
| 334 | - } u1, u2; | |
| 342 | + MY_UNICODE_STRING u1, u2; | |
| 335 | 343 | fnRtlInitUnicodeString(&u1,fn1); |
| 336 | 344 | fnRtlInitUnicodeString(&u2,fn2); |
| 337 | - return (unsigned char)fnRtlEqualUnicodeString(&u1,&u2,1); | |
| 345 | + return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1); | |
| 338 | 346 | } |
| 339 | 347 | /* In what kind of strange parallel universe are we? */ |
| 340 | 348 | return lstrcmpiW(fn1,fn2)==0; |
| 341 | 349 | } |
| 342 | 350 | |
| @@ -461,11 +469,20 @@ | ||
| 461 | 469 | ** is allocated by mprintf(), or NULL on failure. |
| 462 | 470 | */ |
| 463 | 471 | char *win32_file_id( |
| 464 | 472 | const char *zFileName |
| 465 | 473 | ){ |
| 466 | - static FARPROC fnGetFileInformationByHandleEx; | |
| 474 | + /* ---- Data types used by dynamically loaded API functions. -------------- */ | |
| 475 | + typedef struct { /* FILE_ID_INFO from <winbase.h> */ | |
| 476 | + ULONGLONG VolumeSerialNumber; | |
| 477 | + BYTE FileId[16]; | |
| 478 | + } MY_FILE_ID_INFO; | |
| 479 | + /* ---- Prototypes for dynamically loaded API functions. ------------------ */ | |
| 480 | + typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX) | |
| 481 | + (HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD); | |
| 482 | + /* ------------------------------------------------------------------------ */ | |
| 483 | + static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx; | |
| 467 | 484 | static int loaded_fnGetFileInformationByHandleEx; |
| 468 | 485 | wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0); |
| 469 | 486 | HANDLE hFile; |
| 470 | 487 | char *zFileId = 0; |
| 471 | 488 | hFile = CreateFileW( |
| @@ -476,17 +493,15 @@ | ||
| 476 | 493 | OPEN_EXISTING, |
| 477 | 494 | FILE_FLAG_BACKUP_SEMANTICS, |
| 478 | 495 | NULL); |
| 479 | 496 | if( hFile!=INVALID_HANDLE_VALUE ){ |
| 480 | 497 | BY_HANDLE_FILE_INFORMATION fi; |
| 481 | - struct { /* FILE_ID_INFO from <winbase.h> */ | |
| 482 | - u64 VolumeSerialNumber; | |
| 483 | - unsigned char FileId[16]; | |
| 484 | - } fi2; | |
| 498 | + MY_FILE_ID_INFO fi2; | |
| 485 | 499 | if( !loaded_fnGetFileInformationByHandleEx ){ |
| 486 | - fnGetFileInformationByHandleEx = GetProcAddress( | |
| 487 | - GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); | |
| 500 | + fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX) | |
| 501 | + GetProcAddress( | |
| 502 | + GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); | |
| 488 | 503 | loaded_fnGetFileInformationByHandleEx = 1; |
| 489 | 504 | } |
| 490 | 505 | if( fnGetFileInformationByHandleEx ){ |
| 491 | 506 | if( fnGetFileInformationByHandleEx( |
| 492 | 507 | hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){ |
| 493 | 508 |
| --- src/winfile.c | |
| +++ src/winfile.c | |
| @@ -304,39 +304,47 @@ | |
| 304 | */ |
| 305 | int win32_filenames_equal_nocase( |
| 306 | const wchar_t *fn1, |
| 307 | const wchar_t *fn2 |
| 308 | ){ |
| 309 | static FARPROC fnCompareStringOrdinal; |
| 310 | static FARPROC fnRtlInitUnicodeString; |
| 311 | static FARPROC fnRtlEqualUnicodeString; |
| 312 | static int loaded_CompareStringOrdinal; |
| 313 | static int loaded_RtlUnicodeStringAPIs; |
| 314 | if( !loaded_CompareStringOrdinal ){ |
| 315 | fnCompareStringOrdinal = |
| 316 | GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal"); |
| 317 | loaded_CompareStringOrdinal = 1; |
| 318 | } |
| 319 | if( fnCompareStringOrdinal ){ |
| 320 | return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0; |
| 321 | } |
| 322 | if( !loaded_RtlUnicodeStringAPIs ){ |
| 323 | fnRtlInitUnicodeString = |
| 324 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString"); |
| 325 | fnRtlEqualUnicodeString = |
| 326 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString"); |
| 327 | loaded_RtlUnicodeStringAPIs = 1; |
| 328 | } |
| 329 | if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){ |
| 330 | struct { /* UNICODE_STRING from <ntdef.h> */ |
| 331 | unsigned short Length; |
| 332 | unsigned short MaximumLength; |
| 333 | wchar_t *Buffer; |
| 334 | } u1, u2; |
| 335 | fnRtlInitUnicodeString(&u1,fn1); |
| 336 | fnRtlInitUnicodeString(&u2,fn2); |
| 337 | return (unsigned char)fnRtlEqualUnicodeString(&u1,&u2,1); |
| 338 | } |
| 339 | /* In what kind of strange parallel universe are we? */ |
| 340 | return lstrcmpiW(fn1,fn2)==0; |
| 341 | } |
| 342 | |
| @@ -461,11 +469,20 @@ | |
| 461 | ** is allocated by mprintf(), or NULL on failure. |
| 462 | */ |
| 463 | char *win32_file_id( |
| 464 | const char *zFileName |
| 465 | ){ |
| 466 | static FARPROC fnGetFileInformationByHandleEx; |
| 467 | static int loaded_fnGetFileInformationByHandleEx; |
| 468 | wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0); |
| 469 | HANDLE hFile; |
| 470 | char *zFileId = 0; |
| 471 | hFile = CreateFileW( |
| @@ -476,17 +493,15 @@ | |
| 476 | OPEN_EXISTING, |
| 477 | FILE_FLAG_BACKUP_SEMANTICS, |
| 478 | NULL); |
| 479 | if( hFile!=INVALID_HANDLE_VALUE ){ |
| 480 | BY_HANDLE_FILE_INFORMATION fi; |
| 481 | struct { /* FILE_ID_INFO from <winbase.h> */ |
| 482 | u64 VolumeSerialNumber; |
| 483 | unsigned char FileId[16]; |
| 484 | } fi2; |
| 485 | if( !loaded_fnGetFileInformationByHandleEx ){ |
| 486 | fnGetFileInformationByHandleEx = GetProcAddress( |
| 487 | GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); |
| 488 | loaded_fnGetFileInformationByHandleEx = 1; |
| 489 | } |
| 490 | if( fnGetFileInformationByHandleEx ){ |
| 491 | if( fnGetFileInformationByHandleEx( |
| 492 | hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){ |
| 493 |
| --- src/winfile.c | |
| +++ src/winfile.c | |
| @@ -304,39 +304,47 @@ | |
| 304 | */ |
| 305 | int win32_filenames_equal_nocase( |
| 306 | const wchar_t *fn1, |
| 307 | const wchar_t *fn2 |
| 308 | ){ |
| 309 | /* ---- Data types used by dynamically loaded API functions. -------------- */ |
| 310 | typedef struct { /* UNICODE_STRING from <ntdef.h> */ |
| 311 | USHORT Length; |
| 312 | USHORT MaximumLength; |
| 313 | PWSTR Buffer; |
| 314 | } MY_UNICODE_STRING; |
| 315 | /* ---- Prototypes for dynamically loaded API functions. ------------------ */ |
| 316 | typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL); |
| 317 | typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR); |
| 318 | typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING) |
| 319 | (MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN); |
| 320 | /* ------------------------------------------------------------------------ */ |
| 321 | static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal; |
| 322 | static FNRTLINITUNICODESTRING fnRtlInitUnicodeString; |
| 323 | static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString; |
| 324 | static int loaded_CompareStringOrdinal; |
| 325 | static int loaded_RtlUnicodeStringAPIs; |
| 326 | if( !loaded_CompareStringOrdinal ){ |
| 327 | fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL) |
| 328 | GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal"); |
| 329 | loaded_CompareStringOrdinal = 1; |
| 330 | } |
| 331 | if( fnCompareStringOrdinal ){ |
| 332 | return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0; |
| 333 | } |
| 334 | if( !loaded_RtlUnicodeStringAPIs ){ |
| 335 | fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING) |
| 336 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString"); |
| 337 | fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING) |
| 338 | GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString"); |
| 339 | loaded_RtlUnicodeStringAPIs = 1; |
| 340 | } |
| 341 | if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){ |
| 342 | MY_UNICODE_STRING u1, u2; |
| 343 | fnRtlInitUnicodeString(&u1,fn1); |
| 344 | fnRtlInitUnicodeString(&u2,fn2); |
| 345 | return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1); |
| 346 | } |
| 347 | /* In what kind of strange parallel universe are we? */ |
| 348 | return lstrcmpiW(fn1,fn2)==0; |
| 349 | } |
| 350 | |
| @@ -461,11 +469,20 @@ | |
| 469 | ** is allocated by mprintf(), or NULL on failure. |
| 470 | */ |
| 471 | char *win32_file_id( |
| 472 | const char *zFileName |
| 473 | ){ |
| 474 | /* ---- Data types used by dynamically loaded API functions. -------------- */ |
| 475 | typedef struct { /* FILE_ID_INFO from <winbase.h> */ |
| 476 | ULONGLONG VolumeSerialNumber; |
| 477 | BYTE FileId[16]; |
| 478 | } MY_FILE_ID_INFO; |
| 479 | /* ---- Prototypes for dynamically loaded API functions. ------------------ */ |
| 480 | typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX) |
| 481 | (HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD); |
| 482 | /* ------------------------------------------------------------------------ */ |
| 483 | static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx; |
| 484 | static int loaded_fnGetFileInformationByHandleEx; |
| 485 | wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0); |
| 486 | HANDLE hFile; |
| 487 | char *zFileId = 0; |
| 488 | hFile = CreateFileW( |
| @@ -476,17 +493,15 @@ | |
| 493 | OPEN_EXISTING, |
| 494 | FILE_FLAG_BACKUP_SEMANTICS, |
| 495 | NULL); |
| 496 | if( hFile!=INVALID_HANDLE_VALUE ){ |
| 497 | BY_HANDLE_FILE_INFORMATION fi; |
| 498 | MY_FILE_ID_INFO fi2; |
| 499 | if( !loaded_fnGetFileInformationByHandleEx ){ |
| 500 | fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX) |
| 501 | GetProcAddress( |
| 502 | GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx"); |
| 503 | loaded_fnGetFileInformationByHandleEx = 1; |
| 504 | } |
| 505 | if( fnGetFileInformationByHandleEx ){ |
| 506 | if( fnGetFileInformationByHandleEx( |
| 507 | hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){ |
| 508 |
+20
-8
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -1116,17 +1116,29 @@ | ||
| 1116 | 1116 | blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1117 | 1117 | zName, mtime, zHash, sz); |
| 1118 | 1118 | } |
| 1119 | 1119 | db_finalize(&uvq); |
| 1120 | 1120 | } |
| 1121 | + | |
| 1122 | +/* | |
| 1123 | +** Return a string that contains supplemental information about a | |
| 1124 | +** "not authorized" error. The string might be empty if no additional | |
| 1125 | +** information is available. | |
| 1126 | +*/ | |
| 1127 | +static char *whyNotAuth(void){ | |
| 1128 | + if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){ | |
| 1129 | + return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled"; | |
| 1130 | + } | |
| 1131 | + return ""; | |
| 1132 | +} | |
| 1121 | 1133 | |
| 1122 | 1134 | /* |
| 1123 | 1135 | ** Called when there is an attempt to transfer private content to and |
| 1124 | 1136 | ** from a server without authorization. |
| 1125 | 1137 | */ |
| 1126 | 1138 | static void server_private_xfer_not_authorized(void){ |
| 1127 | - @ error not\sauthorized\sto\ssync\sprivate\scontent | |
| 1139 | + @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth()) | |
| 1128 | 1140 | } |
| 1129 | 1141 | |
| 1130 | 1142 | /* |
| 1131 | 1143 | ** Return the common TH1 code to evaluate prior to evaluating any other |
| 1132 | 1144 | ** TH1 transfer notification scripts. |
| @@ -1316,11 +1328,11 @@ | ||
| 1316 | 1328 | ** Server accepts a file from the client. |
| 1317 | 1329 | */ |
| 1318 | 1330 | if( blob_eq(&xfer.aToken[0], "file") ){ |
| 1319 | 1331 | if( !isPush ){ |
| 1320 | 1332 | cgi_reset_content(); |
| 1321 | - @ error not\sauthorized\sto\swrite | |
| 1333 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1322 | 1334 | nErr++; |
| 1323 | 1335 | break; |
| 1324 | 1336 | } |
| 1325 | 1337 | xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList); |
| 1326 | 1338 | if( blob_size(&xfer.err) ){ |
| @@ -1337,11 +1349,11 @@ | ||
| 1337 | 1349 | ** Server accepts a compressed file from the client. |
| 1338 | 1350 | */ |
| 1339 | 1351 | if( blob_eq(&xfer.aToken[0], "cfile") ){ |
| 1340 | 1352 | if( !isPush ){ |
| 1341 | 1353 | cgi_reset_content(); |
| 1342 | - @ error not\sauthorized\sto\swrite | |
| 1354 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1343 | 1355 | nErr++; |
| 1344 | 1356 | break; |
| 1345 | 1357 | } |
| 1346 | 1358 | xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList); |
| 1347 | 1359 | if( blob_size(&xfer.err) ){ |
| @@ -1461,23 +1473,23 @@ | ||
| 1461 | 1473 | } |
| 1462 | 1474 | login_check_credentials(); |
| 1463 | 1475 | if( blob_eq(&xfer.aToken[0], "pull") ){ |
| 1464 | 1476 | if( !g.perm.Read ){ |
| 1465 | 1477 | cgi_reset_content(); |
| 1466 | - @ error not\sauthorized\sto\sread | |
| 1478 | + @ error not\sauthorized\sto\sread%s(whyNotAuth()) | |
| 1467 | 1479 | nErr++; |
| 1468 | 1480 | break; |
| 1469 | 1481 | } |
| 1470 | 1482 | isPull = 1; |
| 1471 | 1483 | }else{ |
| 1472 | 1484 | if( !g.perm.Write ){ |
| 1473 | 1485 | if( !isPull ){ |
| 1474 | 1486 | cgi_reset_content(); |
| 1475 | - @ error not\sauthorized\sto\swrite | |
| 1487 | + @ error not\sauthorized\sto\swrite%s(whyNotAuth()) | |
| 1476 | 1488 | nErr++; |
| 1477 | 1489 | }else{ |
| 1478 | - @ message pull\sonly\s-\snot\sauthorized\sto\spush | |
| 1490 | + @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth()) | |
| 1479 | 1491 | } |
| 1480 | 1492 | }else{ |
| 1481 | 1493 | isPush = 1; |
| 1482 | 1494 | } |
| 1483 | 1495 | } |
| @@ -1491,11 +1503,11 @@ | ||
| 1491 | 1503 | int iVers; |
| 1492 | 1504 | login_check_credentials(); |
| 1493 | 1505 | if( !g.perm.Clone ){ |
| 1494 | 1506 | cgi_reset_content(); |
| 1495 | 1507 | @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) |
| 1496 | - @ error not\sauthorized\sto\sclone | |
| 1508 | + @ error not\sauthorized\sto\sclone%s(whyNotAuth()) | |
| 1497 | 1509 | nErr++; |
| 1498 | 1510 | break; |
| 1499 | 1511 | } |
| 1500 | 1512 | if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){ |
| 1501 | 1513 | @ pragma uv-pull-only |
| @@ -1592,11 +1604,11 @@ | ||
| 1592 | 1604 | } |
| 1593 | 1605 | blob_zero(&content); |
| 1594 | 1606 | blob_extract(xfer.pIn, size, &content); |
| 1595 | 1607 | if( !g.perm.Admin ){ |
| 1596 | 1608 | cgi_reset_content(); |
| 1597 | - @ error not\sauthorized\sto\spush\sconfiguration | |
| 1609 | + @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth()) | |
| 1598 | 1610 | nErr++; |
| 1599 | 1611 | break; |
| 1600 | 1612 | } |
| 1601 | 1613 | configure_receive(zName, &content, CONFIGSET_ALL); |
| 1602 | 1614 | blob_reset(&content); |
| 1603 | 1615 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1116,17 +1116,29 @@ | |
| 1116 | blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1117 | zName, mtime, zHash, sz); |
| 1118 | } |
| 1119 | db_finalize(&uvq); |
| 1120 | } |
| 1121 | |
| 1122 | /* |
| 1123 | ** Called when there is an attempt to transfer private content to and |
| 1124 | ** from a server without authorization. |
| 1125 | */ |
| 1126 | static void server_private_xfer_not_authorized(void){ |
| 1127 | @ error not\sauthorized\sto\ssync\sprivate\scontent |
| 1128 | } |
| 1129 | |
| 1130 | /* |
| 1131 | ** Return the common TH1 code to evaluate prior to evaluating any other |
| 1132 | ** TH1 transfer notification scripts. |
| @@ -1316,11 +1328,11 @@ | |
| 1316 | ** Server accepts a file from the client. |
| 1317 | */ |
| 1318 | if( blob_eq(&xfer.aToken[0], "file") ){ |
| 1319 | if( !isPush ){ |
| 1320 | cgi_reset_content(); |
| 1321 | @ error not\sauthorized\sto\swrite |
| 1322 | nErr++; |
| 1323 | break; |
| 1324 | } |
| 1325 | xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList); |
| 1326 | if( blob_size(&xfer.err) ){ |
| @@ -1337,11 +1349,11 @@ | |
| 1337 | ** Server accepts a compressed file from the client. |
| 1338 | */ |
| 1339 | if( blob_eq(&xfer.aToken[0], "cfile") ){ |
| 1340 | if( !isPush ){ |
| 1341 | cgi_reset_content(); |
| 1342 | @ error not\sauthorized\sto\swrite |
| 1343 | nErr++; |
| 1344 | break; |
| 1345 | } |
| 1346 | xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList); |
| 1347 | if( blob_size(&xfer.err) ){ |
| @@ -1461,23 +1473,23 @@ | |
| 1461 | } |
| 1462 | login_check_credentials(); |
| 1463 | if( blob_eq(&xfer.aToken[0], "pull") ){ |
| 1464 | if( !g.perm.Read ){ |
| 1465 | cgi_reset_content(); |
| 1466 | @ error not\sauthorized\sto\sread |
| 1467 | nErr++; |
| 1468 | break; |
| 1469 | } |
| 1470 | isPull = 1; |
| 1471 | }else{ |
| 1472 | if( !g.perm.Write ){ |
| 1473 | if( !isPull ){ |
| 1474 | cgi_reset_content(); |
| 1475 | @ error not\sauthorized\sto\swrite |
| 1476 | nErr++; |
| 1477 | }else{ |
| 1478 | @ message pull\sonly\s-\snot\sauthorized\sto\spush |
| 1479 | } |
| 1480 | }else{ |
| 1481 | isPush = 1; |
| 1482 | } |
| 1483 | } |
| @@ -1491,11 +1503,11 @@ | |
| 1491 | int iVers; |
| 1492 | login_check_credentials(); |
| 1493 | if( !g.perm.Clone ){ |
| 1494 | cgi_reset_content(); |
| 1495 | @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) |
| 1496 | @ error not\sauthorized\sto\sclone |
| 1497 | nErr++; |
| 1498 | break; |
| 1499 | } |
| 1500 | if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){ |
| 1501 | @ pragma uv-pull-only |
| @@ -1592,11 +1604,11 @@ | |
| 1592 | } |
| 1593 | blob_zero(&content); |
| 1594 | blob_extract(xfer.pIn, size, &content); |
| 1595 | if( !g.perm.Admin ){ |
| 1596 | cgi_reset_content(); |
| 1597 | @ error not\sauthorized\sto\spush\sconfiguration |
| 1598 | nErr++; |
| 1599 | break; |
| 1600 | } |
| 1601 | configure_receive(zName, &content, CONFIGSET_ALL); |
| 1602 | blob_reset(&content); |
| 1603 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1116,17 +1116,29 @@ | |
| 1116 | blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", |
| 1117 | zName, mtime, zHash, sz); |
| 1118 | } |
| 1119 | db_finalize(&uvq); |
| 1120 | } |
| 1121 | |
| 1122 | /* |
| 1123 | ** Return a string that contains supplemental information about a |
| 1124 | ** "not authorized" error. The string might be empty if no additional |
| 1125 | ** information is available. |
| 1126 | */ |
| 1127 | static char *whyNotAuth(void){ |
| 1128 | if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){ |
| 1129 | return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled"; |
| 1130 | } |
| 1131 | return ""; |
| 1132 | } |
| 1133 | |
| 1134 | /* |
| 1135 | ** Called when there is an attempt to transfer private content to and |
| 1136 | ** from a server without authorization. |
| 1137 | */ |
| 1138 | static void server_private_xfer_not_authorized(void){ |
| 1139 | @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth()) |
| 1140 | } |
| 1141 | |
| 1142 | /* |
| 1143 | ** Return the common TH1 code to evaluate prior to evaluating any other |
| 1144 | ** TH1 transfer notification scripts. |
| @@ -1316,11 +1328,11 @@ | |
| 1328 | ** Server accepts a file from the client. |
| 1329 | */ |
| 1330 | if( blob_eq(&xfer.aToken[0], "file") ){ |
| 1331 | if( !isPush ){ |
| 1332 | cgi_reset_content(); |
| 1333 | @ error not\sauthorized\sto\swrite%s(whyNotAuth()) |
| 1334 | nErr++; |
| 1335 | break; |
| 1336 | } |
| 1337 | xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList); |
| 1338 | if( blob_size(&xfer.err) ){ |
| @@ -1337,11 +1349,11 @@ | |
| 1349 | ** Server accepts a compressed file from the client. |
| 1350 | */ |
| 1351 | if( blob_eq(&xfer.aToken[0], "cfile") ){ |
| 1352 | if( !isPush ){ |
| 1353 | cgi_reset_content(); |
| 1354 | @ error not\sauthorized\sto\swrite%s(whyNotAuth()) |
| 1355 | nErr++; |
| 1356 | break; |
| 1357 | } |
| 1358 | xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList); |
| 1359 | if( blob_size(&xfer.err) ){ |
| @@ -1461,23 +1473,23 @@ | |
| 1473 | } |
| 1474 | login_check_credentials(); |
| 1475 | if( blob_eq(&xfer.aToken[0], "pull") ){ |
| 1476 | if( !g.perm.Read ){ |
| 1477 | cgi_reset_content(); |
| 1478 | @ error not\sauthorized\sto\sread%s(whyNotAuth()) |
| 1479 | nErr++; |
| 1480 | break; |
| 1481 | } |
| 1482 | isPull = 1; |
| 1483 | }else{ |
| 1484 | if( !g.perm.Write ){ |
| 1485 | if( !isPull ){ |
| 1486 | cgi_reset_content(); |
| 1487 | @ error not\sauthorized\sto\swrite%s(whyNotAuth()) |
| 1488 | nErr++; |
| 1489 | }else{ |
| 1490 | @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth()) |
| 1491 | } |
| 1492 | }else{ |
| 1493 | isPush = 1; |
| 1494 | } |
| 1495 | } |
| @@ -1491,11 +1503,11 @@ | |
| 1503 | int iVers; |
| 1504 | login_check_credentials(); |
| 1505 | if( !g.perm.Clone ){ |
| 1506 | cgi_reset_content(); |
| 1507 | @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) |
| 1508 | @ error not\sauthorized\sto\sclone%s(whyNotAuth()) |
| 1509 | nErr++; |
| 1510 | break; |
| 1511 | } |
| 1512 | if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){ |
| 1513 | @ pragma uv-pull-only |
| @@ -1592,11 +1604,11 @@ | |
| 1604 | } |
| 1605 | blob_zero(&content); |
| 1606 | blob_extract(xfer.pIn, size, &content); |
| 1607 | if( !g.perm.Admin ){ |
| 1608 | cgi_reset_content(); |
| 1609 | @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth()) |
| 1610 | nErr++; |
| 1611 | break; |
| 1612 | } |
| 1613 | configure_receive(zName, &content, CONFIGSET_ALL); |
| 1614 | blob_reset(&content); |
| 1615 |
+3
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -927,10 +927,13 @@ | ||
| 927 | 927 | login_check_credentials(); |
| 928 | 928 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 929 | 929 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 930 | 930 | eType = ARCHIVE_SQLAR; |
| 931 | 931 | zType = "SQL"; |
| 932 | + /* For some reason, SQL-archives are like catnip for robots. So | |
| 933 | + ** don't allow them to be downloaded by user "nobody" */ | |
| 934 | + if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; } | |
| 932 | 935 | }else{ |
| 933 | 936 | eType = ARCHIVE_ZIP; |
| 934 | 937 | zType = "ZIP"; |
| 935 | 938 | } |
| 936 | 939 | fossil_nice_default(); |
| 937 | 940 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -927,10 +927,13 @@ | |
| 927 | login_check_credentials(); |
| 928 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 929 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 930 | eType = ARCHIVE_SQLAR; |
| 931 | zType = "SQL"; |
| 932 | }else{ |
| 933 | eType = ARCHIVE_ZIP; |
| 934 | zType = "ZIP"; |
| 935 | } |
| 936 | fossil_nice_default(); |
| 937 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -927,10 +927,13 @@ | |
| 927 | login_check_credentials(); |
| 928 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 929 | if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
| 930 | eType = ARCHIVE_SQLAR; |
| 931 | zType = "SQL"; |
| 932 | /* For some reason, SQL-archives are like catnip for robots. So |
| 933 | ** don't allow them to be downloaded by user "nobody" */ |
| 934 | if( g.zLogin==0 ){ login_needed(g.anon.Zip); return; } |
| 935 | }else{ |
| 936 | eType = ARCHIVE_ZIP; |
| 937 | zType = "ZIP"; |
| 938 | } |
| 939 | fossil_nice_default(); |
| 940 |
+7
| --- test/tester.tcl | ||
| +++ test/tester.tcl | ||
| @@ -356,10 +356,11 @@ | ||
| 356 | 356 | mtime-changes \ |
| 357 | 357 | mv-rm-files \ |
| 358 | 358 | pgp-command \ |
| 359 | 359 | preferred-diff-type \ |
| 360 | 360 | proxy \ |
| 361 | + raw-bgcolor \ | |
| 361 | 362 | redirect-to-https \ |
| 362 | 363 | relative-paths \ |
| 363 | 364 | repo-cksum \ |
| 364 | 365 | repolist-skin \ |
| 365 | 366 | robot-restrict \ |
| @@ -373,13 +374,19 @@ | ||
| 373 | 374 | ssl-identity \ |
| 374 | 375 | tclsh \ |
| 375 | 376 | th1-setup \ |
| 376 | 377 | th1-uri-regexp \ |
| 377 | 378 | ticket-default-report \ |
| 379 | + timeline-hard-newlines \ | |
| 380 | + timeline-plaintext \ | |
| 381 | + timeline-truncate-at-blank \ | |
| 382 | + timeline-tslink-info \ | |
| 378 | 383 | timeline-utc \ |
| 379 | 384 | user-color-map \ |
| 385 | + verify-comments \ | |
| 380 | 386 | uv-sync \ |
| 387 | + vuln-report \ | |
| 381 | 388 | web-browser] |
| 382 | 389 | |
| 383 | 390 | fossil test-th-eval "hasfeature legacyMvRm" |
| 384 | 391 | |
| 385 | 392 | if {[normalize_result] eq "1"} { |
| 386 | 393 | |
| 387 | 394 | ADDED test/th1-taint.test |
| --- test/tester.tcl | |
| +++ test/tester.tcl | |
| @@ -356,10 +356,11 @@ | |
| 356 | mtime-changes \ |
| 357 | mv-rm-files \ |
| 358 | pgp-command \ |
| 359 | preferred-diff-type \ |
| 360 | proxy \ |
| 361 | redirect-to-https \ |
| 362 | relative-paths \ |
| 363 | repo-cksum \ |
| 364 | repolist-skin \ |
| 365 | robot-restrict \ |
| @@ -373,13 +374,19 @@ | |
| 373 | ssl-identity \ |
| 374 | tclsh \ |
| 375 | th1-setup \ |
| 376 | th1-uri-regexp \ |
| 377 | ticket-default-report \ |
| 378 | timeline-utc \ |
| 379 | user-color-map \ |
| 380 | uv-sync \ |
| 381 | web-browser] |
| 382 | |
| 383 | fossil test-th-eval "hasfeature legacyMvRm" |
| 384 | |
| 385 | if {[normalize_result] eq "1"} { |
| 386 | |
| 387 | DDED test/th1-taint.test |
| --- test/tester.tcl | |
| +++ test/tester.tcl | |
| @@ -356,10 +356,11 @@ | |
| 356 | mtime-changes \ |
| 357 | mv-rm-files \ |
| 358 | pgp-command \ |
| 359 | preferred-diff-type \ |
| 360 | proxy \ |
| 361 | raw-bgcolor \ |
| 362 | redirect-to-https \ |
| 363 | relative-paths \ |
| 364 | repo-cksum \ |
| 365 | repolist-skin \ |
| 366 | robot-restrict \ |
| @@ -373,13 +374,19 @@ | |
| 374 | ssl-identity \ |
| 375 | tclsh \ |
| 376 | th1-setup \ |
| 377 | th1-uri-regexp \ |
| 378 | ticket-default-report \ |
| 379 | timeline-hard-newlines \ |
| 380 | timeline-plaintext \ |
| 381 | timeline-truncate-at-blank \ |
| 382 | timeline-tslink-info \ |
| 383 | timeline-utc \ |
| 384 | user-color-map \ |
| 385 | verify-comments \ |
| 386 | uv-sync \ |
| 387 | vuln-report \ |
| 388 | web-browser] |
| 389 | |
| 390 | fossil test-th-eval "hasfeature legacyMvRm" |
| 391 | |
| 392 | if {[normalize_result] eq "1"} { |
| 393 | |
| 394 | DDED test/th1-taint.test |
+84
| --- a/test/th1-taint.test | ||
| +++ b/test/th1-taint.test | ||
| @@ -0,0 +1,84 @@ | ||
| 1 | +# | |
| 2 | +# Copyright (c) 2025 D. Richard Hipp | |
| 3 | +# | |
| 4 | +# This program is free software; you can redistribute it and/or | |
| 5 | +# modify it under the terms of the Simplified BSD License (also | |
| 6 | +# known as the "2-Clause License" or "FreeBSD License".) | |
| 7 | +# | |
| 8 | +# This program is distributed in the hope that it will be useful, | |
| 9 | +# but without any warranty; without even the implied warranty of | |
| 10 | +# merchantability or fitness for a particular purpose. | |
| 11 | +# | |
| 12 | +# Author contact information: | |
| 13 | +# [email protected] | |
| 14 | +# http://www.hwaci.com/drh/ | |
| 15 | +# | |
| 16 | +############################################################################ | |
| 17 | +# | |
| 18 | +# TH1 Commands | |
| 19 | +# | |
| 20 | + | |
| 21 | +set path [file dirname [info script]]; test_setup | |
| 22 | + | |
| 23 | +proc taint-test {testnum th1script expected} { | |
| 24 | + global fossilexe | |
| 25 | + set rc [catch {exec $fossilexe test-th-eval $th1script} got] | |
| 26 | + if {$rc} { | |
| 27 | + test th1-taint-$testnum 0 | |
| 28 | + puts $got | |
| 29 | + return | |
| 30 | + } | |
| 31 | + if {$got ne $expected} { | |
| 32 | + test th1-taint-$testnum 0 | |
| 33 | + puts " Expected: $expected" | |
| 34 | + puts " Got: $got" | |
| 35 | + } else { | |
| 36 | + test th1-taint-$testnum 1 | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +taint-test 10 {string is tainted abcd} 0 | |
| 41 | +taint-test 20 {string is tainted [taint abcd]} 1 | |
| 42 | +taint-test 30 {string is tainted [untaint [taint abcd]]} 0 | |
| 43 | +taint-test 40 {string is tainted [untaint abcde]} 0 | |
| 44 | +taint-test 50 {string is tainted "abc[taint def]ghi"} 1 | |
| 45 | +taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1 | |
| 46 | + | |
| 47 | +taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1 | |
| 48 | +taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1 | |
| 49 | + | |
| 50 | +taint-test 200 {string is tainted [list abc def ghi]} 0 | |
| 51 | +taint-test 210 {string is tainted [list [taint abc] def ghi]} 1 | |
| 52 | +taint-test 220 {string is tainted [list abc [taint def] ghi]} 1 | |
| 53 | +taint-test 230 {string is tainted [list abc def [taint ghi]]} 1 | |
| 54 | + | |
| 55 | +taint-test 300 { | |
| 56 | + set res {} | |
| 57 | + foreach x [list abc [taint def] ghi] { | |
| 58 | + lappend res [string is tainted $x] | |
| 59 | + } | |
| 60 | + set res | |
| 61 | +} {1 1 1} | |
| 62 | +taint-test 310 { | |
| 63 | + set res {} | |
| 64 | + foreach {x y} [list abc [taint def] ghi jkl] { | |
| 65 | + lappend res [string is tainted $x] [string is tainted $y] | |
| 66 | + } | |
| 67 | + set res | |
| 68 | +} {1 1 1 1} | |
| 69 | + | |
| 70 | +taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1 | |
| 71 | +taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1 | |
| 72 | +taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1 | |
| 73 | +taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0 | |
| 74 | + | |
| 75 | +taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1 | |
| 76 | + | |
| 77 | +taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1 | |
| 78 | + | |
| 79 | +taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1 | |
| 80 | +taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1 | |
| 81 | +taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1 | |
| 82 | + | |
| 83 | + | |
| 84 | +test_cleanup |
| --- a/test/th1-taint.test | |
| +++ b/test/th1-taint.test | |
| @@ -0,0 +1,84 @@ | |
| --- a/test/th1-taint.test | |
| +++ b/test/th1-taint.test | |
| @@ -0,0 +1,84 @@ | |
| 1 | # |
| 2 | # Copyright (c) 2025 D. Richard Hipp |
| 3 | # |
| 4 | # This program is free software; you can redistribute it and/or |
| 5 | # modify it under the terms of the Simplified BSD License (also |
| 6 | # known as the "2-Clause License" or "FreeBSD License".) |
| 7 | # |
| 8 | # This program is distributed in the hope that it will be useful, |
| 9 | # but without any warranty; without even the implied warranty of |
| 10 | # merchantability or fitness for a particular purpose. |
| 11 | # |
| 12 | # Author contact information: |
| 13 | # [email protected] |
| 14 | # http://www.hwaci.com/drh/ |
| 15 | # |
| 16 | ############################################################################ |
| 17 | # |
| 18 | # TH1 Commands |
| 19 | # |
| 20 | |
| 21 | set path [file dirname [info script]]; test_setup |
| 22 | |
| 23 | proc taint-test {testnum th1script expected} { |
| 24 | global fossilexe |
| 25 | set rc [catch {exec $fossilexe test-th-eval $th1script} got] |
| 26 | if {$rc} { |
| 27 | test th1-taint-$testnum 0 |
| 28 | puts $got |
| 29 | return |
| 30 | } |
| 31 | if {$got ne $expected} { |
| 32 | test th1-taint-$testnum 0 |
| 33 | puts " Expected: $expected" |
| 34 | puts " Got: $got" |
| 35 | } else { |
| 36 | test th1-taint-$testnum 1 |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | taint-test 10 {string is tainted abcd} 0 |
| 41 | taint-test 20 {string is tainted [taint abcd]} 1 |
| 42 | taint-test 30 {string is tainted [untaint [taint abcd]]} 0 |
| 43 | taint-test 40 {string is tainted [untaint abcde]} 0 |
| 44 | taint-test 50 {string is tainted "abc[taint def]ghi"} 1 |
| 45 | taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1 |
| 46 | |
| 47 | taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1 |
| 48 | taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1 |
| 49 | |
| 50 | taint-test 200 {string is tainted [list abc def ghi]} 0 |
| 51 | taint-test 210 {string is tainted [list [taint abc] def ghi]} 1 |
| 52 | taint-test 220 {string is tainted [list abc [taint def] ghi]} 1 |
| 53 | taint-test 230 {string is tainted [list abc def [taint ghi]]} 1 |
| 54 | |
| 55 | taint-test 300 { |
| 56 | set res {} |
| 57 | foreach x [list abc [taint def] ghi] { |
| 58 | lappend res [string is tainted $x] |
| 59 | } |
| 60 | set res |
| 61 | } {1 1 1} |
| 62 | taint-test 310 { |
| 63 | set res {} |
| 64 | foreach {x y} [list abc [taint def] ghi jkl] { |
| 65 | lappend res [string is tainted $x] [string is tainted $y] |
| 66 | } |
| 67 | set res |
| 68 | } {1 1 1 1} |
| 69 | |
| 70 | taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1 |
| 71 | taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1 |
| 72 | taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1 |
| 73 | taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0 |
| 74 | |
| 75 | taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1 |
| 76 | |
| 77 | taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1 |
| 78 | |
| 79 | taint-test 700 {string is tainted [string trim [taint " abcdefg "]]} 1 |
| 80 | taint-test 710 {string is tainted [string trimright [taint " abcdefg "]]} 1 |
| 81 | taint-test 720 {string is tainted [string trimleft [taint " abcdefg "]]} 1 |
| 82 | |
| 83 | |
| 84 | test_cleanup |
+36
-36
| --- test/th1.test | ||
| +++ test/th1.test | ||
| @@ -795,23 +795,23 @@ | ||
| 795 | 795 | rpage-\$requested_page\ |
| 796 | 796 | cpage-\$canonical_page\">" [normalize_result]]} |
| 797 | 797 | |
| 798 | 798 | ############################################################################### |
| 799 | 799 | |
| 800 | -fossil test-th-eval "styleHeader {Page Title Here}" | |
| 801 | -test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 800 | +#fossil test-th-eval "styleHeader {Page Title Here}" | |
| 801 | +#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 802 | 802 | |
| 803 | 803 | ############################################################################### |
| 804 | 804 | |
| 805 | 805 | test_in_checkout th1-header-2 { |
| 806 | 806 | fossil test-th-eval --open-config "styleHeader {Page Title Here}" |
| 807 | 807 | } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 808 | 808 | |
| 809 | 809 | ############################################################################### |
| 810 | 810 | |
| 811 | -fossil test-th-eval "styleFooter" | |
| 812 | -test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 811 | +#fossil test-th-eval "styleFooter" | |
| 812 | +#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 813 | 813 | |
| 814 | 814 | ############################################################################### |
| 815 | 815 | |
| 816 | 816 | fossil test-th-eval --open-config "styleFooter" |
| 817 | 817 | test th1-footer-2 {$RESULT eq {}} |
| @@ -879,44 +879,44 @@ | ||
| 879 | 879 | test th1-artifact-1 {$RESULT eq \ |
| 880 | 880 | {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} |
| 881 | 881 | |
| 882 | 882 | ############################################################################### |
| 883 | 883 | |
| 884 | -fossil test-th-eval "artifact tip" | |
| 885 | -test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 884 | +#fossil test-th-eval "artifact tip" | |
| 885 | +#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 886 | 886 | |
| 887 | 887 | ############################################################################### |
| 888 | 888 | |
| 889 | 889 | test_in_checkout th1-artifact-3 { |
| 890 | 890 | fossil test-th-eval --open-config "artifact tip" |
| 891 | 891 | } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]} |
| 892 | 892 | |
| 893 | 893 | ############################################################################### |
| 894 | 894 | |
| 895 | -fossil test-th-eval "artifact 0000000000" | |
| 896 | -test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 895 | +#fossil test-th-eval "artifact 0000000000" | |
| 896 | +#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 897 | 897 | |
| 898 | 898 | ############################################################################### |
| 899 | 899 | |
| 900 | 900 | fossil test-th-eval --open-config "artifact 0000000000" |
| 901 | 901 | test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} |
| 902 | 902 | |
| 903 | 903 | ############################################################################### |
| 904 | 904 | |
| 905 | -fossil test-th-eval "artifact tip test/th1.test" | |
| 906 | -test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 905 | +#fossil test-th-eval "artifact tip test/th1.test" | |
| 906 | +#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 907 | 907 | |
| 908 | 908 | ############################################################################### |
| 909 | 909 | |
| 910 | 910 | test_in_checkout th1-artifact-7 { |
| 911 | 911 | fossil test-th-eval --open-config "artifact tip test/th1.test" |
| 912 | 912 | } {[regexp -- {th1-artifact-7} $RESULT]} |
| 913 | 913 | |
| 914 | 914 | ############################################################################### |
| 915 | 915 | |
| 916 | -fossil test-th-eval "artifact 0000000000 test/th1.test" | |
| 917 | -test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 916 | +#fossil test-th-eval "artifact 0000000000 test/th1.test" | |
| 917 | +#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} | |
| 918 | 918 | |
| 919 | 919 | ############################################################################### |
| 920 | 920 | |
| 921 | 921 | fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" |
| 922 | 922 | test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} |
| @@ -947,12 +947,12 @@ | ||
| 947 | 947 | } |
| 948 | 948 | } |
| 949 | 949 | |
| 950 | 950 | ############################################################################### |
| 951 | 951 | |
| 952 | -fossil test-th-eval "globalState configuration" | |
| 953 | -test th1-globalState-3 {[string length $RESULT] == 0} | |
| 952 | +#fossil test-th-eval "globalState configuration" | |
| 953 | +#test th1-globalState-3 {[string length $RESULT] == 0} | |
| 954 | 954 | |
| 955 | 955 | ############################################################################### |
| 956 | 956 | |
| 957 | 957 | fossil test-th-eval --open-config "globalState configuration" |
| 958 | 958 | test th1-globalState-4 {[string length $RESULT] > 0} |
| @@ -1041,12 +1041,12 @@ | ||
| 1041 | 1041 | fossil test-th-eval "globalState flags" |
| 1042 | 1042 | test th1-globalState-16 {$RESULT eq "0"} |
| 1043 | 1043 | |
| 1044 | 1044 | ############################################################################### |
| 1045 | 1045 | |
| 1046 | -fossil test-th-eval "reinitialize; globalState configuration" | |
| 1047 | -test th1-reinitialize-1 {$RESULT eq ""} | |
| 1046 | +#fossil test-th-eval "reinitialize; globalState configuration" | |
| 1047 | +#test th1-reinitialize-1 {$RESULT eq ""} | |
| 1048 | 1048 | |
| 1049 | 1049 | ############################################################################### |
| 1050 | 1050 | |
| 1051 | 1051 | fossil test-th-eval "reinitialize 1; globalState configuration" |
| 1052 | 1052 | test th1-reinitialize-2 {$RESULT ne ""} |
| @@ -1056,29 +1056,29 @@ | ||
| 1056 | 1056 | # |
| 1057 | 1057 | # NOTE: This test will fail if the command names are added to TH1, or |
| 1058 | 1058 | # moved from Tcl builds to plain or the reverse. Sorting the |
| 1059 | 1059 | # command lists eliminates a dependence on order. |
| 1060 | 1060 | # |
| 1061 | -fossil test-th-eval "info commands" | |
| 1062 | -set sorted_result [lsort $RESULT] | |
| 1063 | -protOut "Sorted: $sorted_result" | |
| 1064 | -set base_commands {anoncap anycap array artifact break breakpoint \ | |
| 1065 | - builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \ | |
| 1066 | - combobox continue copybtn date decorate defHeader dir enable_htmlify \ | |
| 1067 | - enable_output encode64 error expr for foreach getParameter glob_match \ | |
| 1068 | - globalState hascap hasfeature html htmlize http httpize if info \ | |
| 1069 | - insertCsrf lappend lindex linecount list llength lsearch markdown nonce \ | |
| 1070 | - proc puts query randhex redirect regexp reinitialize rename render \ | |
| 1071 | - repository return searchable set setParameter setting stime string \ | |
| 1072 | - styleFooter styleHeader styleScript submenu tclReady trace unset \ | |
| 1073 | - unversioned uplevel upvar utime verifyCsrf verifyLogin wiki} | |
| 1074 | -set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe} | |
| 1075 | -if {$th1Tcl} { | |
| 1076 | - test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]} | |
| 1077 | -} else { | |
| 1078 | - test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]} | |
| 1079 | -} | |
| 1061 | +#fossil test-th-eval "info commands" | |
| 1062 | +#set sorted_result [lsort $RESULT] | |
| 1063 | +#protOut "Sorted: $sorted_result" | |
| 1064 | +#set base_commands {anoncap anycap array artifact break breakpoint \ | |
| 1065 | +# builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \ | |
| 1066 | +# combobox continue copybtn date decorate defHeader dir \ | |
| 1067 | +# enable_output encode64 error expr for foreach getParameter glob_match \ | |
| 1068 | +# globalState hascap hasfeature html htmlize http httpize if info \ | |
| 1069 | +# insertCsrf lappend lindex linecount list llength lsearch markdown nonce \ | |
| 1070 | +# proc puts query randhex redirect regexp reinitialize rename render \ | |
| 1071 | +# repository return searchable set setParameter setting stime string \ | |
| 1072 | +# styleFooter styleHeader styleScript submenu tclReady trace unset \ | |
| 1073 | +# unversioned uplevel upvar utime verifyCsrf verifyLogin wiki} | |
| 1074 | +#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe} | |
| 1075 | +#if {$th1Tcl} { | |
| 1076 | +# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]} | |
| 1077 | +#} else { | |
| 1078 | +# test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]} | |
| 1079 | +#} | |
| 1080 | 1080 | |
| 1081 | 1081 | ############################################################################### |
| 1082 | 1082 | |
| 1083 | 1083 | fossil test-th-eval "info vars" |
| 1084 | 1084 | |
| @@ -1326,11 +1326,11 @@ | ||
| 1326 | 1326 | |
| 1327 | 1327 | ############################################################################### |
| 1328 | 1328 | |
| 1329 | 1329 | fossil test-th-eval {string is other 123} |
| 1330 | 1330 | test th1-string-is-4 {$RESULT eq \ |
| 1331 | -"TH_ERROR: Expected alnum, double, integer, or list, got: other"} | |
| 1331 | +"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"} | |
| 1332 | 1332 | |
| 1333 | 1333 | ############################################################################### |
| 1334 | 1334 | |
| 1335 | 1335 | fossil test-th-eval {string is alnum 123} |
| 1336 | 1336 | test th1-string-is-5 {$RESULT eq "1"} |
| 1337 | 1337 | |
| 1338 | 1338 | ADDED tools/fake-smtpd.tcl |
| --- test/th1.test | |
| +++ test/th1.test | |
| @@ -795,23 +795,23 @@ | |
| 795 | rpage-\$requested_page\ |
| 796 | cpage-\$canonical_page\">" [normalize_result]]} |
| 797 | |
| 798 | ############################################################################### |
| 799 | |
| 800 | fossil test-th-eval "styleHeader {Page Title Here}" |
| 801 | test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 802 | |
| 803 | ############################################################################### |
| 804 | |
| 805 | test_in_checkout th1-header-2 { |
| 806 | fossil test-th-eval --open-config "styleHeader {Page Title Here}" |
| 807 | } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 808 | |
| 809 | ############################################################################### |
| 810 | |
| 811 | fossil test-th-eval "styleFooter" |
| 812 | test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 813 | |
| 814 | ############################################################################### |
| 815 | |
| 816 | fossil test-th-eval --open-config "styleFooter" |
| 817 | test th1-footer-2 {$RESULT eq {}} |
| @@ -879,44 +879,44 @@ | |
| 879 | test th1-artifact-1 {$RESULT eq \ |
| 880 | {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} |
| 881 | |
| 882 | ############################################################################### |
| 883 | |
| 884 | fossil test-th-eval "artifact tip" |
| 885 | test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 886 | |
| 887 | ############################################################################### |
| 888 | |
| 889 | test_in_checkout th1-artifact-3 { |
| 890 | fossil test-th-eval --open-config "artifact tip" |
| 891 | } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]} |
| 892 | |
| 893 | ############################################################################### |
| 894 | |
| 895 | fossil test-th-eval "artifact 0000000000" |
| 896 | test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 897 | |
| 898 | ############################################################################### |
| 899 | |
| 900 | fossil test-th-eval --open-config "artifact 0000000000" |
| 901 | test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} |
| 902 | |
| 903 | ############################################################################### |
| 904 | |
| 905 | fossil test-th-eval "artifact tip test/th1.test" |
| 906 | test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 907 | |
| 908 | ############################################################################### |
| 909 | |
| 910 | test_in_checkout th1-artifact-7 { |
| 911 | fossil test-th-eval --open-config "artifact tip test/th1.test" |
| 912 | } {[regexp -- {th1-artifact-7} $RESULT]} |
| 913 | |
| 914 | ############################################################################### |
| 915 | |
| 916 | fossil test-th-eval "artifact 0000000000 test/th1.test" |
| 917 | test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 918 | |
| 919 | ############################################################################### |
| 920 | |
| 921 | fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" |
| 922 | test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} |
| @@ -947,12 +947,12 @@ | |
| 947 | } |
| 948 | } |
| 949 | |
| 950 | ############################################################################### |
| 951 | |
| 952 | fossil test-th-eval "globalState configuration" |
| 953 | test th1-globalState-3 {[string length $RESULT] == 0} |
| 954 | |
| 955 | ############################################################################### |
| 956 | |
| 957 | fossil test-th-eval --open-config "globalState configuration" |
| 958 | test th1-globalState-4 {[string length $RESULT] > 0} |
| @@ -1041,12 +1041,12 @@ | |
| 1041 | fossil test-th-eval "globalState flags" |
| 1042 | test th1-globalState-16 {$RESULT eq "0"} |
| 1043 | |
| 1044 | ############################################################################### |
| 1045 | |
| 1046 | fossil test-th-eval "reinitialize; globalState configuration" |
| 1047 | test th1-reinitialize-1 {$RESULT eq ""} |
| 1048 | |
| 1049 | ############################################################################### |
| 1050 | |
| 1051 | fossil test-th-eval "reinitialize 1; globalState configuration" |
| 1052 | test th1-reinitialize-2 {$RESULT ne ""} |
| @@ -1056,29 +1056,29 @@ | |
| 1056 | # |
| 1057 | # NOTE: This test will fail if the command names are added to TH1, or |
| 1058 | # moved from Tcl builds to plain or the reverse. Sorting the |
| 1059 | # command lists eliminates a dependence on order. |
| 1060 | # |
| 1061 | fossil test-th-eval "info commands" |
| 1062 | set sorted_result [lsort $RESULT] |
| 1063 | protOut "Sorted: $sorted_result" |
| 1064 | set base_commands {anoncap anycap array artifact break breakpoint \ |
| 1065 | builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \ |
| 1066 | combobox continue copybtn date decorate defHeader dir enable_htmlify \ |
| 1067 | enable_output encode64 error expr for foreach getParameter glob_match \ |
| 1068 | globalState hascap hasfeature html htmlize http httpize if info \ |
| 1069 | insertCsrf lappend lindex linecount list llength lsearch markdown nonce \ |
| 1070 | proc puts query randhex redirect regexp reinitialize rename render \ |
| 1071 | repository return searchable set setParameter setting stime string \ |
| 1072 | styleFooter styleHeader styleScript submenu tclReady trace unset \ |
| 1073 | unversioned uplevel upvar utime verifyCsrf verifyLogin wiki} |
| 1074 | set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe} |
| 1075 | if {$th1Tcl} { |
| 1076 | test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]} |
| 1077 | } else { |
| 1078 | test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]} |
| 1079 | } |
| 1080 | |
| 1081 | ############################################################################### |
| 1082 | |
| 1083 | fossil test-th-eval "info vars" |
| 1084 | |
| @@ -1326,11 +1326,11 @@ | |
| 1326 | |
| 1327 | ############################################################################### |
| 1328 | |
| 1329 | fossil test-th-eval {string is other 123} |
| 1330 | test th1-string-is-4 {$RESULT eq \ |
| 1331 | "TH_ERROR: Expected alnum, double, integer, or list, got: other"} |
| 1332 | |
| 1333 | ############################################################################### |
| 1334 | |
| 1335 | fossil test-th-eval {string is alnum 123} |
| 1336 | test th1-string-is-5 {$RESULT eq "1"} |
| 1337 | |
| 1338 | DDED tools/fake-smtpd.tcl |
| --- test/th1.test | |
| +++ test/th1.test | |
| @@ -795,23 +795,23 @@ | |
| 795 | rpage-\$requested_page\ |
| 796 | cpage-\$canonical_page\">" [normalize_result]]} |
| 797 | |
| 798 | ############################################################################### |
| 799 | |
| 800 | #fossil test-th-eval "styleHeader {Page Title Here}" |
| 801 | #test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 802 | |
| 803 | ############################################################################### |
| 804 | |
| 805 | test_in_checkout th1-header-2 { |
| 806 | fossil test-th-eval --open-config "styleHeader {Page Title Here}" |
| 807 | } {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]} |
| 808 | |
| 809 | ############################################################################### |
| 810 | |
| 811 | #fossil test-th-eval "styleFooter" |
| 812 | #test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 813 | |
| 814 | ############################################################################### |
| 815 | |
| 816 | fossil test-th-eval --open-config "styleFooter" |
| 817 | test th1-footer-2 {$RESULT eq {}} |
| @@ -879,44 +879,44 @@ | |
| 879 | test th1-artifact-1 {$RESULT eq \ |
| 880 | {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}} |
| 881 | |
| 882 | ############################################################################### |
| 883 | |
| 884 | #fossil test-th-eval "artifact tip" |
| 885 | #test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 886 | |
| 887 | ############################################################################### |
| 888 | |
| 889 | test_in_checkout th1-artifact-3 { |
| 890 | fossil test-th-eval --open-config "artifact tip" |
| 891 | } {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]} |
| 892 | |
| 893 | ############################################################################### |
| 894 | |
| 895 | #fossil test-th-eval "artifact 0000000000" |
| 896 | #test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 897 | |
| 898 | ############################################################################### |
| 899 | |
| 900 | fossil test-th-eval --open-config "artifact 0000000000" |
| 901 | test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}} |
| 902 | |
| 903 | ############################################################################### |
| 904 | |
| 905 | #fossil test-th-eval "artifact tip test/th1.test" |
| 906 | #test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 907 | |
| 908 | ############################################################################### |
| 909 | |
| 910 | test_in_checkout th1-artifact-7 { |
| 911 | fossil test-th-eval --open-config "artifact tip test/th1.test" |
| 912 | } {[regexp -- {th1-artifact-7} $RESULT]} |
| 913 | |
| 914 | ############################################################################### |
| 915 | |
| 916 | #fossil test-th-eval "artifact 0000000000 test/th1.test" |
| 917 | #test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}} |
| 918 | |
| 919 | ############################################################################### |
| 920 | |
| 921 | fossil test-th-eval --open-config "artifact 0000000000 test/th1.test" |
| 922 | test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}} |
| @@ -947,12 +947,12 @@ | |
| 947 | } |
| 948 | } |
| 949 | |
| 950 | ############################################################################### |
| 951 | |
| 952 | #fossil test-th-eval "globalState configuration" |
| 953 | #test th1-globalState-3 {[string length $RESULT] == 0} |
| 954 | |
| 955 | ############################################################################### |
| 956 | |
| 957 | fossil test-th-eval --open-config "globalState configuration" |
| 958 | test th1-globalState-4 {[string length $RESULT] > 0} |
| @@ -1041,12 +1041,12 @@ | |
| 1041 | fossil test-th-eval "globalState flags" |
| 1042 | test th1-globalState-16 {$RESULT eq "0"} |
| 1043 | |
| 1044 | ############################################################################### |
| 1045 | |
| 1046 | #fossil test-th-eval "reinitialize; globalState configuration" |
| 1047 | #test th1-reinitialize-1 {$RESULT eq ""} |
| 1048 | |
| 1049 | ############################################################################### |
| 1050 | |
| 1051 | fossil test-th-eval "reinitialize 1; globalState configuration" |
| 1052 | test th1-reinitialize-2 {$RESULT ne ""} |
| @@ -1056,29 +1056,29 @@ | |
| 1056 | # |
| 1057 | # NOTE: This test will fail if the command names are added to TH1, or |
| 1058 | # moved from Tcl builds to plain or the reverse. Sorting the |
| 1059 | # command lists eliminates a dependence on order. |
| 1060 | # |
| 1061 | #fossil test-th-eval "info commands" |
| 1062 | #set sorted_result [lsort $RESULT] |
| 1063 | #protOut "Sorted: $sorted_result" |
| 1064 | #set base_commands {anoncap anycap array artifact break breakpoint \ |
| 1065 | # builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \ |
| 1066 | # combobox continue copybtn date decorate defHeader dir \ |
| 1067 | # enable_output encode64 error expr for foreach getParameter glob_match \ |
| 1068 | # globalState hascap hasfeature html htmlize http httpize if info \ |
| 1069 | # insertCsrf lappend lindex linecount list llength lsearch markdown nonce \ |
| 1070 | # proc puts query randhex redirect regexp reinitialize rename render \ |
| 1071 | # repository return searchable set setParameter setting stime string \ |
| 1072 | # styleFooter styleHeader styleScript submenu tclReady trace unset \ |
| 1073 | # unversioned uplevel upvar utime verifyCsrf verifyLogin wiki} |
| 1074 | #set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe} |
| 1075 | #if {$th1Tcl} { |
| 1076 | # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]} |
| 1077 | #} else { |
| 1078 | # test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]} |
| 1079 | #} |
| 1080 | |
| 1081 | ############################################################################### |
| 1082 | |
| 1083 | fossil test-th-eval "info vars" |
| 1084 | |
| @@ -1326,11 +1326,11 @@ | |
| 1326 | |
| 1327 | ############################################################################### |
| 1328 | |
| 1329 | fossil test-th-eval {string is other 123} |
| 1330 | test th1-string-is-4 {$RESULT eq \ |
| 1331 | "TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"} |
| 1332 | |
| 1333 | ############################################################################### |
| 1334 | |
| 1335 | fossil test-th-eval {string is alnum 123} |
| 1336 | test th1-string-is-5 {$RESULT eq "1"} |
| 1337 | |
| 1338 | DDED tools/fake-smtpd.tcl |
+84
| --- a/tools/fake-smtpd.tcl | ||
| +++ b/tools/fake-smtpd.tcl | ||
| @@ -0,0 +1,84 @@ | ||
| 1 | +#!/usr/bin/tclsh | |
| 2 | +# | |
| 3 | +# This script is a testing aid for working on the Relay notification method | |
| 4 | +# in Fossil. | |
| 5 | +# | |
| 6 | +# This script listens for connections on port 25 or probably some other TCP | |
| 7 | +# port specified by the "--port N" option. It pretend to be an SMTP server, | |
| 8 | +# though it does not actually relay any email. Instead, it just prints the | |
| 9 | +# SMTP conversation on stdout. | |
| 10 | +# | |
| 11 | +# If the "--max N" option is used, then the fake SMTP server shuts down | |
| 12 | +# with an error after receiving N messages from the client. This can be | |
| 13 | +# used to test retry capabilities in the client. | |
| 14 | +# | |
| 15 | +# Suggested Test Procedure For Fossil Relay Notifications | |
| 16 | +# | |
| 17 | +# 1. Bring up "fossil ui" | |
| 18 | +# 2. Configure notification for relay to localhost:8025 | |
| 19 | +# 3. Start this script in a separate window. Something like: | |
| 20 | +# tclsh fake-smtpd.tcl -port 8025 -max 100 | |
| 21 | +# 4. Send test messages using Fossil | |
| 22 | +# | |
| 23 | +proc conn_puts {chan txt} { | |
| 24 | + puts "S: $txt" | |
| 25 | + puts $chan $txt | |
| 26 | + flush $chan | |
| 27 | +} | |
| 28 | +set mxMsg 100000000 | |
| 29 | +proc connection {chan ip port} { | |
| 30 | + global mxMsg | |
| 31 | + set nMsg 0 | |
| 32 | + puts "*** begin connection from $ip:$port ***" | |
| 33 | + conn_puts $chan "220 localhost fake-SMTPD" | |
| 34 | + set inData 0 | |
| 35 | + while {1} { | |
| 36 | + set line [string trimright [gets $chan]] | |
| 37 | + if {$line eq ""} { | |
| 38 | + if {[eof $chan]} break | |
| 39 | + } | |
| 40 | + puts "C: $line" | |
| 41 | + incr nMsg | |
| 42 | + if {$inData} { | |
| 43 | + if {$line eq "."} { | |
| 44 | + set inData 0 | |
| 45 | + conn_puts $chan "250 Ok" | |
| 46 | + } | |
| 47 | + } elseif {$nMsg>$mxMsg} { | |
| 48 | + conn_puts $chan "999 I'm done!" | |
| 49 | + break | |
| 50 | + } elseif {[string match "HELO *" $line]} { | |
| 51 | + conn_puts $chan "250 Ok" | |
| 52 | + } elseif {[string match "EHLO *" $line]} { | |
| 53 | + conn_puts $chan "250-SIZE 100000" | |
| 54 | + conn_puts $chan "250 HELP" | |
| 55 | + } elseif {[string match "DATA*" $line]} { | |
| 56 | + conn_puts $chan "354 End data with <CR><LF>.<CR><LF>" | |
| 57 | + set inData 1 | |
| 58 | + } elseif {[string match "QUIT*" $line]} { | |
| 59 | + conn_puts $chan "221 Bye" | |
| 60 | + break | |
| 61 | + } else { | |
| 62 | + conn_puts $chan "250 Ok" | |
| 63 | + } | |
| 64 | + } | |
| 65 | + puts "*** connection closed ($nMsg messages) ***" | |
| 66 | + close $chan | |
| 67 | +} | |
| 68 | +set port 25 | |
| 69 | +set argc [llength $argv] | |
| 70 | +for {set i 0} {$i<$argc-1} {incr i} { | |
| 71 | + set arg [lindex $argv $i] | |
| 72 | + if {$arg eq "-port" || $arg eq "--port"} { | |
| 73 | + incr i | |
| 74 | + set port [lindex $argv $i] | |
| 75 | + } | |
| 76 | + if {$arg eq "-max" || $arg eq "--max"} { | |
| 77 | + incr i | |
| 78 | + set mxMsg [lindex $argv $i] | |
| 79 | + } | |
| 80 | +} | |
| 81 | +puts "listening on localhost:$port" | |
| 82 | +socket -server connection $port | |
| 83 | +set forever 0 | |
| 84 | +vwait forever |
| --- a/tools/fake-smtpd.tcl | |
| +++ b/tools/fake-smtpd.tcl | |
| @@ -0,0 +1,84 @@ | |
| --- a/tools/fake-smtpd.tcl | |
| +++ b/tools/fake-smtpd.tcl | |
| @@ -0,0 +1,84 @@ | |
| 1 | #!/usr/bin/tclsh |
| 2 | # |
| 3 | # This script is a testing aid for working on the Relay notification method |
| 4 | # in Fossil. |
| 5 | # |
| 6 | # This script listens for connections on port 25 or probably some other TCP |
| 7 | # port specified by the "--port N" option. It pretend to be an SMTP server, |
| 8 | # though it does not actually relay any email. Instead, it just prints the |
| 9 | # SMTP conversation on stdout. |
| 10 | # |
| 11 | # If the "--max N" option is used, then the fake SMTP server shuts down |
| 12 | # with an error after receiving N messages from the client. This can be |
| 13 | # used to test retry capabilities in the client. |
| 14 | # |
| 15 | # Suggested Test Procedure For Fossil Relay Notifications |
| 16 | # |
| 17 | # 1. Bring up "fossil ui" |
| 18 | # 2. Configure notification for relay to localhost:8025 |
| 19 | # 3. Start this script in a separate window. Something like: |
| 20 | # tclsh fake-smtpd.tcl -port 8025 -max 100 |
| 21 | # 4. Send test messages using Fossil |
| 22 | # |
| 23 | proc conn_puts {chan txt} { |
| 24 | puts "S: $txt" |
| 25 | puts $chan $txt |
| 26 | flush $chan |
| 27 | } |
| 28 | set mxMsg 100000000 |
| 29 | proc connection {chan ip port} { |
| 30 | global mxMsg |
| 31 | set nMsg 0 |
| 32 | puts "*** begin connection from $ip:$port ***" |
| 33 | conn_puts $chan "220 localhost fake-SMTPD" |
| 34 | set inData 0 |
| 35 | while {1} { |
| 36 | set line [string trimright [gets $chan]] |
| 37 | if {$line eq ""} { |
| 38 | if {[eof $chan]} break |
| 39 | } |
| 40 | puts "C: $line" |
| 41 | incr nMsg |
| 42 | if {$inData} { |
| 43 | if {$line eq "."} { |
| 44 | set inData 0 |
| 45 | conn_puts $chan "250 Ok" |
| 46 | } |
| 47 | } elseif {$nMsg>$mxMsg} { |
| 48 | conn_puts $chan "999 I'm done!" |
| 49 | break |
| 50 | } elseif {[string match "HELO *" $line]} { |
| 51 | conn_puts $chan "250 Ok" |
| 52 | } elseif {[string match "EHLO *" $line]} { |
| 53 | conn_puts $chan "250-SIZE 100000" |
| 54 | conn_puts $chan "250 HELP" |
| 55 | } elseif {[string match "DATA*" $line]} { |
| 56 | conn_puts $chan "354 End data with <CR><LF>.<CR><LF>" |
| 57 | set inData 1 |
| 58 | } elseif {[string match "QUIT*" $line]} { |
| 59 | conn_puts $chan "221 Bye" |
| 60 | break |
| 61 | } else { |
| 62 | conn_puts $chan "250 Ok" |
| 63 | } |
| 64 | } |
| 65 | puts "*** connection closed ($nMsg messages) ***" |
| 66 | close $chan |
| 67 | } |
| 68 | set port 25 |
| 69 | set argc [llength $argv] |
| 70 | for {set i 0} {$i<$argc-1} {incr i} { |
| 71 | set arg [lindex $argv $i] |
| 72 | if {$arg eq "-port" || $arg eq "--port"} { |
| 73 | incr i |
| 74 | set port [lindex $argv $i] |
| 75 | } |
| 76 | if {$arg eq "-max" || $arg eq "--max"} { |
| 77 | incr i |
| 78 | set mxMsg [lindex $argv $i] |
| 79 | } |
| 80 | } |
| 81 | puts "listening on localhost:$port" |
| 82 | socket -server connection $port |
| 83 | set forever 0 |
| 84 | vwait forever |
+5
| --- www/cgi.wiki | ||
| +++ www/cgi.wiki | ||
| @@ -79,10 +79,15 @@ | ||
| 79 | 79 | If no repository has such a non-zero repolist-skin setting, then |
| 80 | 80 | the repository list is generic HTML without any decoration, with |
| 81 | 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | 82 | environment variable. The variable can be defined in the CGI |
| 83 | 83 | control file using the [#setenv|<tt>setenv:</tt>] statement. |
| 84 | + | |
| 85 | +The "Project Description" and "Login-Group" columns on the repolist page | |
| 86 | +are optional. They are hidden by default. Show them by | |
| 87 | +etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to | |
| 88 | +a string that contains substrings "description" and/or "login-group". | |
| 84 | 89 | |
| 85 | 90 | The repolist-generated page recurses into subdirectories and will list |
| 86 | 91 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 87 | 92 | |
| 88 | 93 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 89 | 94 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -79,10 +79,15 @@ | |
| 79 | If no repository has such a non-zero repolist-skin setting, then |
| 80 | the repository list is generic HTML without any decoration, with |
| 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | environment variable. The variable can be defined in the CGI |
| 83 | control file using the [#setenv|<tt>setenv:</tt>] statement. |
| 84 | |
| 85 | The repolist-generated page recurses into subdirectories and will list |
| 86 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 87 | |
| 88 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 89 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -79,10 +79,15 @@ | |
| 79 | If no repository has such a non-zero repolist-skin setting, then |
| 80 | the repository list is generic HTML without any decoration, with |
| 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | environment variable. The variable can be defined in the CGI |
| 83 | control file using the [#setenv|<tt>setenv:</tt>] statement. |
| 84 | |
| 85 | The "Project Description" and "Login-Group" columns on the repolist page |
| 86 | are optional. They are hidden by default. Show them by |
| 87 | etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to |
| 88 | a string that contains substrings "description" and/or "login-group". |
| 89 | |
| 90 | The repolist-generated page recurses into subdirectories and will list |
| 91 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 92 | |
| 93 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 94 |
+59
-24
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,43 +1,49 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | -<h2 id='v2_26'>Changes for version 2.26 (pending)</h2> | |
| 3 | +<h2 id='v2_27'>Changes for version 2.27 (pending)</h2> | |
| 4 | 4 | |
| 5 | - * Enhancements to [/help?cmd=diff|fossil diff] and similar: | |
| 5 | + * <i>(pending)</i> | |
| 6 | + | |
| 7 | +<h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol> | |
| 8 | + <li>Enhancements to [/help?cmd=diff|fossil diff] and similar: | |
| 6 | 9 | <ol type="a"> |
| 7 | - <li> The --from can optionally accept a directory name as its argument, | |
| 8 | - and uses files under that directory as the baseline for the diff. | |
| 10 | + <li> The argument to the --from option can be a directory name, causing | |
| 11 | + Fossil to use files under that directory as the baseline for the diff. | |
| 9 | 12 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 10 | 13 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 11 | 14 | are available, or a --by diff if not. |
| 12 | 15 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 13 | 16 | diff up to date with the latest changes on disk. |
| 14 | 17 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | 18 | diffs of multiple files. |
| 16 | 19 | </ol> |
| 17 | - * Added the [/help?cmd=/ckout|/ckout web page] to provide information | |
| 20 | + <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information | |
| 18 | 21 | about pending changes in a working check-out |
| 19 | - * Enhancements to the [/help?cmd=ui|fossil ui] command: | |
| 22 | + <li>Enhancements to the [/help?cmd=ui|fossil ui] command: | |
| 20 | 23 | <ol type="a"> |
| 21 | 24 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 22 | 25 | start page. Or, if the new "--from PATH" option is present, the |
| 23 | 26 | default start page becomes "/ckout?exbase=PATH". |
| 24 | 27 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 25 | 28 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 26 | 29 | person editing this change log has |
| 27 | 30 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 28 | 31 | press "Reload" on the web browser to view edits. |
| 32 | + <li> Accept both IPv4 and IPv6 connections on all platforms, including | |
| 33 | + Windows and OpenBSD. This also applies to the "fossil server" | |
| 34 | + command. | |
| 29 | 35 | </ol> |
| 30 | - * Enhancements to [/help?cmd=merge|fossil merge]: | |
| 36 | + <li>Enhancements to [/help?cmd=merge|fossil merge]: | |
| 31 | 37 | <ol type="a"> |
| 32 | 38 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 33 | 39 | especially the --tk option to that command, to provide analysis |
| 34 | 40 | of the most recent merge or update operation. |
| 35 | 41 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 36 | 42 | text that shows Fossil's suggested resolution to the conflict. |
| 37 | 43 | </ol> |
| 38 | - * Enhancements to [/help?cmd=commit|fossil commit]: | |
| 44 | + <li>Enhancements to [/help?cmd=commit|fossil commit]: | |
| 39 | 45 | <ol type="a"> |
| 40 | 46 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 41 | 47 | in the check-in comment, it will alert the developer and give |
| 42 | 48 | him or her the opportunity to edit the comment before continuing. |
| 43 | 49 | This feature is controllable by the |
| @@ -47,18 +53,19 @@ | ||
| 47 | 53 | <li> Added the ability to sign check-ins with SSH keys. |
| 48 | 54 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 49 | 55 | branch has been changed. |
| 50 | 56 | <li> The interactive checkin comment prompt shows the formatting rules |
| 51 | 57 | set for that repository. |
| 58 | + <li> Add the "--editor" option. | |
| 52 | 59 | </ol> |
| 53 | - * Deprecate the --comfmtflags and --comment-format global options and | |
| 60 | + <li>Deprecate the --comfmtflags and --comment-format global options and | |
| 54 | 61 | no longer list them in the built-in help, but keep them working for |
| 55 | 62 | backwards compatibility. |
| 56 | 63 | Alternative TTY comment formatting can still be specified using the |
| 57 | 64 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 58 | 65 | default comment format is now called "canonical", not "legacy". |
| 59 | - * Enhancements to the [/help?cmd=/timeline|/timeline page]: | |
| 66 | + <li>Enhancements to the [/help?cmd=/timeline|/timeline page]: | |
| 60 | 67 | <ol type="a"> |
| 61 | 68 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 62 | 69 | like "rl=" ("Related List") but adds "mionly" style related |
| 63 | 70 | check-ins instead of the full "rel" style. |
| 64 | 71 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -81,35 +88,35 @@ | ||
| 81 | 88 | <li> Accept the "Z" (Zulu-time) suffix on date arguments for the |
| 82 | 89 | "ymd" and "yw" query parameters. |
| 83 | 90 | <li> The new "min" query parameter, when added to a from=,to= query, |
| 84 | 91 | collapses long runs of check-ins on the same branch into just |
| 85 | 92 | end-points. |
| 86 | - <li> The p= and d= parameters an reference different check-ins, which | |
| 87 | - case the timeline shows those check-ins that are both ancestors | |
| 88 | - of p= and descendants of d=. | |
| 93 | + <li> The p= and d= parameters can now reference different check-ins, | |
| 94 | + in which case the timeline shows those check-ins that are both | |
| 95 | + ancestors of p= and descendants of d=. | |
| 89 | 96 | <li> The saturation and intensity of user-specified checkin and branch |
| 90 | 97 | background colors are automatically adjusted to keep the colors |
| 91 | 98 | compatible with the current skin, unless the |
| 92 | 99 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 93 | 100 | </ol> |
| 94 | - * The [/help?cmd=/docfile|/docfile webpage] was added. It works like | |
| 101 | + <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like | |
| 95 | 102 | /doc but keeps the title of markdown documents with the document rather |
| 96 | 103 | that moving it up to the page title. |
| 97 | - * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis | |
| 104 | + <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis | |
| 98 | 105 | and debugging |
| 99 | - * Added the "artifact_to_json(NAME)" SQL function that returns a JSON | |
| 106 | + <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON | |
| 100 | 107 | decoding of the artifact described by NAME. |
| 101 | - * Improvements to the [/help?cmd=patch|fossil patch] command: | |
| 108 | + <li>Improvements to the [/help?cmd=patch|fossil patch] command: | |
| 102 | 109 | <ol type="a"> |
| 103 | 110 | <li> Fix a bug in "fossil patch create" that causes |
| 104 | 111 | [/help?cmd=revert|fossil revert] operations that happened |
| 105 | 112 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 106 | 113 | to be omitted from the patch. |
| 107 | 114 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 108 | 115 | aliases for remote checkout names. |
| 109 | 116 | </ol> |
| 110 | - * Enhancements to on-line help and the [/help?cmd=help|fossil help] command: | |
| 117 | + <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command: | |
| 111 | 118 | <ol type="a"> |
| 112 | 119 | <li> Add the ability to search the help text, either in the UI |
| 113 | 120 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 114 | 121 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 115 | 122 | <li> Accepts an optional SUBCOMMAND argument following the |
| @@ -116,27 +123,55 @@ | ||
| 116 | 123 | COMMAND argument and only shows results for the specified |
| 117 | 124 | subcommand, not the entire command. |
| 118 | 125 | <li> The -u (--usage) option shows only the command-line syntax |
| 119 | 126 | <li> The -o (--options) option shows only the command-line options |
| 120 | 127 | </ol> |
| 121 | - * Enhancements to the ticket system: | |
| 128 | + <li>Enhancements to the [./tickets.wiki|ticket system]: | |
| 122 | 129 | <ol type="a"> |
| 123 | 130 | <li> Added the ability to attach wiki pages to a ticket for extended |
| 124 | 131 | descriptions. |
| 125 | 132 | <li> Added submenu to the 'View Ticket' page, to use it as |
| 126 | 133 | template for a new ticket. |
| 127 | 134 | <li> Added button 'Submit and New' to create multiple tickets |
| 128 | 135 | in a row. |
| 136 | + <li> Link the version field in ticket view to a matching checkin or tag. | |
| 137 | + <li> Show creation time in report and ticket view. | |
| 138 | + <li> Show previous comments in edit ticket as reference. | |
| 129 | 139 | </ol> |
| 130 | - * Added the "hash" query parameter to the | |
| 140 | + <li>Added the "hash" query parameter to the | |
| 131 | 141 | [/help?cmd=/whatis|/whatis webpage]. |
| 132 | - * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] | |
| 142 | + <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] | |
| 133 | 143 | which alerts subscribers when an admin creates a new user or |
| 134 | 144 | when a user's permissions change. |
| 135 | - * Show project description on repository list. | |
| 136 | - * Diverse minor fixes and additions. | |
| 137 | - | |
| 145 | + <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains | |
| 146 | + the substring "description", then the project description for each repository | |
| 147 | + is shown on the repository list page. The login-group for each project is | |
| 148 | + now only shown if the FOSSIL_REPOLIST_SHOW environment variable exists and | |
| 149 | + contains the substring "login-group". ([./cgi.wiki#repolist|More information]) | |
| 150 | + <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved | |
| 151 | + security: | |
| 152 | + <ol type="a"> | |
| 153 | + <li> TH1 now makes a distinction between | |
| 154 | + [/doc/trunk/www/th1.md#taint|tainted and untainted string values]. | |
| 155 | + This makes it more difficult to write custom TH1 scripts that | |
| 156 | + contain XSS or SQL-injection bugs. The | |
| 157 | + [/help?cmd=vuln-report|vuln-report] setting was added to control | |
| 158 | + what Fossil does when it encounters a potential TH1 | |
| 159 | + security problem. | |
| 160 | + <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr] | |
| 161 | + command. | |
| 162 | + <li> The "enable_htmlify" TH1 command was removed. | |
| 163 | + </ol> | |
| 164 | + <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing | |
| 165 | + the frequency of reconnection attempts over time and providing feedback | |
| 166 | + to the user when the connection is down. | |
| 167 | + <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged | |
| 168 | + in, nor are links to that page displayed to users who are not logged in. Being | |
| 169 | + logged in as "anonymous" is sufficient to overcome this restriction, assuming | |
| 170 | + that "anonymous" can download tarballs and ZIP archives. | |
| 171 | + <li>Many other minor fixes and additions. | |
| 172 | +</ol> | |
| 138 | 173 | |
| 139 | 174 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 140 | 175 | |
| 141 | 176 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 142 | 177 | that have non-ASCII filenames |
| 143 | 178 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,43 +1,49 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_26'>Changes for version 2.26 (pending)</h2> |
| 4 | |
| 5 | * Enhancements to [/help?cmd=diff|fossil diff] and similar: |
| 6 | <ol type="a"> |
| 7 | <li> The --from can optionally accept a directory name as its argument, |
| 8 | and uses files under that directory as the baseline for the diff. |
| 9 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 10 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 11 | are available, or a --by diff if not. |
| 12 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 13 | diff up to date with the latest changes on disk. |
| 14 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | diffs of multiple files. |
| 16 | </ol> |
| 17 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 18 | about pending changes in a working check-out |
| 19 | * Enhancements to the [/help?cmd=ui|fossil ui] command: |
| 20 | <ol type="a"> |
| 21 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 22 | start page. Or, if the new "--from PATH" option is present, the |
| 23 | default start page becomes "/ckout?exbase=PATH". |
| 24 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 25 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 26 | person editing this change log has |
| 27 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 28 | press "Reload" on the web browser to view edits. |
| 29 | </ol> |
| 30 | * Enhancements to [/help?cmd=merge|fossil merge]: |
| 31 | <ol type="a"> |
| 32 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 33 | especially the --tk option to that command, to provide analysis |
| 34 | of the most recent merge or update operation. |
| 35 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 36 | text that shows Fossil's suggested resolution to the conflict. |
| 37 | </ol> |
| 38 | * Enhancements to [/help?cmd=commit|fossil commit]: |
| 39 | <ol type="a"> |
| 40 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 41 | in the check-in comment, it will alert the developer and give |
| 42 | him or her the opportunity to edit the comment before continuing. |
| 43 | This feature is controllable by the |
| @@ -47,18 +53,19 @@ | |
| 47 | <li> Added the ability to sign check-ins with SSH keys. |
| 48 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 49 | branch has been changed. |
| 50 | <li> The interactive checkin comment prompt shows the formatting rules |
| 51 | set for that repository. |
| 52 | </ol> |
| 53 | * Deprecate the --comfmtflags and --comment-format global options and |
| 54 | no longer list them in the built-in help, but keep them working for |
| 55 | backwards compatibility. |
| 56 | Alternative TTY comment formatting can still be specified using the |
| 57 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 58 | default comment format is now called "canonical", not "legacy". |
| 59 | * Enhancements to the [/help?cmd=/timeline|/timeline page]: |
| 60 | <ol type="a"> |
| 61 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 62 | like "rl=" ("Related List") but adds "mionly" style related |
| 63 | check-ins instead of the full "rel" style. |
| 64 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -81,35 +88,35 @@ | |
| 81 | <li> Accept the "Z" (Zulu-time) suffix on date arguments for the |
| 82 | "ymd" and "yw" query parameters. |
| 83 | <li> The new "min" query parameter, when added to a from=,to= query, |
| 84 | collapses long runs of check-ins on the same branch into just |
| 85 | end-points. |
| 86 | <li> The p= and d= parameters an reference different check-ins, which |
| 87 | case the timeline shows those check-ins that are both ancestors |
| 88 | of p= and descendants of d=. |
| 89 | <li> The saturation and intensity of user-specified checkin and branch |
| 90 | background colors are automatically adjusted to keep the colors |
| 91 | compatible with the current skin, unless the |
| 92 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 93 | </ol> |
| 94 | * The [/help?cmd=/docfile|/docfile webpage] was added. It works like |
| 95 | /doc but keeps the title of markdown documents with the document rather |
| 96 | that moving it up to the page title. |
| 97 | * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 98 | and debugging |
| 99 | * Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 100 | decoding of the artifact described by NAME. |
| 101 | * Improvements to the [/help?cmd=patch|fossil patch] command: |
| 102 | <ol type="a"> |
| 103 | <li> Fix a bug in "fossil patch create" that causes |
| 104 | [/help?cmd=revert|fossil revert] operations that happened |
| 105 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 106 | to be omitted from the patch. |
| 107 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 108 | aliases for remote checkout names. |
| 109 | </ol> |
| 110 | * Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 111 | <ol type="a"> |
| 112 | <li> Add the ability to search the help text, either in the UI |
| 113 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 114 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 115 | <li> Accepts an optional SUBCOMMAND argument following the |
| @@ -116,27 +123,55 @@ | |
| 116 | COMMAND argument and only shows results for the specified |
| 117 | subcommand, not the entire command. |
| 118 | <li> The -u (--usage) option shows only the command-line syntax |
| 119 | <li> The -o (--options) option shows only the command-line options |
| 120 | </ol> |
| 121 | * Enhancements to the ticket system: |
| 122 | <ol type="a"> |
| 123 | <li> Added the ability to attach wiki pages to a ticket for extended |
| 124 | descriptions. |
| 125 | <li> Added submenu to the 'View Ticket' page, to use it as |
| 126 | template for a new ticket. |
| 127 | <li> Added button 'Submit and New' to create multiple tickets |
| 128 | in a row. |
| 129 | </ol> |
| 130 | * Added the "hash" query parameter to the |
| 131 | [/help?cmd=/whatis|/whatis webpage]. |
| 132 | * Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] |
| 133 | which alerts subscribers when an admin creates a new user or |
| 134 | when a user's permissions change. |
| 135 | * Show project description on repository list. |
| 136 | * Diverse minor fixes and additions. |
| 137 | |
| 138 | |
| 139 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 140 | |
| 141 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 142 | that have non-ASCII filenames |
| 143 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -1,43 +1,49 @@ | |
| 1 | <title>Change Log</title> |
| 2 | |
| 3 | <h2 id='v2_27'>Changes for version 2.27 (pending)</h2> |
| 4 | |
| 5 | * <i>(pending)</i> |
| 6 | |
| 7 | <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol> |
| 8 | <li>Enhancements to [/help?cmd=diff|fossil diff] and similar: |
| 9 | <ol type="a"> |
| 10 | <li> The argument to the --from option can be a directory name, causing |
| 11 | Fossil to use files under that directory as the baseline for the diff. |
| 12 | <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting] |
| 13 | is defined, Fossil tries to do a --tk diff if "tclsh" and "wish" |
| 14 | are available, or a --by diff if not. |
| 15 | <li> The "Reload" button is added to --tk diffs, to bring the displayed |
| 16 | diff up to date with the latest changes on disk. |
| 17 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 18 | diffs of multiple files. |
| 19 | </ol> |
| 20 | <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 21 | about pending changes in a working check-out |
| 22 | <li>Enhancements to the [/help?cmd=ui|fossil ui] command: |
| 23 | <ol type="a"> |
| 24 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 25 | start page. Or, if the new "--from PATH" option is present, the |
| 26 | default start page becomes "/ckout?exbase=PATH". |
| 27 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 28 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 29 | person editing this change log has |
| 30 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 31 | press "Reload" on the web browser to view edits. |
| 32 | <li> Accept both IPv4 and IPv6 connections on all platforms, including |
| 33 | Windows and OpenBSD. This also applies to the "fossil server" |
| 34 | command. |
| 35 | </ol> |
| 36 | <li>Enhancements to [/help?cmd=merge|fossil merge]: |
| 37 | <ol type="a"> |
| 38 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 39 | especially the --tk option to that command, to provide analysis |
| 40 | of the most recent merge or update operation. |
| 41 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 42 | text that shows Fossil's suggested resolution to the conflict. |
| 43 | </ol> |
| 44 | <li>Enhancements to [/help?cmd=commit|fossil commit]: |
| 45 | <ol type="a"> |
| 46 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 47 | in the check-in comment, it will alert the developer and give |
| 48 | him or her the opportunity to edit the comment before continuing. |
| 49 | This feature is controllable by the |
| @@ -47,18 +53,19 @@ | |
| 53 | <li> Added the ability to sign check-ins with SSH keys. |
| 54 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 55 | branch has been changed. |
| 56 | <li> The interactive checkin comment prompt shows the formatting rules |
| 57 | set for that repository. |
| 58 | <li> Add the "--editor" option. |
| 59 | </ol> |
| 60 | <li>Deprecate the --comfmtflags and --comment-format global options and |
| 61 | no longer list them in the built-in help, but keep them working for |
| 62 | backwards compatibility. |
| 63 | Alternative TTY comment formatting can still be specified using the |
| 64 | [/help?cmd=comment-format|comment-format setting], if desired. The |
| 65 | default comment format is now called "canonical", not "legacy". |
| 66 | <li>Enhancements to the [/help?cmd=/timeline|/timeline page]: |
| 67 | <ol type="a"> |
| 68 | <li> Added the "ml=" ("Merge-in List") query parameter that works |
| 69 | like "rl=" ("Related List") but adds "mionly" style related |
| 70 | check-ins instead of the full "rel" style. |
| 71 | <li> For "tl=", "rl=", and "ml=", the order of the branches in the |
| @@ -81,35 +88,35 @@ | |
| 88 | <li> Accept the "Z" (Zulu-time) suffix on date arguments for the |
| 89 | "ymd" and "yw" query parameters. |
| 90 | <li> The new "min" query parameter, when added to a from=,to= query, |
| 91 | collapses long runs of check-ins on the same branch into just |
| 92 | end-points. |
| 93 | <li> The p= and d= parameters can now reference different check-ins, |
| 94 | in which case the timeline shows those check-ins that are both |
| 95 | ancestors of p= and descendants of d=. |
| 96 | <li> The saturation and intensity of user-specified checkin and branch |
| 97 | background colors are automatically adjusted to keep the colors |
| 98 | compatible with the current skin, unless the |
| 99 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. |
| 100 | </ol> |
| 101 | <li>The [/help?cmd=/docfile|/docfile webpage] was added. It works like |
| 102 | /doc but keeps the title of markdown documents with the document rather |
| 103 | that moving it up to the page title. |
| 104 | <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 105 | and debugging |
| 106 | <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 107 | decoding of the artifact described by NAME. |
| 108 | <li>Improvements to the [/help?cmd=patch|fossil patch] command: |
| 109 | <ol type="a"> |
| 110 | <li> Fix a bug in "fossil patch create" that causes |
| 111 | [/help?cmd=revert|fossil revert] operations that happened |
| 112 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 113 | to be omitted from the patch. |
| 114 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 115 | aliases for remote checkout names. |
| 116 | </ol> |
| 117 | <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 118 | <ol type="a"> |
| 119 | <li> Add the ability to search the help text, either in the UI |
| 120 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 121 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| 122 | <li> Accepts an optional SUBCOMMAND argument following the |
| @@ -116,27 +123,55 @@ | |
| 123 | COMMAND argument and only shows results for the specified |
| 124 | subcommand, not the entire command. |
| 125 | <li> The -u (--usage) option shows only the command-line syntax |
| 126 | <li> The -o (--options) option shows only the command-line options |
| 127 | </ol> |
| 128 | <li>Enhancements to the [./tickets.wiki|ticket system]: |
| 129 | <ol type="a"> |
| 130 | <li> Added the ability to attach wiki pages to a ticket for extended |
| 131 | descriptions. |
| 132 | <li> Added submenu to the 'View Ticket' page, to use it as |
| 133 | template for a new ticket. |
| 134 | <li> Added button 'Submit and New' to create multiple tickets |
| 135 | in a row. |
| 136 | <li> Link the version field in ticket view to a matching checkin or tag. |
| 137 | <li> Show creation time in report and ticket view. |
| 138 | <li> Show previous comments in edit ticket as reference. |
| 139 | </ol> |
| 140 | <li>Added the "hash" query parameter to the |
| 141 | [/help?cmd=/whatis|/whatis webpage]. |
| 142 | <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription] |
| 143 | which alerts subscribers when an admin creates a new user or |
| 144 | when a user's permissions change. |
| 145 | <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains |
| 146 | the substring "description", then the project description for each repository |
| 147 | is shown on the repository list page. The login-group for each project is |
| 148 | now only shown if the FOSSIL_REPOLIST_SHOW environment variable exists and |
| 149 | contains the substring "login-group". ([./cgi.wiki#repolist|More information]) |
| 150 | <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved |
| 151 | security: |
| 152 | <ol type="a"> |
| 153 | <li> TH1 now makes a distinction between |
| 154 | [/doc/trunk/www/th1.md#taint|tainted and untainted string values]. |
| 155 | This makes it more difficult to write custom TH1 scripts that |
| 156 | contain XSS or SQL-injection bugs. The |
| 157 | [/help?cmd=vuln-report|vuln-report] setting was added to control |
| 158 | what Fossil does when it encounters a potential TH1 |
| 159 | security problem. |
| 160 | <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr] |
| 161 | command. |
| 162 | <li> The "enable_htmlify" TH1 command was removed. |
| 163 | </ol> |
| 164 | <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing |
| 165 | the frequency of reconnection attempts over time and providing feedback |
| 166 | to the user when the connection is down. |
| 167 | <li>The [/help?cmd=/sqlar|/sqlar] page does not work for users who are not logged |
| 168 | in, nor are links to that page displayed to users who are not logged in. Being |
| 169 | logged in as "anonymous" is sufficient to overcome this restriction, assuming |
| 170 | that "anonymous" can download tarballs and ZIP archives. |
| 171 | <li>Many other minor fixes and additions. |
| 172 | </ol> |
| 173 | |
| 174 | <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2> |
| 175 | |
| 176 | * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories |
| 177 | that have non-ASCII filenames |
| 178 |
+37
-1
| --- www/customskin.md | ||
| +++ www/customskin.md | ||
| @@ -310,10 +310,11 @@ | ||
| 310 | 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | 311 | option contains a "/" character, then the five control files are |
| 312 | 312 | read out of the directory named. You can then edit the control |
| 313 | 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | 314 | press "Reload" on your browser to see the effects. |
| 315 | + | |
| 315 | 316 | |
| 316 | 317 | ### Disabling The Web Browser Cache During Development |
| 317 | 318 | |
| 318 | 319 | Fossil is aggressive about asking the web browser to cache |
| 319 | 320 | resources. While developing a new skin, it is often helpful to |
| @@ -526,11 +527,46 @@ | ||
| 526 | 527 | Iterate until the desired look is achieved. |
| 527 | 528 | |
| 528 | 529 | 4. Copy/paste the resulting css.txt, details.txt, |
| 529 | 530 | header.txt, and footer.txt files |
| 530 | 531 | into the CSS, details, header, and footer configuration screens |
| 531 | - under the Admin/Skins menu. | |
| 532 | + under the Admin/Skins menu. Alternately, import them using the | |
| 533 | + process described below. | |
| 534 | + | |
| 535 | +An alternative to step 4 is to convert the skin files into a form | |
| 536 | +which can be imported into a repository using `fossil config import`. | |
| 537 | +It requires compiling [a small tool from the fossil source | |
| 538 | +tree](/file/tools/skintxt2config.c): | |
| 539 | + | |
| 540 | +> | |
| 541 | +``` | |
| 542 | +$ cc -o s2c /path/to/fossil/checkout/tools/skintxt2config.c | |
| 543 | +``` | |
| 544 | + | |
| 545 | +With that in place, the custom skin files can be converted with: | |
| 546 | + | |
| 547 | +> | |
| 548 | +``` | |
| 549 | +$ ./s2c yourskin/*.txt > skin.config | |
| 550 | +``` | |
| 551 | + | |
| 552 | +It can be imported into an arbitrary fossil repository with: | |
| 553 | + | |
| 554 | +> | |
| 555 | +``` | |
| 556 | +$ fossil config import skin.config | |
| 557 | +``` | |
| 558 | + | |
| 559 | +And it can be pushed to a remote repository with: | |
| 560 | + | |
| 561 | +> | |
| 562 | +``` | |
| 563 | +$ fossil config push skin | |
| 564 | +``` | |
| 565 | + | |
| 566 | +That approach has proven to be an effective way to locally develop | |
| 567 | +skin changes then push them to a "live" site. | |
| 532 | 568 | |
| 533 | 569 | |
| 534 | 570 | ## See Also |
| 535 | 571 | |
| 536 | 572 | * [Customizing the Timeline Graph](customgraph.md) |
| 537 | 573 |
| --- www/customskin.md | |
| +++ www/customskin.md | |
| @@ -310,10 +310,11 @@ | |
| 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | option contains a "/" character, then the five control files are |
| 312 | read out of the directory named. You can then edit the control |
| 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | press "Reload" on your browser to see the effects. |
| 315 | |
| 316 | ### Disabling The Web Browser Cache During Development |
| 317 | |
| 318 | Fossil is aggressive about asking the web browser to cache |
| 319 | resources. While developing a new skin, it is often helpful to |
| @@ -526,11 +527,46 @@ | |
| 526 | Iterate until the desired look is achieved. |
| 527 | |
| 528 | 4. Copy/paste the resulting css.txt, details.txt, |
| 529 | header.txt, and footer.txt files |
| 530 | into the CSS, details, header, and footer configuration screens |
| 531 | under the Admin/Skins menu. |
| 532 | |
| 533 | |
| 534 | ## See Also |
| 535 | |
| 536 | * [Customizing the Timeline Graph](customgraph.md) |
| 537 |
| --- www/customskin.md | |
| +++ www/customskin.md | |
| @@ -310,10 +310,11 @@ | |
| 310 | with the "--skin ./newskin" option. If the argument to the --skin |
| 311 | option contains a "/" character, then the five control files are |
| 312 | read out of the directory named. You can then edit the control |
| 313 | files in the ./newskin folder using you favorite text editor, and |
| 314 | press "Reload" on your browser to see the effects. |
| 315 | |
| 316 | |
| 317 | ### Disabling The Web Browser Cache During Development |
| 318 | |
| 319 | Fossil is aggressive about asking the web browser to cache |
| 320 | resources. While developing a new skin, it is often helpful to |
| @@ -526,11 +527,46 @@ | |
| 527 | Iterate until the desired look is achieved. |
| 528 | |
| 529 | 4. Copy/paste the resulting css.txt, details.txt, |
| 530 | header.txt, and footer.txt files |
| 531 | into the CSS, details, header, and footer configuration screens |
| 532 | under the Admin/Skins menu. Alternately, import them using the |
| 533 | process described below. |
| 534 | |
| 535 | An alternative to step 4 is to convert the skin files into a form |
| 536 | which can be imported into a repository using `fossil config import`. |
| 537 | It requires compiling [a small tool from the fossil source |
| 538 | tree](/file/tools/skintxt2config.c): |
| 539 | |
| 540 | > |
| 541 | ``` |
| 542 | $ cc -o s2c /path/to/fossil/checkout/tools/skintxt2config.c |
| 543 | ``` |
| 544 | |
| 545 | With that in place, the custom skin files can be converted with: |
| 546 | |
| 547 | > |
| 548 | ``` |
| 549 | $ ./s2c yourskin/*.txt > skin.config |
| 550 | ``` |
| 551 | |
| 552 | It can be imported into an arbitrary fossil repository with: |
| 553 | |
| 554 | > |
| 555 | ``` |
| 556 | $ fossil config import skin.config |
| 557 | ``` |
| 558 | |
| 559 | And it can be pushed to a remote repository with: |
| 560 | |
| 561 | > |
| 562 | ``` |
| 563 | $ fossil config push skin |
| 564 | ``` |
| 565 | |
| 566 | That approach has proven to be an effective way to locally develop |
| 567 | skin changes then push them to a "live" site. |
| 568 | |
| 569 | |
| 570 | ## See Also |
| 571 | |
| 572 | * [Customizing the Timeline Graph](customgraph.md) |
| 573 |
+6
| --- www/env-opts.md | ||
| +++ www/env-opts.md | ||
| @@ -153,10 +153,16 @@ | ||
| 153 | 153 | |
| 154 | 154 | `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page |
| 155 | 155 | loaded by the `fossil all ui` or `fossil ui /` commands. Only used if |
| 156 | 156 | none of the listed repositories has the `repolist_skin` property set. |
| 157 | 157 | Can be set from the [CGI control file][cgictlfile]. |
| 158 | + | |
| 159 | +`FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value | |
| 160 | +that contains the substring "description", then the "Project Description" | |
| 161 | +column appears on the repolist page. If it contains the substring | |
| 162 | +"login-group", then the Login-Group column appears on the repolist page. | |
| 163 | +Can be set from the [CGI control file][cgictlfile]. | |
| 158 | 164 | |
| 159 | 165 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 160 | 166 | SEE as text to be hashed into the actual encryption key. This has no |
| 161 | 167 | effect if Fossil was not compiled with SEE support enabled. |
| 162 | 168 | |
| 163 | 169 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -153,10 +153,16 @@ | |
| 153 | |
| 154 | `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page |
| 155 | loaded by the `fossil all ui` or `fossil ui /` commands. Only used if |
| 156 | none of the listed repositories has the `repolist_skin` property set. |
| 157 | Can be set from the [CGI control file][cgictlfile]. |
| 158 | |
| 159 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 160 | SEE as text to be hashed into the actual encryption key. This has no |
| 161 | effect if Fossil was not compiled with SEE support enabled. |
| 162 | |
| 163 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -153,10 +153,16 @@ | |
| 153 | |
| 154 | `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page |
| 155 | loaded by the `fossil all ui` or `fossil ui /` commands. Only used if |
| 156 | none of the listed repositories has the `repolist_skin` property set. |
| 157 | Can be set from the [CGI control file][cgictlfile]. |
| 158 | |
| 159 | `FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value |
| 160 | that contains the substring "description", then the "Project Description" |
| 161 | column appears on the repolist page. If it contains the substring |
| 162 | "login-group", then the Login-Group column appears on the repolist page. |
| 163 | Can be set from the [CGI control file][cgictlfile]. |
| 164 | |
| 165 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 166 | SEE as text to be hashed into the actual encryption key. This has no |
| 167 | effect if Fossil was not compiled with SEE support enabled. |
| 168 | |
| 169 |
+4
-4
| --- www/index.wiki | ||
| +++ www/index.wiki | ||
| @@ -84,16 +84,16 @@ | ||
| 84 | 84 | the repository are consistent prior to each commit. |
| 85 | 85 | |
| 86 | 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | 87 | |
| 88 | 88 | <hr> |
| 89 | -<h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3> | |
| 89 | +<h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3> | |
| 90 | 90 | |
| 91 | 91 | * [/uv/download.html|Download] |
| 92 | - * [./changes.wiki#v2_25|Change Summary] | |
| 93 | - * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25] | |
| 94 | - * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release] | |
| 92 | + * [./changes.wiki#v2_26|Change Summary] | |
| 93 | + * [/timeline?p=version-2.26&bt=version-2.25&y=ci|Check-ins in version 2.26] | |
| 94 | + * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release] | |
| 95 | 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | 96 | |
| 97 | 97 | <hr> |
| 98 | 98 | <h3>Quick Start</h3> |
| 99 | 99 | |
| 100 | 100 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -84,16 +84,16 @@ | |
| 84 | the repository are consistent prior to each commit. |
| 85 | |
| 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | |
| 88 | <hr> |
| 89 | <h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_25|Change Summary] |
| 93 | * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25] |
| 94 | * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
| --- www/index.wiki | |
| +++ www/index.wiki | |
| @@ -84,16 +84,16 @@ | |
| 84 | the repository are consistent prior to each commit. |
| 85 | |
| 86 | 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license]. |
| 87 | |
| 88 | <hr> |
| 89 | <h3>Latest Release: 2.26 ([/timeline?c=version-2.26|2025-04-30])</h3> |
| 90 | |
| 91 | * [/uv/download.html|Download] |
| 92 | * [./changes.wiki#v2_26|Change Summary] |
| 93 | * [/timeline?p=version-2.26&bt=version-2.25&y=ci|Check-ins in version 2.26] |
| 94 | * [/timeline?df=version-2.26&y=ci|Check-ins derived from the 2.26 release] |
| 95 | * [/timeline?t=release|Timeline of all past releases] |
| 96 | |
| 97 | <hr> |
| 98 | <h3>Quick Start</h3> |
| 99 | |
| 100 |
+75
-65
| --- www/quickstart.wiki | ||
| +++ www/quickstart.wiki | ||
| @@ -19,15 +19,13 @@ | ||
| 19 | 19 | This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC |
| 20 | 20 | </b></pre> |
| 21 | 21 | |
| 22 | 22 | <h2 id="workflow" name="fslclone">General Work Flow</h2> |
| 23 | 23 | |
| 24 | -Fossil works with repository files (a database in a single file with the project's | |
| 25 | -complete history) and with checked-out local trees (the working directory | |
| 26 | -you use to do your work). | |
| 27 | -(See [./glossary.md | the glossary] for more background.) | |
| 28 | -The workflow looks like this: | |
| 24 | +Fossil works with [./glossary.md#repository | repository files] | |
| 25 | +and [./glossary.md#check-out | check-out directories] using a | |
| 26 | +workflow like this: | |
| 29 | 27 | |
| 30 | 28 | <ul> |
| 31 | 29 | <li>Create or clone a repository file. ([/help/init|fossil init] or |
| 32 | 30 | [/help/clone | fossil clone]) |
| 33 | 31 | <li>Check out a local tree. ([/help/open | fossil open]) |
| @@ -41,12 +39,11 @@ | ||
| 41 | 39 | The following sections give a brief overview of these |
| 42 | 40 | operations. |
| 43 | 41 | |
| 44 | 42 | <h2 id="new">Starting A New Project</h2> |
| 45 | 43 | |
| 46 | -To start a new project with fossil create a new empty repository | |
| 47 | -this way: ([/help/init | more info]) | |
| 44 | +To start a new project with Fossil, [/help/init | create a new empty repository]: | |
| 48 | 45 | |
| 49 | 46 | <pre><b>fossil init</b> <i>repository-filename</i> |
| 50 | 47 | </pre> |
| 51 | 48 | |
| 52 | 49 | You can name the database anything you like, and you can place it anywhere in the filesystem. |
| @@ -82,14 +79,14 @@ | ||
| 82 | 79 | <h2 id="clone">Cloning An Existing Repository</h2> |
| 83 | 80 | |
| 84 | 81 | Most fossil operations interact with a repository that is on the |
| 85 | 82 | local disk drive, not on a remote system. Hence, before accessing |
| 86 | 83 | a remote repository it is necessary to make a local copy of that |
| 87 | -repository. Making a local copy of a remote repository is called | |
| 88 | -"cloning". | |
| 84 | +repository, a process called | |
| 85 | +"[/help/clone | cloning]". | |
| 89 | 86 | |
| 90 | -Clone a remote repository as follows: ([/help/clone | more info]) | |
| 87 | +This is done as follows: | |
| 91 | 88 | |
| 92 | 89 | <pre><b>fossil clone</b> <i>URL repository-filename</i> |
| 93 | 90 | </pre> |
| 94 | 91 | |
| 95 | 92 | The <i>URL</i> specifies the fossil repository |
| @@ -107,12 +104,20 @@ | ||
| 107 | 104 | 100% complete... |
| 108 | 105 | Extra delta compression... |
| 109 | 106 | Vacuuming the database... |
| 110 | 107 | project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 |
| 111 | 108 | server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 |
| 112 | -admin-user: exampleuser (password is "yoWgDR42iv")> | |
| 109 | +admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")> | |
| 113 | 110 | </b></pre> |
| 111 | + | |
| 112 | +This <i>exampleuser</i> will be used by Fossil as the author of commits when | |
| 113 | +you checkin changes to the repository. It is also used by Fossil when you | |
| 114 | +make your repository available to others using the built-in server mode by | |
| 115 | +running <tt>[/help/server | fossil server]</tt> and will also be used when | |
| 116 | +running <tt>[/help/ui | fossil ui]</tt> to view the repository through | |
| 117 | +the Fossil UI. See the quick start topic for setting up a | |
| 118 | +<a href="#server">server</a> for more details. | |
| 114 | 119 | |
| 115 | 120 | If the remote repository requires a login, include a |
| 116 | 121 | userid in the URL like this: |
| 117 | 122 | |
| 118 | 123 | <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre> |
| @@ -153,26 +158,23 @@ | ||
| 153 | 158 | |
| 154 | 159 | <h2 id="checkout">Checking Out A Local Tree</h2> |
| 155 | 160 | |
| 156 | 161 | To work on a project in fossil, you need to check out a local |
| 157 | 162 | copy of the source tree. Create the directory you want to be |
| 158 | -the root of your tree and cd into that directory. Then | |
| 159 | -do this: ([/help/open | more info]) | |
| 163 | +the root of your tree, <tt>cd</tt> into that directory, and then: | |
| 160 | 164 | |
| 161 | 165 | <pre><b>fossil open</b> <i>repository-filename</i></pre> |
| 162 | 166 | |
| 163 | -for example: | |
| 167 | +For example: | |
| 164 | 168 | |
| 165 | 169 | <pre><b>fossil open ../myclone.fossil |
| 166 | 170 | BUILD.txt |
| 167 | 171 | COPYRIGHT-BSD2.txt |
| 168 | 172 | README.md |
| 169 | 173 | ︙ |
| 170 | 174 | </tt></b></pre> |
| 171 | 175 | |
| 172 | -(or "fossil open ..\myclone.fossil" on Windows). | |
| 173 | - | |
| 174 | 176 | This leaves you with the newest version of the tree |
| 175 | 177 | checked out. |
| 176 | 178 | From anywhere underneath the root of your local tree, you |
| 177 | 179 | can type commands like the following to find out the status of |
| 178 | 180 | your local tree: |
| @@ -320,41 +322,60 @@ | ||
| 320 | 322 | |
| 321 | 323 | This will get you started on identifying checkins. The |
| 322 | 324 | <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including |
| 323 | 325 | how timestamps can also be used. |
| 324 | 326 | |
| 325 | -<h2 id="config">Configuring Your Local Repository</h2> | |
| 327 | +<h2 id="config">Accessing Your Local Repository's Web User Interface</h2> | |
| 326 | 328 | |
| 327 | -When you create a new repository, either by cloning an existing | |
| 328 | -project or create a new project of your own, you usually want to do some | |
| 329 | -local configuration. This is easily accomplished using the web-server | |
| 330 | -that is built into fossil. Start the fossil web server like this: | |
| 331 | -([/help/ui | more info]) | |
| 329 | +After you create a new repository, you usually want to do some local | |
| 330 | +configuration. This is most easily accomplished by firing up the Fossil | |
| 331 | +UI: | |
| 332 | 332 | |
| 333 | 333 | <pre> |
| 334 | 334 | <b>fossil ui</b> <i>repository-filename</i> |
| 335 | 335 | </pre> |
| 336 | 336 | |
| 337 | -You can omit the <i>repository-filename</i> from the command above | |
| 337 | +You can shorten that to just [/help/ui | <b>fossil ui</b>] | |
| 338 | 338 | if you are inside a checked-out local tree. |
| 339 | 339 | |
| 340 | -This starts a web server then automatically launches your | |
| 341 | -web browser and makes it point to this web server. If your system | |
| 342 | -has an unusual configuration, fossil might not be able to figure out | |
| 343 | -how to start your web browser. In that case, first tell fossil | |
| 344 | -where to find your web browser using a command like this: | |
| 340 | +This command starts an internal web server, after which Fossil | |
| 341 | +automatically launches your default browser, pointed at itself, | |
| 342 | +presenting a special view of the repository, its web user interface. | |
| 343 | + | |
| 344 | +You may override Fossil's logic for selecting the default browser so: | |
| 345 | 345 | |
| 346 | 346 | <pre> |
| 347 | 347 | <b>fossil setting web-browser</b> <i>path-to-web-browser</i> |
| 348 | 348 | </pre> |
| 349 | 349 | |
| 350 | -By default, fossil does not require a login for HTTP connections | |
| 351 | -coming in from the IP loopback address 127.0.0.1. You can, and perhaps | |
| 352 | -should, change this after you create a few users. | |
| 350 | +When launched this way, Fossil binds its internal web server to the IP | |
| 351 | +loopback address, 127.0.0.1, which it treats specially, bypassing all | |
| 352 | +user controls, effectively giving visitors the | |
| 353 | +[./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity]. | |
| 354 | + | |
| 355 | +Why is that a good idea, you ask? Because it is a safe | |
| 356 | +presumption that only someone with direct file access to the repository | |
| 357 | +database file could be using the resulting web interface. Anyone who can | |
| 358 | +modify the repo DB directly could give themselves any and all access | |
| 359 | +with a SQL query, or even by direct file manipulation; no amount of | |
| 360 | +access control matters to such a user. | |
| 361 | + | |
| 362 | +(Contrast the [#server | many <i>other</i> ways] of setting Fossil up | |
| 363 | +as an HTTP server, where the repo DB is on the other side of the HTTP | |
| 364 | +server wall, inaccessible by all means other than Fossil's own | |
| 365 | +mediation. For this reason, the "localhost bypasses access control" | |
| 366 | +policy does <i>not</i> apply to these other interfaces. That is a very | |
| 367 | +good thing, since without this difference in policy, it would be unsafe | |
| 368 | +to bind a [/help?cmd=server | <b>fossil server</b>] instance to | |
| 369 | +localhost on a high-numbered port and then reverse-proxy it out to the | |
| 370 | +world via HTTPS, a practice this author does engage in, with confidence.) | |
| 353 | 371 | |
| 354 | -When you are finished configuring, just press Control-C or use | |
| 355 | -the <b>kill</b> command to shut down the mini-server. | |
| 372 | +Once you are finished configuring Fossil, you may safely Control-C out | |
| 373 | +of the <b>fossil ui</b> command to shut down this privileged | |
| 374 | +built-in web server. Moreover, you may by grace of SQLite do this <i>at | |
| 375 | +any time</i>: all changes are either committed durably to the repo DB or | |
| 376 | +rolled back, in their totality. This includes configuration changes. | |
| 356 | 377 | |
| 357 | 378 | <h2 id="sharing">Sharing Changes</h2> |
| 358 | 379 | |
| 359 | 380 | When [./concepts.wiki#workflow|autosync] is turned off, |
| 360 | 381 | the changes you [/help/commit | commit] are only |
| @@ -464,55 +485,44 @@ | ||
| 464 | 485 | level of undo/redo. |
| 465 | 486 | |
| 466 | 487 | |
| 467 | 488 | <h2 id="server">Setting Up A Server</h2> |
| 468 | 489 | |
| 469 | -Fossil can act as a stand-alone web server using one of these | |
| 470 | -commands: | |
| 490 | +In addition to the inward-facing <b>fossil ui</b> mode covered [#config | |
| 491 | +| above], Fossil can also act as an outward-facing web server: | |
| 471 | 492 | |
| 472 | 493 | <pre> |
| 473 | 494 | <b>[/help/server | fossil server]</b> <i>repository-filename</i> |
| 474 | -<b>[/help/ui | fossil ui]</b> <i>repository-filename</i> | |
| 475 | 495 | </pre> |
| 476 | 496 | |
| 477 | -The <i>repository-filename</i> can be omitted when these commands | |
| 478 | -are run from within an open check-out, which is a particularly useful | |
| 479 | -shortcut with the <b>fossil ui</b> command. | |
| 480 | - | |
| 481 | -The <b>ui</b> command is intended for accessing the web user interface | |
| 482 | -from a local desktop. (We sometimes call this mode "Fossil UI.") | |
| 483 | -The <b>ui</b> command differs from the | |
| 484 | -<b>server</b> command by binding to the loopback IP | |
| 485 | -address only (thus making the web UI visible only on the | |
| 486 | -local machine) and by automatically starting your default web browser, | |
| 487 | -pointing it at the running UI | |
| 488 | -server. The localhost restriction exists because it also gives anyone | |
| 489 | -who can access the resulting web UI full control over the | |
| 490 | -repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful | |
| 491 | -Setup capabliity].) | |
| 492 | - | |
| 493 | -For cross-machine collaboration, use the <b>server</b> command instead, | |
| 494 | -which binds on all IP addresses, does not try to start a web browser, | |
| 495 | -and enforces [./caps/ | Fossil's role-based access control system]. | |
| 496 | - | |
| 497 | -Servers are also easily configured as: | |
| 497 | +Just as with <b>fossil ui</b>, you may omit the | |
| 498 | +<i>repository-filename</i> parameter when running this from within an open | |
| 499 | +check-out. | |
| 500 | + | |
| 501 | +<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network | |
| 502 | +interfaces by default in this mode, and it enforces the configured | |
| 503 | +[./caps/ | role-based access controls]. Further, because it is meant to | |
| 504 | +provide external web service, it doesn't try to launch a local web | |
| 505 | +browser pointing to a "Fossil UI" presentation; external visitors see | |
| 506 | +your repository's configured home page instead. | |
| 507 | + | |
| 508 | +To serve varying needs, there are additional ways to serve a Fossil repo | |
| 509 | +to external users: | |
| 498 | 510 | |
| 499 | 511 | <ul> |
| 512 | +<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki | | |
| 513 | + self-hosting repositories] | |
| 514 | +<li>[./server/any/scgi.md|SCGI] | |
| 500 | 515 | <li>[./server/any/inetd.md|inetd] |
| 501 | 516 | <li>[./server/debian/service.md|systemd] |
| 502 | -<li>[./server/any/cgi.md|CGI] | |
| 503 | -<li>[./server/any/scgi.md|SCGI] | |
| 504 | 517 | </ul> |
| 505 | 518 | |
| 506 | 519 | …along with [./server/#matrix | several other options]. |
| 507 | 520 | |
| 508 | -The [./selfhost.wiki | self-hosting fossil repositories] use | |
| 509 | -CGI. | |
| 510 | - | |
| 511 | -You might <i>need</i> to set up a server, whether you know it yet or | |
| 512 | -not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server] | |
| 513 | -article for details. | |
| 521 | +We recommend that you read the [./server/whyuseaserver.wiki | Benefits | |
| 522 | +of a Fossil Server] article, because you might <i>need</i> to do this | |
| 523 | +and not yet know it. | |
| 514 | 524 | |
| 515 | 525 | <h2 id="proxy">HTTP Proxies</h2> |
| 516 | 526 | |
| 517 | 527 | If you are behind a restrictive firewall that requires you to use |
| 518 | 528 | an HTTP proxy to reach the internet, then you can configure the proxy |
| 519 | 529 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -19,15 +19,13 @@ | |
| 19 | This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC |
| 20 | </b></pre> |
| 21 | |
| 22 | <h2 id="workflow" name="fslclone">General Work Flow</h2> |
| 23 | |
| 24 | Fossil works with repository files (a database in a single file with the project's |
| 25 | complete history) and with checked-out local trees (the working directory |
| 26 | you use to do your work). |
| 27 | (See [./glossary.md | the glossary] for more background.) |
| 28 | The workflow looks like this: |
| 29 | |
| 30 | <ul> |
| 31 | <li>Create or clone a repository file. ([/help/init|fossil init] or |
| 32 | [/help/clone | fossil clone]) |
| 33 | <li>Check out a local tree. ([/help/open | fossil open]) |
| @@ -41,12 +39,11 @@ | |
| 41 | The following sections give a brief overview of these |
| 42 | operations. |
| 43 | |
| 44 | <h2 id="new">Starting A New Project</h2> |
| 45 | |
| 46 | To start a new project with fossil create a new empty repository |
| 47 | this way: ([/help/init | more info]) |
| 48 | |
| 49 | <pre><b>fossil init</b> <i>repository-filename</i> |
| 50 | </pre> |
| 51 | |
| 52 | You can name the database anything you like, and you can place it anywhere in the filesystem. |
| @@ -82,14 +79,14 @@ | |
| 82 | <h2 id="clone">Cloning An Existing Repository</h2> |
| 83 | |
| 84 | Most fossil operations interact with a repository that is on the |
| 85 | local disk drive, not on a remote system. Hence, before accessing |
| 86 | a remote repository it is necessary to make a local copy of that |
| 87 | repository. Making a local copy of a remote repository is called |
| 88 | "cloning". |
| 89 | |
| 90 | Clone a remote repository as follows: ([/help/clone | more info]) |
| 91 | |
| 92 | <pre><b>fossil clone</b> <i>URL repository-filename</i> |
| 93 | </pre> |
| 94 | |
| 95 | The <i>URL</i> specifies the fossil repository |
| @@ -107,12 +104,20 @@ | |
| 107 | 100% complete... |
| 108 | Extra delta compression... |
| 109 | Vacuuming the database... |
| 110 | project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 |
| 111 | server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 |
| 112 | admin-user: exampleuser (password is "yoWgDR42iv")> |
| 113 | </b></pre> |
| 114 | |
| 115 | If the remote repository requires a login, include a |
| 116 | userid in the URL like this: |
| 117 | |
| 118 | <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre> |
| @@ -153,26 +158,23 @@ | |
| 153 | |
| 154 | <h2 id="checkout">Checking Out A Local Tree</h2> |
| 155 | |
| 156 | To work on a project in fossil, you need to check out a local |
| 157 | copy of the source tree. Create the directory you want to be |
| 158 | the root of your tree and cd into that directory. Then |
| 159 | do this: ([/help/open | more info]) |
| 160 | |
| 161 | <pre><b>fossil open</b> <i>repository-filename</i></pre> |
| 162 | |
| 163 | for example: |
| 164 | |
| 165 | <pre><b>fossil open ../myclone.fossil |
| 166 | BUILD.txt |
| 167 | COPYRIGHT-BSD2.txt |
| 168 | README.md |
| 169 | ︙ |
| 170 | </tt></b></pre> |
| 171 | |
| 172 | (or "fossil open ..\myclone.fossil" on Windows). |
| 173 | |
| 174 | This leaves you with the newest version of the tree |
| 175 | checked out. |
| 176 | From anywhere underneath the root of your local tree, you |
| 177 | can type commands like the following to find out the status of |
| 178 | your local tree: |
| @@ -320,41 +322,60 @@ | |
| 320 | |
| 321 | This will get you started on identifying checkins. The |
| 322 | <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including |
| 323 | how timestamps can also be used. |
| 324 | |
| 325 | <h2 id="config">Configuring Your Local Repository</h2> |
| 326 | |
| 327 | When you create a new repository, either by cloning an existing |
| 328 | project or create a new project of your own, you usually want to do some |
| 329 | local configuration. This is easily accomplished using the web-server |
| 330 | that is built into fossil. Start the fossil web server like this: |
| 331 | ([/help/ui | more info]) |
| 332 | |
| 333 | <pre> |
| 334 | <b>fossil ui</b> <i>repository-filename</i> |
| 335 | </pre> |
| 336 | |
| 337 | You can omit the <i>repository-filename</i> from the command above |
| 338 | if you are inside a checked-out local tree. |
| 339 | |
| 340 | This starts a web server then automatically launches your |
| 341 | web browser and makes it point to this web server. If your system |
| 342 | has an unusual configuration, fossil might not be able to figure out |
| 343 | how to start your web browser. In that case, first tell fossil |
| 344 | where to find your web browser using a command like this: |
| 345 | |
| 346 | <pre> |
| 347 | <b>fossil setting web-browser</b> <i>path-to-web-browser</i> |
| 348 | </pre> |
| 349 | |
| 350 | By default, fossil does not require a login for HTTP connections |
| 351 | coming in from the IP loopback address 127.0.0.1. You can, and perhaps |
| 352 | should, change this after you create a few users. |
| 353 | |
| 354 | When you are finished configuring, just press Control-C or use |
| 355 | the <b>kill</b> command to shut down the mini-server. |
| 356 | |
| 357 | <h2 id="sharing">Sharing Changes</h2> |
| 358 | |
| 359 | When [./concepts.wiki#workflow|autosync] is turned off, |
| 360 | the changes you [/help/commit | commit] are only |
| @@ -464,55 +485,44 @@ | |
| 464 | level of undo/redo. |
| 465 | |
| 466 | |
| 467 | <h2 id="server">Setting Up A Server</h2> |
| 468 | |
| 469 | Fossil can act as a stand-alone web server using one of these |
| 470 | commands: |
| 471 | |
| 472 | <pre> |
| 473 | <b>[/help/server | fossil server]</b> <i>repository-filename</i> |
| 474 | <b>[/help/ui | fossil ui]</b> <i>repository-filename</i> |
| 475 | </pre> |
| 476 | |
| 477 | The <i>repository-filename</i> can be omitted when these commands |
| 478 | are run from within an open check-out, which is a particularly useful |
| 479 | shortcut with the <b>fossil ui</b> command. |
| 480 | |
| 481 | The <b>ui</b> command is intended for accessing the web user interface |
| 482 | from a local desktop. (We sometimes call this mode "Fossil UI.") |
| 483 | The <b>ui</b> command differs from the |
| 484 | <b>server</b> command by binding to the loopback IP |
| 485 | address only (thus making the web UI visible only on the |
| 486 | local machine) and by automatically starting your default web browser, |
| 487 | pointing it at the running UI |
| 488 | server. The localhost restriction exists because it also gives anyone |
| 489 | who can access the resulting web UI full control over the |
| 490 | repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful |
| 491 | Setup capabliity].) |
| 492 | |
| 493 | For cross-machine collaboration, use the <b>server</b> command instead, |
| 494 | which binds on all IP addresses, does not try to start a web browser, |
| 495 | and enforces [./caps/ | Fossil's role-based access control system]. |
| 496 | |
| 497 | Servers are also easily configured as: |
| 498 | |
| 499 | <ul> |
| 500 | <li>[./server/any/inetd.md|inetd] |
| 501 | <li>[./server/debian/service.md|systemd] |
| 502 | <li>[./server/any/cgi.md|CGI] |
| 503 | <li>[./server/any/scgi.md|SCGI] |
| 504 | </ul> |
| 505 | |
| 506 | …along with [./server/#matrix | several other options]. |
| 507 | |
| 508 | The [./selfhost.wiki | self-hosting fossil repositories] use |
| 509 | CGI. |
| 510 | |
| 511 | You might <i>need</i> to set up a server, whether you know it yet or |
| 512 | not. See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server] |
| 513 | article for details. |
| 514 | |
| 515 | <h2 id="proxy">HTTP Proxies</h2> |
| 516 | |
| 517 | If you are behind a restrictive firewall that requires you to use |
| 518 | an HTTP proxy to reach the internet, then you can configure the proxy |
| 519 |
| --- www/quickstart.wiki | |
| +++ www/quickstart.wiki | |
| @@ -19,15 +19,13 @@ | |
| 19 | This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC |
| 20 | </b></pre> |
| 21 | |
| 22 | <h2 id="workflow" name="fslclone">General Work Flow</h2> |
| 23 | |
| 24 | Fossil works with [./glossary.md#repository | repository files] |
| 25 | and [./glossary.md#check-out | check-out directories] using a |
| 26 | workflow like this: |
| 27 | |
| 28 | <ul> |
| 29 | <li>Create or clone a repository file. ([/help/init|fossil init] or |
| 30 | [/help/clone | fossil clone]) |
| 31 | <li>Check out a local tree. ([/help/open | fossil open]) |
| @@ -41,12 +39,11 @@ | |
| 39 | The following sections give a brief overview of these |
| 40 | operations. |
| 41 | |
| 42 | <h2 id="new">Starting A New Project</h2> |
| 43 | |
| 44 | To start a new project with Fossil, [/help/init | create a new empty repository]: |
| 45 | |
| 46 | <pre><b>fossil init</b> <i>repository-filename</i> |
| 47 | </pre> |
| 48 | |
| 49 | You can name the database anything you like, and you can place it anywhere in the filesystem. |
| @@ -82,14 +79,14 @@ | |
| 79 | <h2 id="clone">Cloning An Existing Repository</h2> |
| 80 | |
| 81 | Most fossil operations interact with a repository that is on the |
| 82 | local disk drive, not on a remote system. Hence, before accessing |
| 83 | a remote repository it is necessary to make a local copy of that |
| 84 | repository, a process called |
| 85 | "[/help/clone | cloning]". |
| 86 | |
| 87 | This is done as follows: |
| 88 | |
| 89 | <pre><b>fossil clone</b> <i>URL repository-filename</i> |
| 90 | </pre> |
| 91 | |
| 92 | The <i>URL</i> specifies the fossil repository |
| @@ -107,12 +104,20 @@ | |
| 104 | 100% complete... |
| 105 | Extra delta compression... |
| 106 | Vacuuming the database... |
| 107 | project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333 |
| 108 | server-id: 016595e9043054038a9ea9bc526d7f33f7ac0e42 |
| 109 | admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")> |
| 110 | </b></pre> |
| 111 | |
| 112 | This <i>exampleuser</i> will be used by Fossil as the author of commits when |
| 113 | you checkin changes to the repository. It is also used by Fossil when you |
| 114 | make your repository available to others using the built-in server mode by |
| 115 | running <tt>[/help/server | fossil server]</tt> and will also be used when |
| 116 | running <tt>[/help/ui | fossil ui]</tt> to view the repository through |
| 117 | the Fossil UI. See the quick start topic for setting up a |
| 118 | <a href="#server">server</a> for more details. |
| 119 | |
| 120 | If the remote repository requires a login, include a |
| 121 | userid in the URL like this: |
| 122 | |
| 123 | <pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre> |
| @@ -153,26 +158,23 @@ | |
| 158 | |
| 159 | <h2 id="checkout">Checking Out A Local Tree</h2> |
| 160 | |
| 161 | To work on a project in fossil, you need to check out a local |
| 162 | copy of the source tree. Create the directory you want to be |
| 163 | the root of your tree, <tt>cd</tt> into that directory, and then: |
| 164 | |
| 165 | <pre><b>fossil open</b> <i>repository-filename</i></pre> |
| 166 | |
| 167 | For example: |
| 168 | |
| 169 | <pre><b>fossil open ../myclone.fossil |
| 170 | BUILD.txt |
| 171 | COPYRIGHT-BSD2.txt |
| 172 | README.md |
| 173 | ︙ |
| 174 | </tt></b></pre> |
| 175 | |
| 176 | This leaves you with the newest version of the tree |
| 177 | checked out. |
| 178 | From anywhere underneath the root of your local tree, you |
| 179 | can type commands like the following to find out the status of |
| 180 | your local tree: |
| @@ -320,41 +322,60 @@ | |
| 322 | |
| 323 | This will get you started on identifying checkins. The |
| 324 | <a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including |
| 325 | how timestamps can also be used. |
| 326 | |
| 327 | <h2 id="config">Accessing Your Local Repository's Web User Interface</h2> |
| 328 | |
| 329 | After you create a new repository, you usually want to do some local |
| 330 | configuration. This is most easily accomplished by firing up the Fossil |
| 331 | UI: |
| 332 | |
| 333 | <pre> |
| 334 | <b>fossil ui</b> <i>repository-filename</i> |
| 335 | </pre> |
| 336 | |
| 337 | You can shorten that to just [/help/ui | <b>fossil ui</b>] |
| 338 | if you are inside a checked-out local tree. |
| 339 | |
| 340 | This command starts an internal web server, after which Fossil |
| 341 | automatically launches your default browser, pointed at itself, |
| 342 | presenting a special view of the repository, its web user interface. |
| 343 | |
| 344 | You may override Fossil's logic for selecting the default browser so: |
| 345 | |
| 346 | <pre> |
| 347 | <b>fossil setting web-browser</b> <i>path-to-web-browser</i> |
| 348 | </pre> |
| 349 | |
| 350 | When launched this way, Fossil binds its internal web server to the IP |
| 351 | loopback address, 127.0.0.1, which it treats specially, bypassing all |
| 352 | user controls, effectively giving visitors the |
| 353 | [./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity]. |
| 354 | |
| 355 | Why is that a good idea, you ask? Because it is a safe |
| 356 | presumption that only someone with direct file access to the repository |
| 357 | database file could be using the resulting web interface. Anyone who can |
| 358 | modify the repo DB directly could give themselves any and all access |
| 359 | with a SQL query, or even by direct file manipulation; no amount of |
| 360 | access control matters to such a user. |
| 361 | |
| 362 | (Contrast the [#server | many <i>other</i> ways] of setting Fossil up |
| 363 | as an HTTP server, where the repo DB is on the other side of the HTTP |
| 364 | server wall, inaccessible by all means other than Fossil's own |
| 365 | mediation. For this reason, the "localhost bypasses access control" |
| 366 | policy does <i>not</i> apply to these other interfaces. That is a very |
| 367 | good thing, since without this difference in policy, it would be unsafe |
| 368 | to bind a [/help?cmd=server | <b>fossil server</b>] instance to |
| 369 | localhost on a high-numbered port and then reverse-proxy it out to the |
| 370 | world via HTTPS, a practice this author does engage in, with confidence.) |
| 371 | |
| 372 | Once you are finished configuring Fossil, you may safely Control-C out |
| 373 | of the <b>fossil ui</b> command to shut down this privileged |
| 374 | built-in web server. Moreover, you may by grace of SQLite do this <i>at |
| 375 | any time</i>: all changes are either committed durably to the repo DB or |
| 376 | rolled back, in their totality. This includes configuration changes. |
| 377 | |
| 378 | <h2 id="sharing">Sharing Changes</h2> |
| 379 | |
| 380 | When [./concepts.wiki#workflow|autosync] is turned off, |
| 381 | the changes you [/help/commit | commit] are only |
| @@ -464,55 +485,44 @@ | |
| 485 | level of undo/redo. |
| 486 | |
| 487 | |
| 488 | <h2 id="server">Setting Up A Server</h2> |
| 489 | |
| 490 | In addition to the inward-facing <b>fossil ui</b> mode covered [#config |
| 491 | | above], Fossil can also act as an outward-facing web server: |
| 492 | |
| 493 | <pre> |
| 494 | <b>[/help/server | fossil server]</b> <i>repository-filename</i> |
| 495 | </pre> |
| 496 | |
| 497 | Just as with <b>fossil ui</b>, you may omit the |
| 498 | <i>repository-filename</i> parameter when running this from within an open |
| 499 | check-out. |
| 500 | |
| 501 | <i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network |
| 502 | interfaces by default in this mode, and it enforces the configured |
| 503 | [./caps/ | role-based access controls]. Further, because it is meant to |
| 504 | provide external web service, it doesn't try to launch a local web |
| 505 | browser pointing to a "Fossil UI" presentation; external visitors see |
| 506 | your repository's configured home page instead. |
| 507 | |
| 508 | To serve varying needs, there are additional ways to serve a Fossil repo |
| 509 | to external users: |
| 510 | |
| 511 | <ul> |
| 512 | <li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki | |
| 513 | self-hosting repositories] |
| 514 | <li>[./server/any/scgi.md|SCGI] |
| 515 | <li>[./server/any/inetd.md|inetd] |
| 516 | <li>[./server/debian/service.md|systemd] |
| 517 | </ul> |
| 518 | |
| 519 | …along with [./server/#matrix | several other options]. |
| 520 | |
| 521 | We recommend that you read the [./server/whyuseaserver.wiki | Benefits |
| 522 | of a Fossil Server] article, because you might <i>need</i> to do this |
| 523 | and not yet know it. |
| 524 | |
| 525 | <h2 id="proxy">HTTP Proxies</h2> |
| 526 | |
| 527 | If you are behind a restrictive firewall that requires you to use |
| 528 | an HTTP proxy to reach the internet, then you can configure the proxy |
| 529 |
+3
-3
| --- www/server/debian/service.md | ||
| +++ www/server/debian/service.md | ||
| @@ -52,11 +52,11 @@ | ||
| 52 | 52 | suitable for sharing a Fossil repo to a workgroup on a private LAN. |
| 53 | 53 | |
| 54 | 54 | To do this, write the following in |
| 55 | 55 | `~/.local/share/systemd/user/fossil.service`: |
| 56 | 56 | |
| 57 | -```dosini | |
| 57 | +> ```dosini | |
| 58 | 58 | [Unit] |
| 59 | 59 | Description=Fossil user server |
| 60 | 60 | After=network-online.target |
| 61 | 61 | |
| 62 | 62 | [Service] |
| @@ -164,11 +164,11 @@ | ||
| 164 | 164 | It’s more complicated, but it has some nice properties. |
| 165 | 165 | |
| 166 | 166 | We first need to define the privileged socket listener by writing |
| 167 | 167 | `/etc/systemd/system/fossil.socket`: |
| 168 | 168 | |
| 169 | -```dosini | |
| 169 | +> ```dosini | |
| 170 | 170 | [Unit] |
| 171 | 171 | Description=Fossil socket |
| 172 | 172 | |
| 173 | 173 | [Socket] |
| 174 | 174 | Accept=yes |
| @@ -189,11 +189,11 @@ | ||
| 189 | 189 | documentation](../any/inetd.md). |
| 190 | 190 | |
| 191 | 191 | Next, create the service definition file in that same directory as |
| 192 | 192 | `[email protected]`: |
| 193 | 193 | |
| 194 | -```dosini | |
| 194 | +> ```dosini | |
| 195 | 195 | [Unit] |
| 196 | 196 | Description=Fossil socket server |
| 197 | 197 | After=network-online.target |
| 198 | 198 | |
| 199 | 199 | [Service] |
| 200 | 200 |
| --- www/server/debian/service.md | |
| +++ www/server/debian/service.md | |
| @@ -52,11 +52,11 @@ | |
| 52 | suitable for sharing a Fossil repo to a workgroup on a private LAN. |
| 53 | |
| 54 | To do this, write the following in |
| 55 | `~/.local/share/systemd/user/fossil.service`: |
| 56 | |
| 57 | ```dosini |
| 58 | [Unit] |
| 59 | Description=Fossil user server |
| 60 | After=network-online.target |
| 61 | |
| 62 | [Service] |
| @@ -164,11 +164,11 @@ | |
| 164 | It’s more complicated, but it has some nice properties. |
| 165 | |
| 166 | We first need to define the privileged socket listener by writing |
| 167 | `/etc/systemd/system/fossil.socket`: |
| 168 | |
| 169 | ```dosini |
| 170 | [Unit] |
| 171 | Description=Fossil socket |
| 172 | |
| 173 | [Socket] |
| 174 | Accept=yes |
| @@ -189,11 +189,11 @@ | |
| 189 | documentation](../any/inetd.md). |
| 190 | |
| 191 | Next, create the service definition file in that same directory as |
| 192 | `[email protected]`: |
| 193 | |
| 194 | ```dosini |
| 195 | [Unit] |
| 196 | Description=Fossil socket server |
| 197 | After=network-online.target |
| 198 | |
| 199 | [Service] |
| 200 |
| --- www/server/debian/service.md | |
| +++ www/server/debian/service.md | |
| @@ -52,11 +52,11 @@ | |
| 52 | suitable for sharing a Fossil repo to a workgroup on a private LAN. |
| 53 | |
| 54 | To do this, write the following in |
| 55 | `~/.local/share/systemd/user/fossil.service`: |
| 56 | |
| 57 | > ```dosini |
| 58 | [Unit] |
| 59 | Description=Fossil user server |
| 60 | After=network-online.target |
| 61 | |
| 62 | [Service] |
| @@ -164,11 +164,11 @@ | |
| 164 | It’s more complicated, but it has some nice properties. |
| 165 | |
| 166 | We first need to define the privileged socket listener by writing |
| 167 | `/etc/systemd/system/fossil.socket`: |
| 168 | |
| 169 | > ```dosini |
| 170 | [Unit] |
| 171 | Description=Fossil socket |
| 172 | |
| 173 | [Socket] |
| 174 | Accept=yes |
| @@ -189,11 +189,11 @@ | |
| 189 | documentation](../any/inetd.md). |
| 190 | |
| 191 | Next, create the service definition file in that same directory as |
| 192 | `[email protected]`: |
| 193 | |
| 194 | > ```dosini |
| 195 | [Unit] |
| 196 | Description=Fossil socket server |
| 197 | After=network-online.target |
| 198 | |
| 199 | [Service] |
| 200 |
+148
-28
| --- www/th1.md | ||
| +++ www/th1.md | ||
| @@ -12,29 +12,43 @@ | ||
| 12 | 12 | time all of the test cases for SQLite were written in Tcl and Tcl could not |
| 13 | 13 | be easily compiled on the SymbianOS. So TH1 was developed as a cut-down |
| 14 | 14 | version of Tcl that would facilitate running the SQLite test scripts on |
| 15 | 15 | SymbianOS. |
| 16 | 16 | |
| 17 | -Fossil was first being designed at about the same time that TH1 was | |
| 18 | -being developed for testing SQLite on SymbianOS. | |
| 17 | +Fossil was first being designed at about the same time. | |
| 19 | 18 | Early prototypes of Fossil were written in pure Tcl. But as the development |
| 20 | 19 | shifted toward the use of C-code, the need arose to have a Tcl-like |
| 21 | 20 | scripting language to help with code generation. TH1 was small and |
| 22 | 21 | light-weight and used minimal resources and seemed ideally suited for the |
| 23 | 22 | task. |
| 24 | 23 | |
| 25 | -The name "TH1" stands "Test Harness 1", since that was its original purpose. | |
| 24 | +The name "TH1" stands for "Test Harness 1", | |
| 25 | +since its original purpose was to serve as testing harness | |
| 26 | +for SQLite. | |
| 26 | 27 | |
| 27 | -Overview | |
| --------- | ||
| 28 | +Where TH1 Is Used In Fossil | |
| 29 | +--------------------------- | |
| 30 | + | |
| 31 | + * In the header and footer for [skins](./customskin.md) | |
| 32 | + text within `<th1>...</th1>` is run as a TH1 script. | |
| 33 | + ([example](/builtin/skins/default/header.txt)) | |
| 34 | + | |
| 35 | + * This display of [tickets](./bugtheory.wiki) is controlled by TH1 | |
| 36 | + scripts, so that the ticket format can be customized for each | |
| 37 | + project. Administrators can visit the <b>/tktsetup</b> page in | |
| 38 | + their repositories to view and customize these scripts. | |
| 39 | + ([example usage](./custom_ticket.wiki)) | |
| 40 | + | |
| 41 | +Overview Of The Tcl/TH1 Language | |
| 42 | +-------------------------------- | |
| 28 | 43 | |
| 29 | 44 | TH1 is a string-processing language. All values are strings. Any numerical |
| 30 | 45 | operations are accomplished by converting from string to numeric, performing |
| 31 | 46 | the computation, then converting the result back into a string. (This might |
| 32 | 47 | seem inefficient, but it is faster than people imagine, and numeric |
| 33 | 48 | computations do not come up very often for the kinds of work that TH1 does, |
| 34 | -so it has never been a factor.) | |
| 49 | +so it has never been an issue.) | |
| 35 | 50 | |
| 36 | 51 | A TH1 script consists of a sequence of commands. |
| 37 | 52 | Each command is terminated by the first *unescaped* newline or ";" character. |
| 38 | 53 | The text of the command (excluding the newline or semicolon terminator) |
| 39 | 54 | is broken into space-separated tokens. The first token is the command |
| @@ -68,11 +82,11 @@ | ||
| 68 | 82 | are removed from each token by the command parser.) The third token |
| 69 | 83 | is the `puts "hello"`, with its whitespace and newlines. The fourth token |
| 70 | 84 | is `else` and the fifth and last token is `puts "world"`. |
| 71 | 85 | |
| 72 | 86 | The `if` command evaluates its first argument (the second token) |
| 73 | -as an expression, and if that expression is true, evaluates its | |
| 87 | +as an expression, and if that expression is true, it evaluates its | |
| 74 | 88 | second argument (the third token) as a TH1 script. |
| 75 | 89 | If the expression is false and the third argument is `else`, then |
| 76 | 90 | the fourth argument is evaluated as a TH1 expression. |
| 77 | 91 | |
| 78 | 92 | So, you see, even though the example above spans five lines, it is really |
| @@ -106,10 +120,49 @@ | ||
| 106 | 120 | $repository "" info trunk]]] end] |
| 107 | 121 | |
| 108 | 122 | Those backslashes allow the command to wrap nicely within a standard |
| 109 | 123 | terminal width while telling the interpreter to consider those three |
| 110 | 124 | lines as a single command. |
| 125 | + | |
| 126 | +<a id="taint"></a>Tainted And Untainted Strings | |
| 127 | +----------------------------------------------- | |
| 128 | + | |
| 129 | +Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between | |
| 130 | +"tainted" and "untainted" strings. Tainted strings are strings that are | |
| 131 | +derived from user inputs that might contain text that is designed to subvert | |
| 132 | +the script. Untainted strings are known to come from secure sources and | |
| 133 | +are assumed to contain no malicious content. | |
| 134 | + | |
| 135 | +Beginning with Fossil version 2.26, and depending on the value of the | |
| 136 | +[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted | |
| 137 | +strings from being used in ways that might lead to XSS or SQL-injection | |
| 138 | +attacks. This feature helps to ensure that XSS and SQL-injection | |
| 139 | +vulnerabilities are not *accidentally* added to Fossil when | |
| 140 | +custom TH1 scripts for headers or footers or tickets are added to a | |
| 141 | +repository. Note that the tainted/untainted distinction in strings does | |
| 142 | +not make it impossible to introduce XSS and SQL-injections vulnerabilities | |
| 143 | +using poorly-written TH1 scripts; it just makes it more difficult and | |
| 144 | +less likely to happen by accident. Developers must still consider the | |
| 145 | +security implications TH1 customizations they add to Fossil, and take | |
| 146 | +appropriate precautions when writing custom TH1. Peer review of TH1 | |
| 147 | +script changes is encouraged. | |
| 148 | + | |
| 149 | +In Fossil version 2.26, if the vuln-report setting is set to "block" | |
| 150 | +or "fatal", the [html](#html) and [query](#query) TH1 commands will | |
| 151 | +fail with an error if their argument is a tainted string. This helps | |
| 152 | +to prevent XSS and SQL-injection attacks, respectively. Note that | |
| 153 | +the default value of the vuln-report setting is "log", which allows those | |
| 154 | +commands to continue working and only writes a warning message into the | |
| 155 | +error log. <b>Future versions of Fossil may change the default value | |
| 156 | +of the vuln-report setting to "block" or "fatal".</b> Fossil users | |
| 157 | +with customized TH1 scripts are encouraged to audit their customizations | |
| 158 | +and fix any potential vulnerabilities soon, so as to avoid breakage | |
| 159 | +caused by future upgrades. <b>Future versions of Fossil might also | |
| 160 | +place additional restrictions on the use of tainted strings.</b> | |
| 161 | +For example, it is likely that future versions of Fossil will disallow | |
| 162 | +using tainted strings as script, for example as the body of a "for" | |
| 163 | +loop or of a "proc". | |
| 111 | 164 | |
| 112 | 165 | |
| 113 | 166 | Summary of Core TH1 Commands |
| 114 | 167 | ---------------------------- |
| 115 | 168 | |
| @@ -147,10 +200,13 @@ | ||
| 147 | 200 | * string last NEEDLE HAYSTACK ?START-INDEX? |
| 148 | 201 | * string match PATTERN STRING |
| 149 | 202 | * string length STRING |
| 150 | 203 | * string range STRING FIRST LAST |
| 151 | 204 | * string repeat STRING COUNT |
| 205 | + * string trim STRING | |
| 206 | + * string trimleft STRING | |
| 207 | + * string trimright STRING | |
| 152 | 208 | * unset VARNAME |
| 153 | 209 | * uplevel ?LEVEL? SCRIPT |
| 154 | 210 | * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? |
| 155 | 211 | |
| 156 | 212 | All of the above commands work as in the original Tcl. Refer to the |
| @@ -182,11 +238,10 @@ | ||
| 182 | 238 | * [copybtn](#copybtn) |
| 183 | 239 | * [date](#date) |
| 184 | 240 | * [decorate](#decorate) |
| 185 | 241 | * [defHeader](#defHeader) |
| 186 | 242 | * [dir](#dir) |
| 187 | - * [enable\_htmlify](#enable_htmlify) | |
| 188 | 243 | * [enable\_output](#enable_output) |
| 189 | 244 | * [encode64](#encode64) |
| 190 | 245 | * [getParameter](#getParameter) |
| 191 | 246 | * [glob\_match](#glob_match) |
| 192 | 247 | * [globalState](#globalState) |
| @@ -214,17 +269,19 @@ | ||
| 214 | 269 | * [stime](#stime) |
| 215 | 270 | * [styleHeader](#styleHeader) |
| 216 | 271 | * [styleFooter](#styleFooter) |
| 217 | 272 | * [styleScript](#styleScript) |
| 218 | 273 | * [submenu](#submenu) |
| 274 | + * [taint](#taintCmd) | |
| 219 | 275 | * [tclEval](#tclEval) |
| 220 | 276 | * [tclExpr](#tclExpr) |
| 221 | 277 | * [tclInvoke](#tclInvoke) |
| 222 | 278 | * [tclIsSafe](#tclIsSafe) |
| 223 | 279 | * [tclMakeSafe](#tclMakeSafe) |
| 224 | 280 | * [tclReady](#tclReady) |
| 225 | 281 | * [trace](#trace) |
| 282 | + * [untaint](#untaintCmd) | |
| 226 | 283 | * [unversioned content](#unversioned_content) |
| 227 | 284 | * [unversioned list](#unversioned_list) |
| 228 | 285 | * [utime](#utime) |
| 229 | 286 | * [verifyCsrf](#verifyCsrf) |
| 230 | 287 | * [verifyLogin](#verifyLogin) |
| @@ -413,28 +470,10 @@ | ||
| 413 | 470 | the files matching the pattern GLOB within CHECKIN will be returned. |
| 414 | 471 | If DETAILS is non-zero, the result will be a list-of-lists, with each |
| 415 | 472 | element containing at least three elements: the file name, the file |
| 416 | 473 | size (in bytes), and the file last modification time (relative to the |
| 417 | 474 | time zone configured for the repository). |
| 418 | - | |
| 419 | -<a id="enable_htmlify"></a>TH1 enable\_htmlify Command | |
| ------------------------------------------------------- | ||
| 420 | - | |
| 421 | - * enable\_htmlify | |
| 422 | - * enable\_htmlify ?TRACE-LABEL? BOOLEAN | |
| 423 | - | |
| 424 | -By default, certain output from `puts` and similar commands is escaped | |
| 425 | -for HTML. The first call form returns the current state of that | |
| 426 | -feature: `1` for on and `0` for off. The second call form enables | |
| 427 | -(non-0) or disables (0) that feature and returns the *pre-call* state | |
| 428 | -of that feature (so that a second call can pass that value to restore | |
| 429 | -it to its previous state). The optional `TRACE-LABEL` argument causes | |
| 430 | -the TH1 tracing output (if enabled) to add a marker when the second | |
| 431 | -form of this command is invoked, and includes that label and the | |
| 432 | -boolean argument's value in the trace. If tracing is disabled, that | |
| 433 | -argument has no effect. | |
| 434 | - | |
| 435 | 475 | |
| 436 | 476 | <a id="enable_output"></a>TH1 enable\_output Command |
| 437 | 477 | ------------------------------------------------------ |
| 438 | 478 | |
| 439 | 479 | * enable\_output BOOLEAN |
| @@ -527,11 +566,25 @@ | ||
| 527 | 566 | <a id="html"></a>TH1 html Command |
| 528 | 567 | ----------------------------------- |
| 529 | 568 | |
| 530 | 569 | * html STRING |
| 531 | 570 | |
| 532 | -Outputs the STRING escaped for HTML. | |
| 571 | +Outputs the STRING literally. It is assumed that STRING contains | |
| 572 | +valid HTML, or that if STRING contains any characters that are | |
| 573 | +significant to HTML (such as `<`, `>`, `'`, or `&`) have already | |
| 574 | +been escaped, perhaps by the [htmlize](#htmlize) command. Use the | |
| 575 | +[puts](#puts) command to output text that might contain unescaped | |
| 576 | +HTML markup. | |
| 577 | + | |
| 578 | +**Beware of XSS attacks!** If the STRING value to the html command | |
| 579 | +can be controlled by a hostile user, then he might be able to sneak | |
| 580 | +in malicious HTML or Javascript which could result in a | |
| 581 | +cross-site scripting (XSS) attack. Be careful that all text that | |
| 582 | +in STRING that might come from user input has been sanitized by the | |
| 583 | +[htmlize](#htmlize) command or similar. In recent versions of Fossil, | |
| 584 | +the STRING value must be [untainted](#taint) or else the "html" command | |
| 585 | +will fail. | |
| 533 | 586 | |
| 534 | 587 | <a id="htmlize"></a>TH1 htmlize Command |
| 535 | 588 | ----------------------------------------- |
| 536 | 589 | |
| 537 | 590 | * htmlize STRING |
| @@ -595,12 +648,16 @@ | ||
| 595 | 648 | <a id="puts"></a>TH1 puts Command |
| 596 | 649 | ----------------------------------- |
| 597 | 650 | |
| 598 | 651 | * puts STRING |
| 599 | 652 | |
| 600 | -Outputs the STRING unchanged, where "unchanged" might, depending on | |
| 601 | -the context, mean "with some characters escaped for HTML." | |
| 653 | +Outputs STRING. Characters within STRING that have special meaning | |
| 654 | +in HTML are escaped prior to being output. Thus is it safe for STRING | |
| 655 | +to be derived from user inputs. See also the [html](#html) command | |
| 656 | +which behaves similarly except does not escape HTML markup. This | |
| 657 | +command ("puts") is safe to use on [tainted strings](#taint), but the "html" | |
| 658 | +command is not. | |
| 602 | 659 | |
| 603 | 660 | <a id="query"></a>TH1 query Command |
| 604 | 661 | ------------------------------------- |
| 605 | 662 | |
| 606 | 663 | * query ?-nocomplain? SQL CODE |
| @@ -608,11 +665,44 @@ | ||
| 608 | 665 | Runs the SQL query given by the SQL argument. For each row in the result |
| 609 | 666 | set, run CODE. |
| 610 | 667 | |
| 611 | 668 | In SQL, parameters such as $var are filled in using the value of variable |
| 612 | 669 | "var". Result values are stored in variables with the column name prior |
| 613 | -to each invocation of CODE. | |
| 670 | +to each invocation of CODE. The names of the variables in which results | |
| 671 | +are stored can be controlled using "AS name" clauses in the SQL. As | |
| 672 | +the database will often contain content that originates from untrusted | |
| 673 | +users, all result values are marked as [tainted](#taint). | |
| 674 | + | |
| 675 | +**Beware of SQL injections in the `query` command!** | |
| 676 | +The SQL argument to the query command should always be literal SQL | |
| 677 | +text enclosed in {...}. The SQL argument should never be a double-quoted | |
| 678 | +string or the value of a \$variable, as those constructs can lead to | |
| 679 | +an SQL Injection attack. If you need to include the values of one or | |
| 680 | +more TH1 variables as part of the SQL, then put \$variable inside the | |
| 681 | +{...}. The \$variable keyword will then get passed down into the SQLite | |
| 682 | +parser which knows to look up the value of \$variable in the TH1 symbol | |
| 683 | +table. For example: | |
| 684 | + | |
| 685 | +~~~ | |
| 686 | + query {SELECT res FROM tab1 WHERE key=$mykey} {...} | |
| 687 | +~~~ | |
| 688 | + | |
| 689 | +SQLite will see the \$mykey token in the SQL and will know to resolve it | |
| 690 | +to the value of the "mykey" TH1 variable, safely and without the possibility | |
| 691 | +of SQL injection. The following is unsafe: | |
| 692 | + | |
| 693 | +~~~ | |
| 694 | + query "SELECT res FROM tab1 WHERE key='$mykey'" {...} ;# <-- UNSAFE! | |
| 695 | +~~~ | |
| 696 | + | |
| 697 | +In this second example, TH1 does the expansion of `$mykey` prior to passing | |
| 698 | +the text down into SQLite. So if `$mykey` contains a single-quote character, | |
| 699 | +followed by additional hostile text, that will result in an SQL injection. | |
| 700 | + | |
| 701 | +To help guard against SQL-injections, recent versions of Fossil require | |
| 702 | +that the SQL argument be [untainted](#taint) or else the "query" command | |
| 703 | +will fail. | |
| 614 | 704 | |
| 615 | 705 | <a id="randhex"></a>TH1 randhex Command |
| 616 | 706 | ----------------------------------------- |
| 617 | 707 | |
| 618 | 708 | * randhex N |
| @@ -638,10 +728,12 @@ | ||
| 638 | 728 | * regexp ?-nocase? ?--? exp string |
| 639 | 729 | |
| 640 | 730 | Checks the string against the specified regular expression and returns |
| 641 | 731 | non-zero if it matches. If the regular expression is invalid or cannot |
| 642 | 732 | be compiled, an error will be generated. |
| 733 | + | |
| 734 | +See [fossil grep](./grep.md) for details on the regexp syntax. | |
| 643 | 735 | |
| 644 | 736 | <a id="reinitialize"></a>TH1 reinitialize Command |
| 645 | 737 | --------------------------------------------------- |
| 646 | 738 | |
| 647 | 739 | * reinitialize ?FLAGS? |
| @@ -741,10 +833,24 @@ | ||
| 741 | 833 | |
| 742 | 834 | * submenu link LABEL URL |
| 743 | 835 | |
| 744 | 836 | Add hyperlink to the submenu of the current page. |
| 745 | 837 | |
| 838 | +<a id="taintCmd"></a>TH1 taint Command | |
| 839 | +----------------------------------------- | |
| 840 | + | |
| 841 | + * taint STRING | |
| 842 | + | |
| 843 | +This command returns a copy of STRING that has been marked as | |
| 844 | +[tainted](#taint). Tainted strings are strings which might be | |
| 845 | +controlled by an attacker and might contain hostile inputs and | |
| 846 | +are thus unsafe to use in certain contexts. For example, tainted | |
| 847 | +strings should not be output as part of a webpage as they might | |
| 848 | +contain rogue HTML or Javascript that could lead to an XSS | |
| 849 | +vulnerability. Similarly, tainted strings should not be run as | |
| 850 | +SQL since they might contain an SQL-injection vulerability. | |
| 851 | + | |
| 746 | 852 | <a id="tclEval"></a>TH1 tclEval Command |
| 747 | 853 | ----------------------------------------- |
| 748 | 854 | |
| 749 | 855 | **This command requires the Tcl integration feature.** |
| 750 | 856 | |
| @@ -812,10 +918,22 @@ | ||
| 812 | 918 | |
| 813 | 919 | * trace STRING |
| 814 | 920 | |
| 815 | 921 | Generates a TH1 trace message if TH1 tracing is enabled. |
| 816 | 922 | |
| 923 | +<a id="untaintCmd"></a>TH1 taint Command | |
| 924 | +----------------------------------------- | |
| 925 | + | |
| 926 | + * untaint STRING | |
| 927 | + | |
| 928 | +This command returns a copy of STRING that has been marked as | |
| 929 | +[untainted](#taint). Untainted strings are strings which are | |
| 930 | +believed to be free of potentially hostile content. Use this | |
| 931 | +command with caution, as it overwrites the tainted-string protection | |
| 932 | +mechanisms that are built into TH1. If you do not understand all | |
| 933 | +the implications of executing this command, then do not use it. | |
| 934 | + | |
| 817 | 935 | <a id="unversioned_content"></a>TH1 unversioned content Command |
| 818 | 936 | ----------------------------------------------------------------- |
| 819 | 937 | |
| 820 | 938 | * unversioned content FILENAME |
| 821 | 939 | |
| 822 | 940 |
| --- www/th1.md | |
| +++ www/th1.md | |
| @@ -12,29 +12,43 @@ | |
| 12 | time all of the test cases for SQLite were written in Tcl and Tcl could not |
| 13 | be easily compiled on the SymbianOS. So TH1 was developed as a cut-down |
| 14 | version of Tcl that would facilitate running the SQLite test scripts on |
| 15 | SymbianOS. |
| 16 | |
| 17 | Fossil was first being designed at about the same time that TH1 was |
| 18 | being developed for testing SQLite on SymbianOS. |
| 19 | Early prototypes of Fossil were written in pure Tcl. But as the development |
| 20 | shifted toward the use of C-code, the need arose to have a Tcl-like |
| 21 | scripting language to help with code generation. TH1 was small and |
| 22 | light-weight and used minimal resources and seemed ideally suited for the |
| 23 | task. |
| 24 | |
| 25 | The name "TH1" stands "Test Harness 1", since that was its original purpose. |
| 26 | |
| 27 | Overview |
| --------- | |
| 28 | |
| 29 | TH1 is a string-processing language. All values are strings. Any numerical |
| 30 | operations are accomplished by converting from string to numeric, performing |
| 31 | the computation, then converting the result back into a string. (This might |
| 32 | seem inefficient, but it is faster than people imagine, and numeric |
| 33 | computations do not come up very often for the kinds of work that TH1 does, |
| 34 | so it has never been a factor.) |
| 35 | |
| 36 | A TH1 script consists of a sequence of commands. |
| 37 | Each command is terminated by the first *unescaped* newline or ";" character. |
| 38 | The text of the command (excluding the newline or semicolon terminator) |
| 39 | is broken into space-separated tokens. The first token is the command |
| @@ -68,11 +82,11 @@ | |
| 68 | are removed from each token by the command parser.) The third token |
| 69 | is the `puts "hello"`, with its whitespace and newlines. The fourth token |
| 70 | is `else` and the fifth and last token is `puts "world"`. |
| 71 | |
| 72 | The `if` command evaluates its first argument (the second token) |
| 73 | as an expression, and if that expression is true, evaluates its |
| 74 | second argument (the third token) as a TH1 script. |
| 75 | If the expression is false and the third argument is `else`, then |
| 76 | the fourth argument is evaluated as a TH1 expression. |
| 77 | |
| 78 | So, you see, even though the example above spans five lines, it is really |
| @@ -106,10 +120,49 @@ | |
| 106 | $repository "" info trunk]]] end] |
| 107 | |
| 108 | Those backslashes allow the command to wrap nicely within a standard |
| 109 | terminal width while telling the interpreter to consider those three |
| 110 | lines as a single command. |
| 111 | |
| 112 | |
| 113 | Summary of Core TH1 Commands |
| 114 | ---------------------------- |
| 115 | |
| @@ -147,10 +200,13 @@ | |
| 147 | * string last NEEDLE HAYSTACK ?START-INDEX? |
| 148 | * string match PATTERN STRING |
| 149 | * string length STRING |
| 150 | * string range STRING FIRST LAST |
| 151 | * string repeat STRING COUNT |
| 152 | * unset VARNAME |
| 153 | * uplevel ?LEVEL? SCRIPT |
| 154 | * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? |
| 155 | |
| 156 | All of the above commands work as in the original Tcl. Refer to the |
| @@ -182,11 +238,10 @@ | |
| 182 | * [copybtn](#copybtn) |
| 183 | * [date](#date) |
| 184 | * [decorate](#decorate) |
| 185 | * [defHeader](#defHeader) |
| 186 | * [dir](#dir) |
| 187 | * [enable\_htmlify](#enable_htmlify) |
| 188 | * [enable\_output](#enable_output) |
| 189 | * [encode64](#encode64) |
| 190 | * [getParameter](#getParameter) |
| 191 | * [glob\_match](#glob_match) |
| 192 | * [globalState](#globalState) |
| @@ -214,17 +269,19 @@ | |
| 214 | * [stime](#stime) |
| 215 | * [styleHeader](#styleHeader) |
| 216 | * [styleFooter](#styleFooter) |
| 217 | * [styleScript](#styleScript) |
| 218 | * [submenu](#submenu) |
| 219 | * [tclEval](#tclEval) |
| 220 | * [tclExpr](#tclExpr) |
| 221 | * [tclInvoke](#tclInvoke) |
| 222 | * [tclIsSafe](#tclIsSafe) |
| 223 | * [tclMakeSafe](#tclMakeSafe) |
| 224 | * [tclReady](#tclReady) |
| 225 | * [trace](#trace) |
| 226 | * [unversioned content](#unversioned_content) |
| 227 | * [unversioned list](#unversioned_list) |
| 228 | * [utime](#utime) |
| 229 | * [verifyCsrf](#verifyCsrf) |
| 230 | * [verifyLogin](#verifyLogin) |
| @@ -413,28 +470,10 @@ | |
| 413 | the files matching the pattern GLOB within CHECKIN will be returned. |
| 414 | If DETAILS is non-zero, the result will be a list-of-lists, with each |
| 415 | element containing at least three elements: the file name, the file |
| 416 | size (in bytes), and the file last modification time (relative to the |
| 417 | time zone configured for the repository). |
| 418 | |
| 419 | <a id="enable_htmlify"></a>TH1 enable\_htmlify Command |
| ------------------------------------------------------- | |
| 420 | |
| 421 | * enable\_htmlify |
| 422 | * enable\_htmlify ?TRACE-LABEL? BOOLEAN |
| 423 | |
| 424 | By default, certain output from `puts` and similar commands is escaped |
| 425 | for HTML. The first call form returns the current state of that |
| 426 | feature: `1` for on and `0` for off. The second call form enables |
| 427 | (non-0) or disables (0) that feature and returns the *pre-call* state |
| 428 | of that feature (so that a second call can pass that value to restore |
| 429 | it to its previous state). The optional `TRACE-LABEL` argument causes |
| 430 | the TH1 tracing output (if enabled) to add a marker when the second |
| 431 | form of this command is invoked, and includes that label and the |
| 432 | boolean argument's value in the trace. If tracing is disabled, that |
| 433 | argument has no effect. |
| 434 | |
| 435 | |
| 436 | <a id="enable_output"></a>TH1 enable\_output Command |
| 437 | ------------------------------------------------------ |
| 438 | |
| 439 | * enable\_output BOOLEAN |
| @@ -527,11 +566,25 @@ | |
| 527 | <a id="html"></a>TH1 html Command |
| 528 | ----------------------------------- |
| 529 | |
| 530 | * html STRING |
| 531 | |
| 532 | Outputs the STRING escaped for HTML. |
| 533 | |
| 534 | <a id="htmlize"></a>TH1 htmlize Command |
| 535 | ----------------------------------------- |
| 536 | |
| 537 | * htmlize STRING |
| @@ -595,12 +648,16 @@ | |
| 595 | <a id="puts"></a>TH1 puts Command |
| 596 | ----------------------------------- |
| 597 | |
| 598 | * puts STRING |
| 599 | |
| 600 | Outputs the STRING unchanged, where "unchanged" might, depending on |
| 601 | the context, mean "with some characters escaped for HTML." |
| 602 | |
| 603 | <a id="query"></a>TH1 query Command |
| 604 | ------------------------------------- |
| 605 | |
| 606 | * query ?-nocomplain? SQL CODE |
| @@ -608,11 +665,44 @@ | |
| 608 | Runs the SQL query given by the SQL argument. For each row in the result |
| 609 | set, run CODE. |
| 610 | |
| 611 | In SQL, parameters such as $var are filled in using the value of variable |
| 612 | "var". Result values are stored in variables with the column name prior |
| 613 | to each invocation of CODE. |
| 614 | |
| 615 | <a id="randhex"></a>TH1 randhex Command |
| 616 | ----------------------------------------- |
| 617 | |
| 618 | * randhex N |
| @@ -638,10 +728,12 @@ | |
| 638 | * regexp ?-nocase? ?--? exp string |
| 639 | |
| 640 | Checks the string against the specified regular expression and returns |
| 641 | non-zero if it matches. If the regular expression is invalid or cannot |
| 642 | be compiled, an error will be generated. |
| 643 | |
| 644 | <a id="reinitialize"></a>TH1 reinitialize Command |
| 645 | --------------------------------------------------- |
| 646 | |
| 647 | * reinitialize ?FLAGS? |
| @@ -741,10 +833,24 @@ | |
| 741 | |
| 742 | * submenu link LABEL URL |
| 743 | |
| 744 | Add hyperlink to the submenu of the current page. |
| 745 | |
| 746 | <a id="tclEval"></a>TH1 tclEval Command |
| 747 | ----------------------------------------- |
| 748 | |
| 749 | **This command requires the Tcl integration feature.** |
| 750 | |
| @@ -812,10 +918,22 @@ | |
| 812 | |
| 813 | * trace STRING |
| 814 | |
| 815 | Generates a TH1 trace message if TH1 tracing is enabled. |
| 816 | |
| 817 | <a id="unversioned_content"></a>TH1 unversioned content Command |
| 818 | ----------------------------------------------------------------- |
| 819 | |
| 820 | * unversioned content FILENAME |
| 821 | |
| 822 |
| --- www/th1.md | |
| +++ www/th1.md | |
| @@ -12,29 +12,43 @@ | |
| 12 | time all of the test cases for SQLite were written in Tcl and Tcl could not |
| 13 | be easily compiled on the SymbianOS. So TH1 was developed as a cut-down |
| 14 | version of Tcl that would facilitate running the SQLite test scripts on |
| 15 | SymbianOS. |
| 16 | |
| 17 | Fossil was first being designed at about the same time. |
| 18 | Early prototypes of Fossil were written in pure Tcl. But as the development |
| 19 | shifted toward the use of C-code, the need arose to have a Tcl-like |
| 20 | scripting language to help with code generation. TH1 was small and |
| 21 | light-weight and used minimal resources and seemed ideally suited for the |
| 22 | task. |
| 23 | |
| 24 | The name "TH1" stands for "Test Harness 1", |
| 25 | since its original purpose was to serve as testing harness |
| 26 | for SQLite. |
| 27 | |
| --------- | |
| 28 | Where TH1 Is Used In Fossil |
| 29 | --------------------------- |
| 30 | |
| 31 | * In the header and footer for [skins](./customskin.md) |
| 32 | text within `<th1>...</th1>` is run as a TH1 script. |
| 33 | ([example](/builtin/skins/default/header.txt)) |
| 34 | |
| 35 | * This display of [tickets](./bugtheory.wiki) is controlled by TH1 |
| 36 | scripts, so that the ticket format can be customized for each |
| 37 | project. Administrators can visit the <b>/tktsetup</b> page in |
| 38 | their repositories to view and customize these scripts. |
| 39 | ([example usage](./custom_ticket.wiki)) |
| 40 | |
| 41 | Overview Of The Tcl/TH1 Language |
| 42 | -------------------------------- |
| 43 | |
| 44 | TH1 is a string-processing language. All values are strings. Any numerical |
| 45 | operations are accomplished by converting from string to numeric, performing |
| 46 | the computation, then converting the result back into a string. (This might |
| 47 | seem inefficient, but it is faster than people imagine, and numeric |
| 48 | computations do not come up very often for the kinds of work that TH1 does, |
| 49 | so it has never been an issue.) |
| 50 | |
| 51 | A TH1 script consists of a sequence of commands. |
| 52 | Each command is terminated by the first *unescaped* newline or ";" character. |
| 53 | The text of the command (excluding the newline or semicolon terminator) |
| 54 | is broken into space-separated tokens. The first token is the command |
| @@ -68,11 +82,11 @@ | |
| 82 | are removed from each token by the command parser.) The third token |
| 83 | is the `puts "hello"`, with its whitespace and newlines. The fourth token |
| 84 | is `else` and the fifth and last token is `puts "world"`. |
| 85 | |
| 86 | The `if` command evaluates its first argument (the second token) |
| 87 | as an expression, and if that expression is true, it evaluates its |
| 88 | second argument (the third token) as a TH1 script. |
| 89 | If the expression is false and the third argument is `else`, then |
| 90 | the fourth argument is evaluated as a TH1 expression. |
| 91 | |
| 92 | So, you see, even though the example above spans five lines, it is really |
| @@ -106,10 +120,49 @@ | |
| 120 | $repository "" info trunk]]] end] |
| 121 | |
| 122 | Those backslashes allow the command to wrap nicely within a standard |
| 123 | terminal width while telling the interpreter to consider those three |
| 124 | lines as a single command. |
| 125 | |
| 126 | <a id="taint"></a>Tainted And Untainted Strings |
| 127 | ----------------------------------------------- |
| 128 | |
| 129 | Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between |
| 130 | "tainted" and "untainted" strings. Tainted strings are strings that are |
| 131 | derived from user inputs that might contain text that is designed to subvert |
| 132 | the script. Untainted strings are known to come from secure sources and |
| 133 | are assumed to contain no malicious content. |
| 134 | |
| 135 | Beginning with Fossil version 2.26, and depending on the value of the |
| 136 | [vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted |
| 137 | strings from being used in ways that might lead to XSS or SQL-injection |
| 138 | attacks. This feature helps to ensure that XSS and SQL-injection |
| 139 | vulnerabilities are not *accidentally* added to Fossil when |
| 140 | custom TH1 scripts for headers or footers or tickets are added to a |
| 141 | repository. Note that the tainted/untainted distinction in strings does |
| 142 | not make it impossible to introduce XSS and SQL-injections vulnerabilities |
| 143 | using poorly-written TH1 scripts; it just makes it more difficult and |
| 144 | less likely to happen by accident. Developers must still consider the |
| 145 | security implications TH1 customizations they add to Fossil, and take |
| 146 | appropriate precautions when writing custom TH1. Peer review of TH1 |
| 147 | script changes is encouraged. |
| 148 | |
| 149 | In Fossil version 2.26, if the vuln-report setting is set to "block" |
| 150 | or "fatal", the [html](#html) and [query](#query) TH1 commands will |
| 151 | fail with an error if their argument is a tainted string. This helps |
| 152 | to prevent XSS and SQL-injection attacks, respectively. Note that |
| 153 | the default value of the vuln-report setting is "log", which allows those |
| 154 | commands to continue working and only writes a warning message into the |
| 155 | error log. <b>Future versions of Fossil may change the default value |
| 156 | of the vuln-report setting to "block" or "fatal".</b> Fossil users |
| 157 | with customized TH1 scripts are encouraged to audit their customizations |
| 158 | and fix any potential vulnerabilities soon, so as to avoid breakage |
| 159 | caused by future upgrades. <b>Future versions of Fossil might also |
| 160 | place additional restrictions on the use of tainted strings.</b> |
| 161 | For example, it is likely that future versions of Fossil will disallow |
| 162 | using tainted strings as script, for example as the body of a "for" |
| 163 | loop or of a "proc". |
| 164 | |
| 165 | |
| 166 | Summary of Core TH1 Commands |
| 167 | ---------------------------- |
| 168 | |
| @@ -147,10 +200,13 @@ | |
| 200 | * string last NEEDLE HAYSTACK ?START-INDEX? |
| 201 | * string match PATTERN STRING |
| 202 | * string length STRING |
| 203 | * string range STRING FIRST LAST |
| 204 | * string repeat STRING COUNT |
| 205 | * string trim STRING |
| 206 | * string trimleft STRING |
| 207 | * string trimright STRING |
| 208 | * unset VARNAME |
| 209 | * uplevel ?LEVEL? SCRIPT |
| 210 | * upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR? |
| 211 | |
| 212 | All of the above commands work as in the original Tcl. Refer to the |
| @@ -182,11 +238,10 @@ | |
| 238 | * [copybtn](#copybtn) |
| 239 | * [date](#date) |
| 240 | * [decorate](#decorate) |
| 241 | * [defHeader](#defHeader) |
| 242 | * [dir](#dir) |
| 243 | * [enable\_output](#enable_output) |
| 244 | * [encode64](#encode64) |
| 245 | * [getParameter](#getParameter) |
| 246 | * [glob\_match](#glob_match) |
| 247 | * [globalState](#globalState) |
| @@ -214,17 +269,19 @@ | |
| 269 | * [stime](#stime) |
| 270 | * [styleHeader](#styleHeader) |
| 271 | * [styleFooter](#styleFooter) |
| 272 | * [styleScript](#styleScript) |
| 273 | * [submenu](#submenu) |
| 274 | * [taint](#taintCmd) |
| 275 | * [tclEval](#tclEval) |
| 276 | * [tclExpr](#tclExpr) |
| 277 | * [tclInvoke](#tclInvoke) |
| 278 | * [tclIsSafe](#tclIsSafe) |
| 279 | * [tclMakeSafe](#tclMakeSafe) |
| 280 | * [tclReady](#tclReady) |
| 281 | * [trace](#trace) |
| 282 | * [untaint](#untaintCmd) |
| 283 | * [unversioned content](#unversioned_content) |
| 284 | * [unversioned list](#unversioned_list) |
| 285 | * [utime](#utime) |
| 286 | * [verifyCsrf](#verifyCsrf) |
| 287 | * [verifyLogin](#verifyLogin) |
| @@ -413,28 +470,10 @@ | |
| 470 | the files matching the pattern GLOB within CHECKIN will be returned. |
| 471 | If DETAILS is non-zero, the result will be a list-of-lists, with each |
| 472 | element containing at least three elements: the file name, the file |
| 473 | size (in bytes), and the file last modification time (relative to the |
| 474 | time zone configured for the repository). |
| ------------------------------------------------------- | |
| 475 | |
| 476 | <a id="enable_output"></a>TH1 enable\_output Command |
| 477 | ------------------------------------------------------ |
| 478 | |
| 479 | * enable\_output BOOLEAN |
| @@ -527,11 +566,25 @@ | |
| 566 | <a id="html"></a>TH1 html Command |
| 567 | ----------------------------------- |
| 568 | |
| 569 | * html STRING |
| 570 | |
| 571 | Outputs the STRING literally. It is assumed that STRING contains |
| 572 | valid HTML, or that if STRING contains any characters that are |
| 573 | significant to HTML (such as `<`, `>`, `'`, or `&`) have already |
| 574 | been escaped, perhaps by the [htmlize](#htmlize) command. Use the |
| 575 | [puts](#puts) command to output text that might contain unescaped |
| 576 | HTML markup. |
| 577 | |
| 578 | **Beware of XSS attacks!** If the STRING value to the html command |
| 579 | can be controlled by a hostile user, then he might be able to sneak |
| 580 | in malicious HTML or Javascript which could result in a |
| 581 | cross-site scripting (XSS) attack. Be careful that all text that |
| 582 | in STRING that might come from user input has been sanitized by the |
| 583 | [htmlize](#htmlize) command or similar. In recent versions of Fossil, |
| 584 | the STRING value must be [untainted](#taint) or else the "html" command |
| 585 | will fail. |
| 586 | |
| 587 | <a id="htmlize"></a>TH1 htmlize Command |
| 588 | ----------------------------------------- |
| 589 | |
| 590 | * htmlize STRING |
| @@ -595,12 +648,16 @@ | |
| 648 | <a id="puts"></a>TH1 puts Command |
| 649 | ----------------------------------- |
| 650 | |
| 651 | * puts STRING |
| 652 | |
| 653 | Outputs STRING. Characters within STRING that have special meaning |
| 654 | in HTML are escaped prior to being output. Thus is it safe for STRING |
| 655 | to be derived from user inputs. See also the [html](#html) command |
| 656 | which behaves similarly except does not escape HTML markup. This |
| 657 | command ("puts") is safe to use on [tainted strings](#taint), but the "html" |
| 658 | command is not. |
| 659 | |
| 660 | <a id="query"></a>TH1 query Command |
| 661 | ------------------------------------- |
| 662 | |
| 663 | * query ?-nocomplain? SQL CODE |
| @@ -608,11 +665,44 @@ | |
| 665 | Runs the SQL query given by the SQL argument. For each row in the result |
| 666 | set, run CODE. |
| 667 | |
| 668 | In SQL, parameters such as $var are filled in using the value of variable |
| 669 | "var". Result values are stored in variables with the column name prior |
| 670 | to each invocation of CODE. The names of the variables in which results |
| 671 | are stored can be controlled using "AS name" clauses in the SQL. As |
| 672 | the database will often contain content that originates from untrusted |
| 673 | users, all result values are marked as [tainted](#taint). |
| 674 | |
| 675 | **Beware of SQL injections in the `query` command!** |
| 676 | The SQL argument to the query command should always be literal SQL |
| 677 | text enclosed in {...}. The SQL argument should never be a double-quoted |
| 678 | string or the value of a \$variable, as those constructs can lead to |
| 679 | an SQL Injection attack. If you need to include the values of one or |
| 680 | more TH1 variables as part of the SQL, then put \$variable inside the |
| 681 | {...}. The \$variable keyword will then get passed down into the SQLite |
| 682 | parser which knows to look up the value of \$variable in the TH1 symbol |
| 683 | table. For example: |
| 684 | |
| 685 | ~~~ |
| 686 | query {SELECT res FROM tab1 WHERE key=$mykey} {...} |
| 687 | ~~~ |
| 688 | |
| 689 | SQLite will see the \$mykey token in the SQL and will know to resolve it |
| 690 | to the value of the "mykey" TH1 variable, safely and without the possibility |
| 691 | of SQL injection. The following is unsafe: |
| 692 | |
| 693 | ~~~ |
| 694 | query "SELECT res FROM tab1 WHERE key='$mykey'" {...} ;# <-- UNSAFE! |
| 695 | ~~~ |
| 696 | |
| 697 | In this second example, TH1 does the expansion of `$mykey` prior to passing |
| 698 | the text down into SQLite. So if `$mykey` contains a single-quote character, |
| 699 | followed by additional hostile text, that will result in an SQL injection. |
| 700 | |
| 701 | To help guard against SQL-injections, recent versions of Fossil require |
| 702 | that the SQL argument be [untainted](#taint) or else the "query" command |
| 703 | will fail. |
| 704 | |
| 705 | <a id="randhex"></a>TH1 randhex Command |
| 706 | ----------------------------------------- |
| 707 | |
| 708 | * randhex N |
| @@ -638,10 +728,12 @@ | |
| 728 | * regexp ?-nocase? ?--? exp string |
| 729 | |
| 730 | Checks the string against the specified regular expression and returns |
| 731 | non-zero if it matches. If the regular expression is invalid or cannot |
| 732 | be compiled, an error will be generated. |
| 733 | |
| 734 | See [fossil grep](./grep.md) for details on the regexp syntax. |
| 735 | |
| 736 | <a id="reinitialize"></a>TH1 reinitialize Command |
| 737 | --------------------------------------------------- |
| 738 | |
| 739 | * reinitialize ?FLAGS? |
| @@ -741,10 +833,24 @@ | |
| 833 | |
| 834 | * submenu link LABEL URL |
| 835 | |
| 836 | Add hyperlink to the submenu of the current page. |
| 837 | |
| 838 | <a id="taintCmd"></a>TH1 taint Command |
| 839 | ----------------------------------------- |
| 840 | |
| 841 | * taint STRING |
| 842 | |
| 843 | This command returns a copy of STRING that has been marked as |
| 844 | [tainted](#taint). Tainted strings are strings which might be |
| 845 | controlled by an attacker and might contain hostile inputs and |
| 846 | are thus unsafe to use in certain contexts. For example, tainted |
| 847 | strings should not be output as part of a webpage as they might |
| 848 | contain rogue HTML or Javascript that could lead to an XSS |
| 849 | vulnerability. Similarly, tainted strings should not be run as |
| 850 | SQL since they might contain an SQL-injection vulerability. |
| 851 | |
| 852 | <a id="tclEval"></a>TH1 tclEval Command |
| 853 | ----------------------------------------- |
| 854 | |
| 855 | **This command requires the Tcl integration feature.** |
| 856 | |
| @@ -812,10 +918,22 @@ | |
| 918 | |
| 919 | * trace STRING |
| 920 | |
| 921 | Generates a TH1 trace message if TH1 tracing is enabled. |
| 922 | |
| 923 | <a id="untaintCmd"></a>TH1 taint Command |
| 924 | ----------------------------------------- |
| 925 | |
| 926 | * untaint STRING |
| 927 | |
| 928 | This command returns a copy of STRING that has been marked as |
| 929 | [untainted](#taint). Untainted strings are strings which are |
| 930 | believed to be free of potentially hostile content. Use this |
| 931 | command with caution, as it overwrites the tainted-string protection |
| 932 | mechanisms that are built into TH1. If you do not understand all |
| 933 | the implications of executing this command, then do not use it. |
| 934 | |
| 935 | <a id="unversioned_content"></a>TH1 unversioned content Command |
| 936 | ----------------------------------------------------------------- |
| 937 | |
| 938 | * unversioned content FILENAME |
| 939 | |
| 940 |