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