Fossil SCM
Apply the logic in/around [ec68aaf42536b4fb] to the chat search so that it does not abort, and generate an error log entry, when given characters which fts5 does not like.
Commit
b698ba99427d3a530e2d5ea7a64afd84e27074c93980d75a67d8e7bb0e277250
Parent
4711a8c4ddd2e14…
2 files changed
+3
-1
+25
-11
+3
-1
| --- src/chat.c | ||
| +++ src/chat.c | ||
| @@ -784,20 +784,22 @@ | ||
| 784 | 784 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime " |
| 785 | 785 | " FROM chat WHERE msgid=+%Q", |
| 786 | 786 | zQuery |
| 787 | 787 | ); |
| 788 | 788 | }else{ |
| 789 | + char * zPat = search_simplify_pattern(zQuery); | |
| 789 | 790 | blob_append_sql(&sql, |
| 790 | 791 | "SELECT * FROM (" |
| 791 | 792 | "SELECT c.msgid, datetime(c.mtime), c.xfrom, " |
| 792 | 793 | " highlight(chatfts1, 0, '<span class=\"match\">', '</span>'), " |
| 793 | 794 | " octet_length(c.file), c.fname, c.fmime, c.mdel, c.lmtime " |
| 794 | 795 | " FROM chatfts1(%Q) f, chat c " |
| 795 | 796 | " WHERE f.rowid=c.msgid" |
| 796 | 797 | " ORDER BY f.rowid DESC LIMIT %d" |
| 797 | - ") ORDER BY 1 ASC", zQuery, nLimit | |
| 798 | + ") ORDER BY 1 ASC", zPat, nLimit | |
| 798 | 799 | ); |
| 800 | + fossil_free(zPat); | |
| 799 | 801 | } |
| 800 | 802 | }else{ |
| 801 | 803 | blob_append_sql(&sql, |
| 802 | 804 | "SELECT msgid, datetime(mtime), xfrom, " |
| 803 | 805 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime" |
| 804 | 806 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -784,20 +784,22 @@ | |
| 784 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime " |
| 785 | " FROM chat WHERE msgid=+%Q", |
| 786 | zQuery |
| 787 | ); |
| 788 | }else{ |
| 789 | blob_append_sql(&sql, |
| 790 | "SELECT * FROM (" |
| 791 | "SELECT c.msgid, datetime(c.mtime), c.xfrom, " |
| 792 | " highlight(chatfts1, 0, '<span class=\"match\">', '</span>'), " |
| 793 | " octet_length(c.file), c.fname, c.fmime, c.mdel, c.lmtime " |
| 794 | " FROM chatfts1(%Q) f, chat c " |
| 795 | " WHERE f.rowid=c.msgid" |
| 796 | " ORDER BY f.rowid DESC LIMIT %d" |
| 797 | ") ORDER BY 1 ASC", zQuery, nLimit |
| 798 | ); |
| 799 | } |
| 800 | }else{ |
| 801 | blob_append_sql(&sql, |
| 802 | "SELECT msgid, datetime(mtime), xfrom, " |
| 803 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime" |
| 804 |
| --- src/chat.c | |
| +++ src/chat.c | |
| @@ -784,20 +784,22 @@ | |
| 784 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime " |
| 785 | " FROM chat WHERE msgid=+%Q", |
| 786 | zQuery |
| 787 | ); |
| 788 | }else{ |
| 789 | char * zPat = search_simplify_pattern(zQuery); |
| 790 | blob_append_sql(&sql, |
| 791 | "SELECT * FROM (" |
| 792 | "SELECT c.msgid, datetime(c.mtime), c.xfrom, " |
| 793 | " highlight(chatfts1, 0, '<span class=\"match\">', '</span>'), " |
| 794 | " octet_length(c.file), c.fname, c.fmime, c.mdel, c.lmtime " |
| 795 | " FROM chatfts1(%Q) f, chat c " |
| 796 | " WHERE f.rowid=c.msgid" |
| 797 | " ORDER BY f.rowid DESC LIMIT %d" |
| 798 | ") ORDER BY 1 ASC", zPat, nLimit |
| 799 | ); |
| 800 | fossil_free(zPat); |
| 801 | } |
| 802 | }else{ |
| 803 | blob_append_sql(&sql, |
| 804 | "SELECT msgid, datetime(mtime), xfrom, " |
| 805 | " xmsg, octet_length(file), fname, fmime, mdel, lmtime" |
| 806 |
+25
-11
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -975,10 +975,33 @@ | ||
| 975 | 975 | } |
| 976 | 976 | #else |
| 977 | 977 | sqlite3_result_double(context, r); |
| 978 | 978 | #endif |
| 979 | 979 | } |
| 980 | + | |
| 981 | +/* | |
| 982 | +** Expects a search pattern string. Makes a copy of the string, | |
| 983 | +** replaces all non-alphanum ASCII characters with a space, and | |
| 984 | +** lower-cases all upper-case ASCII characters. The intent is to avoid | |
| 985 | +** causing errors in FTS5 searches with inputs which contain AND, OR, | |
| 986 | +** and symbols like #. The caller is responsible for passing the | |
| 987 | +** result to fossil_free(). | |
| 988 | +*/ | |
| 989 | +char *search_simplify_pattern(const char * zPattern){ | |
| 990 | + char *zPat = mprintf("%s",zPattern); | |
| 991 | + int i; | |
| 992 | + for(i=0; zPat[i]; i++){ | |
| 993 | + if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; | |
| 994 | + if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); | |
| 995 | + } | |
| 996 | + for(i--; i>=0 && zPat[i]==' '; i--){} | |
| 997 | + if( i<0 ){ | |
| 998 | + fossil_free(zPat); | |
| 999 | + zPat = mprintf("\"\""); | |
| 1000 | + } | |
| 1001 | + return zPat; | |
| 1002 | +} | |
| 980 | 1003 | |
| 981 | 1004 | /* |
| 982 | 1005 | ** When this routine is called, there already exists a table |
| 983 | 1006 | ** |
| 984 | 1007 | ** x(label,url,score,id,snip). |
| @@ -997,25 +1020,16 @@ | ||
| 997 | 1020 | LOCAL void search_indexed( |
| 998 | 1021 | const char *zPattern, /* The query pattern */ |
| 999 | 1022 | unsigned int srchFlags /* What to search over */ |
| 1000 | 1023 | ){ |
| 1001 | 1024 | Blob sql; |
| 1002 | - char *zPat = mprintf("%s",zPattern); | |
| 1003 | - int i; | |
| 1025 | + char *zPat; | |
| 1004 | 1026 | static const char *zSnippetCall; |
| 1005 | 1027 | if( srchFlags==0 ) return; |
| 1006 | 1028 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8|SQLITE_INNOCUOUS, 0, |
| 1007 | 1029 | search_rank_sqlfunc, 0, 0); |
| 1008 | - for(i=0; zPat[i]; i++){ | |
| 1009 | - if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; | |
| 1010 | - if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); | |
| 1011 | - } | |
| 1012 | - for(i--; i>=0 && zPat[i]==' '; i--){} | |
| 1013 | - if( i<0 ){ | |
| 1014 | - fossil_free(zPat); | |
| 1015 | - zPat = mprintf("\"\""); | |
| 1016 | - } | |
| 1030 | + zPat = search_simplify_pattern(zPattern); | |
| 1017 | 1031 | blob_init(&sql, 0, 0); |
| 1018 | 1032 | if( search_index_type(0)==4 ){ |
| 1019 | 1033 | /* If this repo is still using the legacy FTS4 search index, then |
| 1020 | 1034 | ** the snippet() function is slightly different */ |
| 1021 | 1035 | zSnippetCall = "snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"; |
| 1022 | 1036 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -975,10 +975,33 @@ | |
| 975 | } |
| 976 | #else |
| 977 | sqlite3_result_double(context, r); |
| 978 | #endif |
| 979 | } |
| 980 | |
| 981 | /* |
| 982 | ** When this routine is called, there already exists a table |
| 983 | ** |
| 984 | ** x(label,url,score,id,snip). |
| @@ -997,25 +1020,16 @@ | |
| 997 | LOCAL void search_indexed( |
| 998 | const char *zPattern, /* The query pattern */ |
| 999 | unsigned int srchFlags /* What to search over */ |
| 1000 | ){ |
| 1001 | Blob sql; |
| 1002 | char *zPat = mprintf("%s",zPattern); |
| 1003 | int i; |
| 1004 | static const char *zSnippetCall; |
| 1005 | if( srchFlags==0 ) return; |
| 1006 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8|SQLITE_INNOCUOUS, 0, |
| 1007 | search_rank_sqlfunc, 0, 0); |
| 1008 | for(i=0; zPat[i]; i++){ |
| 1009 | if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; |
| 1010 | if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); |
| 1011 | } |
| 1012 | for(i--; i>=0 && zPat[i]==' '; i--){} |
| 1013 | if( i<0 ){ |
| 1014 | fossil_free(zPat); |
| 1015 | zPat = mprintf("\"\""); |
| 1016 | } |
| 1017 | blob_init(&sql, 0, 0); |
| 1018 | if( search_index_type(0)==4 ){ |
| 1019 | /* If this repo is still using the legacy FTS4 search index, then |
| 1020 | ** the snippet() function is slightly different */ |
| 1021 | zSnippetCall = "snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"; |
| 1022 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -975,10 +975,33 @@ | |
| 975 | } |
| 976 | #else |
| 977 | sqlite3_result_double(context, r); |
| 978 | #endif |
| 979 | } |
| 980 | |
| 981 | /* |
| 982 | ** Expects a search pattern string. Makes a copy of the string, |
| 983 | ** replaces all non-alphanum ASCII characters with a space, and |
| 984 | ** lower-cases all upper-case ASCII characters. The intent is to avoid |
| 985 | ** causing errors in FTS5 searches with inputs which contain AND, OR, |
| 986 | ** and symbols like #. The caller is responsible for passing the |
| 987 | ** result to fossil_free(). |
| 988 | */ |
| 989 | char *search_simplify_pattern(const char * zPattern){ |
| 990 | char *zPat = mprintf("%s",zPattern); |
| 991 | int i; |
| 992 | for(i=0; zPat[i]; i++){ |
| 993 | if( (zPat[i]&0x80)==0 && !fossil_isalnum(zPat[i]) ) zPat[i] = ' '; |
| 994 | if( fossil_isupper(zPat[i]) ) zPat[i] = fossil_tolower(zPat[i]); |
| 995 | } |
| 996 | for(i--; i>=0 && zPat[i]==' '; i--){} |
| 997 | if( i<0 ){ |
| 998 | fossil_free(zPat); |
| 999 | zPat = mprintf("\"\""); |
| 1000 | } |
| 1001 | return zPat; |
| 1002 | } |
| 1003 | |
| 1004 | /* |
| 1005 | ** When this routine is called, there already exists a table |
| 1006 | ** |
| 1007 | ** x(label,url,score,id,snip). |
| @@ -997,25 +1020,16 @@ | |
| 1020 | LOCAL void search_indexed( |
| 1021 | const char *zPattern, /* The query pattern */ |
| 1022 | unsigned int srchFlags /* What to search over */ |
| 1023 | ){ |
| 1024 | Blob sql; |
| 1025 | char *zPat; |
| 1026 | static const char *zSnippetCall; |
| 1027 | if( srchFlags==0 ) return; |
| 1028 | sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8|SQLITE_INNOCUOUS, 0, |
| 1029 | search_rank_sqlfunc, 0, 0); |
| 1030 | zPat = search_simplify_pattern(zPattern); |
| 1031 | blob_init(&sql, 0, 0); |
| 1032 | if( search_index_type(0)==4 ){ |
| 1033 | /* If this repo is still using the legacy FTS4 search index, then |
| 1034 | ** the snippet() function is slightly different */ |
| 1035 | zSnippetCall = "snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"; |
| 1036 |