Fossil SCM
Merge from trunk.
Commit
f60a7ed29171903f0df24366c041997ae1475024632454b0fb83737a1234505f
Parent
3eac814daa37e7d…
39 files changed
+1
+86
-24
+428
-115
+1
-1
+2
-2
+2
-1
+95
-125
+10
-4
+333
+4
-4
-1
+4
-1
+1
-1
+3
-3
+2
+29
-11
+10
-1
+16
-2
+68
-34
-1
+28
-5
+295
+56
-1
+9
-24
+3
-2
+9
-95
+20
-9
-7
+1
+5
-3
+52
+29
-72
+322
-185
+5
-1
+41
-14
+6
+8
-4
+33
-5
~
.fossil-settings/ignore-glob
~
Makefile.in
~
extsrc/shell.c
~
extsrc/sqlite3.c
~
extsrc/sqlite3.h
~
src/backlink.c
~
src/branch.c
~
src/checkin.c
~
src/checkout.c
~
src/color.c
~
src/comformat.c
~
src/configure.c
~
src/db.c
~
src/doc.c
~
src/encode.c
~
src/finfo.c
~
src/graph.c
~
src/http.c
~
src/http_ssl.c
~
src/info.c
~
src/json_config.c
~
src/main.c
~
src/manifest.c
~
src/name.c
~
src/printf.c
~
src/repolist.c
~
src/search.c
~
src/security_audit.c
~
src/setup.c
~
src/sitemap.c
~
src/statrep.c
~
src/terminal.c
~
src/timeline.c
~
src/wikiformat.c
~
www/cgi.wiki
~
www/changes.wiki
~
www/env-opts.md
~
www/fileformat.wiki
~
www/serverext.wiki
| --- .fossil-settings/ignore-glob | ||
| +++ .fossil-settings/ignore-glob | ||
| @@ -5,5 +5,6 @@ | ||
| 5 | 5 | fossil |
| 6 | 6 | fossil.exe |
| 7 | 7 | win/fossil.exe |
| 8 | 8 | *shell-see.* |
| 9 | 9 | *sqlite3-see.* |
| 10 | +bld | |
| 10 | 11 |
| --- .fossil-settings/ignore-glob | |
| +++ .fossil-settings/ignore-glob | |
| @@ -5,5 +5,6 @@ | |
| 5 | fossil |
| 6 | fossil.exe |
| 7 | win/fossil.exe |
| 8 | *shell-see.* |
| 9 | *sqlite3-see.* |
| 10 |
| --- .fossil-settings/ignore-glob | |
| +++ .fossil-settings/ignore-glob | |
| @@ -5,5 +5,6 @@ | |
| 5 | fossil |
| 6 | fossil.exe |
| 7 | win/fossil.exe |
| 8 | *shell-see.* |
| 9 | *sqlite3-see.* |
| 10 | bld |
| 11 |
No diff available
+86
-24
| --- extsrc/shell.c | ||
| +++ extsrc/shell.c | ||
| @@ -1162,10 +1162,27 @@ | ||
| 1162 | 1162 | } |
| 1163 | 1163 | } |
| 1164 | 1164 | return n; |
| 1165 | 1165 | } |
| 1166 | 1166 | #endif |
| 1167 | + | |
| 1168 | +/* | |
| 1169 | +** Check to see if z[] is a valid VT100 escape. If it is, then | |
| 1170 | +** return the number of bytes in the escape sequence. Return 0 if | |
| 1171 | +** z[] is not a VT100 escape. | |
| 1172 | +** | |
| 1173 | +** This routine assumes that z[0] is \033 (ESC). | |
| 1174 | +*/ | |
| 1175 | +static int isVt100(const unsigned char *z){ | |
| 1176 | + int i; | |
| 1177 | + if( z[1]!='[' ) return 0; | |
| 1178 | + i = 2; | |
| 1179 | + while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } | |
| 1180 | + while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } | |
| 1181 | + if( z[i]<0x40 || z[i]>0x7e ) return 0; | |
| 1182 | + return i+1; | |
| 1183 | +} | |
| 1167 | 1184 | |
| 1168 | 1185 | /* |
| 1169 | 1186 | ** Output string zUtf to stdout as w characters. If w is negative, |
| 1170 | 1187 | ** then right-justify the text. W is the width in UTF-8 characters, not |
| 1171 | 1188 | ** in bytes. This is different from the %*.*s specification in printf |
| @@ -1178,10 +1195,11 @@ | ||
| 1178 | 1195 | static void utf8_width_print(FILE *out, int w, const char *zUtf){ |
| 1179 | 1196 | const unsigned char *a = (const unsigned char*)zUtf; |
| 1180 | 1197 | unsigned char c; |
| 1181 | 1198 | int i = 0; |
| 1182 | 1199 | int n = 0; |
| 1200 | + int k; | |
| 1183 | 1201 | int aw = w<0 ? -w : w; |
| 1184 | 1202 | if( zUtf==0 ) zUtf = ""; |
| 1185 | 1203 | while( (c = a[i])!=0 ){ |
| 1186 | 1204 | if( (c&0xc0)==0xc0 ){ |
| 1187 | 1205 | int u; |
| @@ -1190,10 +1208,12 @@ | ||
| 1190 | 1208 | if( x+n>aw ){ |
| 1191 | 1209 | break; |
| 1192 | 1210 | } |
| 1193 | 1211 | i += len; |
| 1194 | 1212 | n += x; |
| 1213 | + }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){ | |
| 1214 | + i += k; | |
| 1195 | 1215 | }else if( n>=aw ){ |
| 1196 | 1216 | break; |
| 1197 | 1217 | }else{ |
| 1198 | 1218 | n++; |
| 1199 | 1219 | i++; |
| @@ -6270,12 +6290,11 @@ | ||
| 6270 | 6290 | ** start HIDDEN, |
| 6271 | 6291 | ** stop HIDDEN, |
| 6272 | 6292 | ** step HIDDEN |
| 6273 | 6293 | ** ); |
| 6274 | 6294 | ** |
| 6275 | -** The virtual table also has a rowid, logically equivalent to n+1 where | |
| 6276 | -** "n" is the ascending integer in the aforesaid production definition. | |
| 6295 | +** The virtual table also has a rowid which is an alias for the value. | |
| 6277 | 6296 | ** |
| 6278 | 6297 | ** Function arguments in queries against this virtual table are translated |
| 6279 | 6298 | ** into equality constraints against successive hidden columns. In other |
| 6280 | 6299 | ** words, the following pairs of queries are equivalent to each other: |
| 6281 | 6300 | ** |
| @@ -6326,10 +6345,11 @@ | ||
| 6326 | 6345 | /* #include "sqlite3ext.h" */ |
| 6327 | 6346 | SQLITE_EXTENSION_INIT1 |
| 6328 | 6347 | #include <assert.h> |
| 6329 | 6348 | #include <string.h> |
| 6330 | 6349 | #include <limits.h> |
| 6350 | +#include <math.h> | |
| 6331 | 6351 | |
| 6332 | 6352 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 6333 | 6353 | /* |
| 6334 | 6354 | ** Return that member of a generate_series(...) sequence whose 0-based |
| 6335 | 6355 | ** index is ix. The 0th member is given by smBase. The sequence members |
| @@ -6486,10 +6506,11 @@ | ||
| 6486 | 6506 | ){ |
| 6487 | 6507 | sqlite3_vtab *pNew; |
| 6488 | 6508 | int rc; |
| 6489 | 6509 | |
| 6490 | 6510 | /* Column numbers */ |
| 6511 | +#define SERIES_COLUMN_ROWID (-1) | |
| 6491 | 6512 | #define SERIES_COLUMN_VALUE 0 |
| 6492 | 6513 | #define SERIES_COLUMN_START 1 |
| 6493 | 6514 | #define SERIES_COLUMN_STOP 2 |
| 6494 | 6515 | #define SERIES_COLUMN_STEP 3 |
| 6495 | 6516 | |
| @@ -6573,17 +6594,15 @@ | ||
| 6573 | 6594 | #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) |
| 6574 | 6595 | #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) |
| 6575 | 6596 | #endif |
| 6576 | 6597 | |
| 6577 | 6598 | /* |
| 6578 | -** Return the rowid for the current row, logically equivalent to n+1 where | |
| 6579 | -** "n" is the ascending integer in the aforesaid production definition. | |
| 6599 | +** The rowid is the same as the value. | |
| 6580 | 6600 | */ |
| 6581 | 6601 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 6582 | 6602 | series_cursor *pCur = (series_cursor*)cur; |
| 6583 | - sqlite3_uint64 n = pCur->ss.uSeqIndexNow; | |
| 6584 | - *pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0); | |
| 6603 | + *pRowid = pCur->ss.iValueNow; | |
| 6585 | 6604 | return SQLITE_OK; |
| 6586 | 6605 | } |
| 6587 | 6606 | |
| 6588 | 6607 | /* |
| 6589 | 6608 | ** Return TRUE if the cursor has been moved off of the last |
| @@ -6692,29 +6711,56 @@ | ||
| 6692 | 6711 | if( idxNum & 0x3380 ){ |
| 6693 | 6712 | /* Extract the maximum range of output values determined by |
| 6694 | 6713 | ** constraints on the "value" column. |
| 6695 | 6714 | */ |
| 6696 | 6715 | if( idxNum & 0x0080 ){ |
| 6697 | - iMin = iMax = sqlite3_value_int64(argv[i++]); | |
| 6716 | + if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6717 | + double r = sqlite3_value_double(argv[i++]); | |
| 6718 | + if( r==ceil(r) ){ | |
| 6719 | + iMin = iMax = (sqlite3_int64)r; | |
| 6720 | + }else{ | |
| 6721 | + returnNoRows = 1; | |
| 6722 | + } | |
| 6723 | + }else{ | |
| 6724 | + iMin = iMax = sqlite3_value_int64(argv[i++]); | |
| 6725 | + } | |
| 6698 | 6726 | }else{ |
| 6699 | 6727 | if( idxNum & 0x0300 ){ |
| 6700 | - iMin = sqlite3_value_int64(argv[i++]); | |
| 6701 | - if( idxNum & 0x0200 ){ | |
| 6702 | - if( iMin==LARGEST_INT64 ){ | |
| 6703 | - returnNoRows = 1; | |
| 6728 | + if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6729 | + double r = sqlite3_value_double(argv[i++]); | |
| 6730 | + if( idxNum & 0x0200 && r==ceil(r) ){ | |
| 6731 | + iMin = (sqlite3_int64)ceil(r+1.0); | |
| 6704 | 6732 | }else{ |
| 6705 | - iMin++; | |
| 6733 | + iMin = (sqlite3_int64)ceil(r); | |
| 6734 | + } | |
| 6735 | + }else{ | |
| 6736 | + iMin = sqlite3_value_int64(argv[i++]); | |
| 6737 | + if( idxNum & 0x0200 ){ | |
| 6738 | + if( iMin==LARGEST_INT64 ){ | |
| 6739 | + returnNoRows = 1; | |
| 6740 | + }else{ | |
| 6741 | + iMin++; | |
| 6742 | + } | |
| 6706 | 6743 | } |
| 6707 | 6744 | } |
| 6708 | 6745 | } |
| 6709 | 6746 | if( idxNum & 0x3000 ){ |
| 6710 | - iMax = sqlite3_value_int64(argv[i++]); | |
| 6711 | - if( idxNum & 0x2000 ){ | |
| 6712 | - if( iMax==SMALLEST_INT64 ){ | |
| 6713 | - returnNoRows = 1; | |
| 6747 | + if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ | |
| 6748 | + double r = sqlite3_value_double(argv[i++]); | |
| 6749 | + if( (idxNum & 0x2000)!=0 && r==floor(r) ){ | |
| 6750 | + iMax = (sqlite3_int64)(r-1.0); | |
| 6714 | 6751 | }else{ |
| 6715 | - iMax--; | |
| 6752 | + iMax = (sqlite3_int64)floor(r); | |
| 6753 | + } | |
| 6754 | + }else{ | |
| 6755 | + iMax = sqlite3_value_int64(argv[i++]); | |
| 6756 | + if( idxNum & 0x2000 ){ | |
| 6757 | + if( iMax==SMALLEST_INT64 ){ | |
| 6758 | + returnNoRows = 1; | |
| 6759 | + }else{ | |
| 6760 | + iMax--; | |
| 6761 | + } | |
| 6716 | 6762 | } |
| 6717 | 6763 | } |
| 6718 | 6764 | } |
| 6719 | 6765 | if( iMin>iMax ){ |
| 6720 | 6766 | returnNoRows = 1; |
| @@ -6867,11 +6913,14 @@ | ||
| 6867 | 6913 | idxNum |= 0x40; |
| 6868 | 6914 | } |
| 6869 | 6915 | continue; |
| 6870 | 6916 | } |
| 6871 | 6917 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6872 | - if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){ | |
| 6918 | + if( (pConstraint->iColumn==SERIES_COLUMN_VALUE || | |
| 6919 | + pConstraint->iColumn==SERIES_COLUMN_ROWID) | |
| 6920 | + && pConstraint->usable | |
| 6921 | + ){ | |
| 6873 | 6922 | switch( op ){ |
| 6874 | 6923 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6875 | 6924 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6876 | 6925 | idxNum |= 0x0080; |
| 6877 | 6926 | idxNum &= ~0x3300; |
| @@ -24197,13 +24246,18 @@ | ||
| 24197 | 24246 | j++; |
| 24198 | 24247 | }while( (n&7)!=0 && n<mxWidth ); |
| 24199 | 24248 | i++; |
| 24200 | 24249 | continue; |
| 24201 | 24250 | } |
| 24202 | - n++; | |
| 24203 | - j += 3; | |
| 24204 | - i++; | |
| 24251 | + if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){ | |
| 24252 | + i += k; | |
| 24253 | + j += k; | |
| 24254 | + }else{ | |
| 24255 | + n++; | |
| 24256 | + j += 3; | |
| 24257 | + i++; | |
| 24258 | + } | |
| 24205 | 24259 | } |
| 24206 | 24260 | if( n>=mxWidth && bWordWrap ){ |
| 24207 | 24261 | /* Perhaps try to back up to a better place to break the line */ |
| 24208 | 24262 | for(k=i; k>i/2; k--){ |
| 24209 | 24263 | if( IsSpace(z[k-1]) ) break; |
| @@ -24265,13 +24319,21 @@ | ||
| 24265 | 24319 | break; |
| 24266 | 24320 | case SHELL_ESC_ASCII: |
| 24267 | 24321 | zOut[j++] = '^'; |
| 24268 | 24322 | zOut[j++] = 0x40 + c; |
| 24269 | 24323 | break; |
| 24270 | - case SHELL_ESC_OFF: | |
| 24271 | - zOut[j++] = c; | |
| 24324 | + case SHELL_ESC_OFF: { | |
| 24325 | + int nn; | |
| 24326 | + if( c==0x1b && (nn = isVt100(&z[i]))>0 ){ | |
| 24327 | + memcpy(&zOut[j], &z[i], nn); | |
| 24328 | + j += nn; | |
| 24329 | + i += nn - 1; | |
| 24330 | + }else{ | |
| 24331 | + zOut[j++] = c; | |
| 24332 | + } | |
| 24272 | 24333 | break; |
| 24334 | + } | |
| 24273 | 24335 | } |
| 24274 | 24336 | i++; |
| 24275 | 24337 | } |
| 24276 | 24338 | zOut[j] = 0; |
| 24277 | 24339 | return (char*)zOut; |
| @@ -27021,11 +27083,11 @@ | ||
| 27021 | 27083 | for(i=0; i<pgSz; i+=16){ |
| 27022 | 27084 | const u8 *aLine = aData+i; |
| 27023 | 27085 | for(j=0; j<16 && aLine[j]==0; j++){} |
| 27024 | 27086 | if( j==16 ) continue; |
| 27025 | 27087 | if( !seenPageLabel ){ |
| 27026 | - sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz); | |
| 27088 | + sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); | |
| 27027 | 27089 | seenPageLabel = 1; |
| 27028 | 27090 | } |
| 27029 | 27091 | sqlite3_fprintf(p->out, "| %5d:", i); |
| 27030 | 27092 | for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); |
| 27031 | 27093 | sqlite3_fprintf(p->out, " "); |
| 27032 | 27094 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -1162,10 +1162,27 @@ | |
| 1162 | } |
| 1163 | } |
| 1164 | return n; |
| 1165 | } |
| 1166 | #endif |
| 1167 | |
| 1168 | /* |
| 1169 | ** Output string zUtf to stdout as w characters. If w is negative, |
| 1170 | ** then right-justify the text. W is the width in UTF-8 characters, not |
| 1171 | ** in bytes. This is different from the %*.*s specification in printf |
| @@ -1178,10 +1195,11 @@ | |
| 1178 | static void utf8_width_print(FILE *out, int w, const char *zUtf){ |
| 1179 | const unsigned char *a = (const unsigned char*)zUtf; |
| 1180 | unsigned char c; |
| 1181 | int i = 0; |
| 1182 | int n = 0; |
| 1183 | int aw = w<0 ? -w : w; |
| 1184 | if( zUtf==0 ) zUtf = ""; |
| 1185 | while( (c = a[i])!=0 ){ |
| 1186 | if( (c&0xc0)==0xc0 ){ |
| 1187 | int u; |
| @@ -1190,10 +1208,12 @@ | |
| 1190 | if( x+n>aw ){ |
| 1191 | break; |
| 1192 | } |
| 1193 | i += len; |
| 1194 | n += x; |
| 1195 | }else if( n>=aw ){ |
| 1196 | break; |
| 1197 | }else{ |
| 1198 | n++; |
| 1199 | i++; |
| @@ -6270,12 +6290,11 @@ | |
| 6270 | ** start HIDDEN, |
| 6271 | ** stop HIDDEN, |
| 6272 | ** step HIDDEN |
| 6273 | ** ); |
| 6274 | ** |
| 6275 | ** The virtual table also has a rowid, logically equivalent to n+1 where |
| 6276 | ** "n" is the ascending integer in the aforesaid production definition. |
| 6277 | ** |
| 6278 | ** Function arguments in queries against this virtual table are translated |
| 6279 | ** into equality constraints against successive hidden columns. In other |
| 6280 | ** words, the following pairs of queries are equivalent to each other: |
| 6281 | ** |
| @@ -6326,10 +6345,11 @@ | |
| 6326 | /* #include "sqlite3ext.h" */ |
| 6327 | SQLITE_EXTENSION_INIT1 |
| 6328 | #include <assert.h> |
| 6329 | #include <string.h> |
| 6330 | #include <limits.h> |
| 6331 | |
| 6332 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 6333 | /* |
| 6334 | ** Return that member of a generate_series(...) sequence whose 0-based |
| 6335 | ** index is ix. The 0th member is given by smBase. The sequence members |
| @@ -6486,10 +6506,11 @@ | |
| 6486 | ){ |
| 6487 | sqlite3_vtab *pNew; |
| 6488 | int rc; |
| 6489 | |
| 6490 | /* Column numbers */ |
| 6491 | #define SERIES_COLUMN_VALUE 0 |
| 6492 | #define SERIES_COLUMN_START 1 |
| 6493 | #define SERIES_COLUMN_STOP 2 |
| 6494 | #define SERIES_COLUMN_STEP 3 |
| 6495 | |
| @@ -6573,17 +6594,15 @@ | |
| 6573 | #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) |
| 6574 | #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) |
| 6575 | #endif |
| 6576 | |
| 6577 | /* |
| 6578 | ** Return the rowid for the current row, logically equivalent to n+1 where |
| 6579 | ** "n" is the ascending integer in the aforesaid production definition. |
| 6580 | */ |
| 6581 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 6582 | series_cursor *pCur = (series_cursor*)cur; |
| 6583 | sqlite3_uint64 n = pCur->ss.uSeqIndexNow; |
| 6584 | *pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0); |
| 6585 | return SQLITE_OK; |
| 6586 | } |
| 6587 | |
| 6588 | /* |
| 6589 | ** Return TRUE if the cursor has been moved off of the last |
| @@ -6692,29 +6711,56 @@ | |
| 6692 | if( idxNum & 0x3380 ){ |
| 6693 | /* Extract the maximum range of output values determined by |
| 6694 | ** constraints on the "value" column. |
| 6695 | */ |
| 6696 | if( idxNum & 0x0080 ){ |
| 6697 | iMin = iMax = sqlite3_value_int64(argv[i++]); |
| 6698 | }else{ |
| 6699 | if( idxNum & 0x0300 ){ |
| 6700 | iMin = sqlite3_value_int64(argv[i++]); |
| 6701 | if( idxNum & 0x0200 ){ |
| 6702 | if( iMin==LARGEST_INT64 ){ |
| 6703 | returnNoRows = 1; |
| 6704 | }else{ |
| 6705 | iMin++; |
| 6706 | } |
| 6707 | } |
| 6708 | } |
| 6709 | if( idxNum & 0x3000 ){ |
| 6710 | iMax = sqlite3_value_int64(argv[i++]); |
| 6711 | if( idxNum & 0x2000 ){ |
| 6712 | if( iMax==SMALLEST_INT64 ){ |
| 6713 | returnNoRows = 1; |
| 6714 | }else{ |
| 6715 | iMax--; |
| 6716 | } |
| 6717 | } |
| 6718 | } |
| 6719 | if( iMin>iMax ){ |
| 6720 | returnNoRows = 1; |
| @@ -6867,11 +6913,14 @@ | |
| 6867 | idxNum |= 0x40; |
| 6868 | } |
| 6869 | continue; |
| 6870 | } |
| 6871 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6872 | if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){ |
| 6873 | switch( op ){ |
| 6874 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6875 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6876 | idxNum |= 0x0080; |
| 6877 | idxNum &= ~0x3300; |
| @@ -24197,13 +24246,18 @@ | |
| 24197 | j++; |
| 24198 | }while( (n&7)!=0 && n<mxWidth ); |
| 24199 | i++; |
| 24200 | continue; |
| 24201 | } |
| 24202 | n++; |
| 24203 | j += 3; |
| 24204 | i++; |
| 24205 | } |
| 24206 | if( n>=mxWidth && bWordWrap ){ |
| 24207 | /* Perhaps try to back up to a better place to break the line */ |
| 24208 | for(k=i; k>i/2; k--){ |
| 24209 | if( IsSpace(z[k-1]) ) break; |
| @@ -24265,13 +24319,21 @@ | |
| 24265 | break; |
| 24266 | case SHELL_ESC_ASCII: |
| 24267 | zOut[j++] = '^'; |
| 24268 | zOut[j++] = 0x40 + c; |
| 24269 | break; |
| 24270 | case SHELL_ESC_OFF: |
| 24271 | zOut[j++] = c; |
| 24272 | break; |
| 24273 | } |
| 24274 | i++; |
| 24275 | } |
| 24276 | zOut[j] = 0; |
| 24277 | return (char*)zOut; |
| @@ -27021,11 +27083,11 @@ | |
| 27021 | for(i=0; i<pgSz; i+=16){ |
| 27022 | const u8 *aLine = aData+i; |
| 27023 | for(j=0; j<16 && aLine[j]==0; j++){} |
| 27024 | if( j==16 ) continue; |
| 27025 | if( !seenPageLabel ){ |
| 27026 | sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz); |
| 27027 | seenPageLabel = 1; |
| 27028 | } |
| 27029 | sqlite3_fprintf(p->out, "| %5d:", i); |
| 27030 | for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); |
| 27031 | sqlite3_fprintf(p->out, " "); |
| 27032 |
| --- extsrc/shell.c | |
| +++ extsrc/shell.c | |
| @@ -1162,10 +1162,27 @@ | |
| 1162 | } |
| 1163 | } |
| 1164 | return n; |
| 1165 | } |
| 1166 | #endif |
| 1167 | |
| 1168 | /* |
| 1169 | ** Check to see if z[] is a valid VT100 escape. If it is, then |
| 1170 | ** return the number of bytes in the escape sequence. Return 0 if |
| 1171 | ** z[] is not a VT100 escape. |
| 1172 | ** |
| 1173 | ** This routine assumes that z[0] is \033 (ESC). |
| 1174 | */ |
| 1175 | static int isVt100(const unsigned char *z){ |
| 1176 | int i; |
| 1177 | if( z[1]!='[' ) return 0; |
| 1178 | i = 2; |
| 1179 | while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } |
| 1180 | while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } |
| 1181 | if( z[i]<0x40 || z[i]>0x7e ) return 0; |
| 1182 | return i+1; |
| 1183 | } |
| 1184 | |
| 1185 | /* |
| 1186 | ** Output string zUtf to stdout as w characters. If w is negative, |
| 1187 | ** then right-justify the text. W is the width in UTF-8 characters, not |
| 1188 | ** in bytes. This is different from the %*.*s specification in printf |
| @@ -1178,10 +1195,11 @@ | |
| 1195 | static void utf8_width_print(FILE *out, int w, const char *zUtf){ |
| 1196 | const unsigned char *a = (const unsigned char*)zUtf; |
| 1197 | unsigned char c; |
| 1198 | int i = 0; |
| 1199 | int n = 0; |
| 1200 | int k; |
| 1201 | int aw = w<0 ? -w : w; |
| 1202 | if( zUtf==0 ) zUtf = ""; |
| 1203 | while( (c = a[i])!=0 ){ |
| 1204 | if( (c&0xc0)==0xc0 ){ |
| 1205 | int u; |
| @@ -1190,10 +1208,12 @@ | |
| 1208 | if( x+n>aw ){ |
| 1209 | break; |
| 1210 | } |
| 1211 | i += len; |
| 1212 | n += x; |
| 1213 | }else if( c==0x1b && (k = isVt100(&a[i]))>0 ){ |
| 1214 | i += k; |
| 1215 | }else if( n>=aw ){ |
| 1216 | break; |
| 1217 | }else{ |
| 1218 | n++; |
| 1219 | i++; |
| @@ -6270,12 +6290,11 @@ | |
| 6290 | ** start HIDDEN, |
| 6291 | ** stop HIDDEN, |
| 6292 | ** step HIDDEN |
| 6293 | ** ); |
| 6294 | ** |
| 6295 | ** The virtual table also has a rowid which is an alias for the value. |
| 6296 | ** |
| 6297 | ** Function arguments in queries against this virtual table are translated |
| 6298 | ** into equality constraints against successive hidden columns. In other |
| 6299 | ** words, the following pairs of queries are equivalent to each other: |
| 6300 | ** |
| @@ -6326,10 +6345,11 @@ | |
| 6345 | /* #include "sqlite3ext.h" */ |
| 6346 | SQLITE_EXTENSION_INIT1 |
| 6347 | #include <assert.h> |
| 6348 | #include <string.h> |
| 6349 | #include <limits.h> |
| 6350 | #include <math.h> |
| 6351 | |
| 6352 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 6353 | /* |
| 6354 | ** Return that member of a generate_series(...) sequence whose 0-based |
| 6355 | ** index is ix. The 0th member is given by smBase. The sequence members |
| @@ -6486,10 +6506,11 @@ | |
| 6506 | ){ |
| 6507 | sqlite3_vtab *pNew; |
| 6508 | int rc; |
| 6509 | |
| 6510 | /* Column numbers */ |
| 6511 | #define SERIES_COLUMN_ROWID (-1) |
| 6512 | #define SERIES_COLUMN_VALUE 0 |
| 6513 | #define SERIES_COLUMN_START 1 |
| 6514 | #define SERIES_COLUMN_STOP 2 |
| 6515 | #define SERIES_COLUMN_STEP 3 |
| 6516 | |
| @@ -6573,17 +6594,15 @@ | |
| 6594 | #define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) |
| 6595 | #define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) |
| 6596 | #endif |
| 6597 | |
| 6598 | /* |
| 6599 | ** The rowid is the same as the value. |
| 6600 | */ |
| 6601 | static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 6602 | series_cursor *pCur = (series_cursor*)cur; |
| 6603 | *pRowid = pCur->ss.iValueNow; |
| 6604 | return SQLITE_OK; |
| 6605 | } |
| 6606 | |
| 6607 | /* |
| 6608 | ** Return TRUE if the cursor has been moved off of the last |
| @@ -6692,29 +6711,56 @@ | |
| 6711 | if( idxNum & 0x3380 ){ |
| 6712 | /* Extract the maximum range of output values determined by |
| 6713 | ** constraints on the "value" column. |
| 6714 | */ |
| 6715 | if( idxNum & 0x0080 ){ |
| 6716 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6717 | double r = sqlite3_value_double(argv[i++]); |
| 6718 | if( r==ceil(r) ){ |
| 6719 | iMin = iMax = (sqlite3_int64)r; |
| 6720 | }else{ |
| 6721 | returnNoRows = 1; |
| 6722 | } |
| 6723 | }else{ |
| 6724 | iMin = iMax = sqlite3_value_int64(argv[i++]); |
| 6725 | } |
| 6726 | }else{ |
| 6727 | if( idxNum & 0x0300 ){ |
| 6728 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6729 | double r = sqlite3_value_double(argv[i++]); |
| 6730 | if( idxNum & 0x0200 && r==ceil(r) ){ |
| 6731 | iMin = (sqlite3_int64)ceil(r+1.0); |
| 6732 | }else{ |
| 6733 | iMin = (sqlite3_int64)ceil(r); |
| 6734 | } |
| 6735 | }else{ |
| 6736 | iMin = sqlite3_value_int64(argv[i++]); |
| 6737 | if( idxNum & 0x0200 ){ |
| 6738 | if( iMin==LARGEST_INT64 ){ |
| 6739 | returnNoRows = 1; |
| 6740 | }else{ |
| 6741 | iMin++; |
| 6742 | } |
| 6743 | } |
| 6744 | } |
| 6745 | } |
| 6746 | if( idxNum & 0x3000 ){ |
| 6747 | if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){ |
| 6748 | double r = sqlite3_value_double(argv[i++]); |
| 6749 | if( (idxNum & 0x2000)!=0 && r==floor(r) ){ |
| 6750 | iMax = (sqlite3_int64)(r-1.0); |
| 6751 | }else{ |
| 6752 | iMax = (sqlite3_int64)floor(r); |
| 6753 | } |
| 6754 | }else{ |
| 6755 | iMax = sqlite3_value_int64(argv[i++]); |
| 6756 | if( idxNum & 0x2000 ){ |
| 6757 | if( iMax==SMALLEST_INT64 ){ |
| 6758 | returnNoRows = 1; |
| 6759 | }else{ |
| 6760 | iMax--; |
| 6761 | } |
| 6762 | } |
| 6763 | } |
| 6764 | } |
| 6765 | if( iMin>iMax ){ |
| 6766 | returnNoRows = 1; |
| @@ -6867,11 +6913,14 @@ | |
| 6913 | idxNum |= 0x40; |
| 6914 | } |
| 6915 | continue; |
| 6916 | } |
| 6917 | if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| 6918 | if( (pConstraint->iColumn==SERIES_COLUMN_VALUE || |
| 6919 | pConstraint->iColumn==SERIES_COLUMN_ROWID) |
| 6920 | && pConstraint->usable |
| 6921 | ){ |
| 6922 | switch( op ){ |
| 6923 | case SQLITE_INDEX_CONSTRAINT_EQ: |
| 6924 | case SQLITE_INDEX_CONSTRAINT_IS: { |
| 6925 | idxNum |= 0x0080; |
| 6926 | idxNum &= ~0x3300; |
| @@ -24197,13 +24246,18 @@ | |
| 24246 | j++; |
| 24247 | }while( (n&7)!=0 && n<mxWidth ); |
| 24248 | i++; |
| 24249 | continue; |
| 24250 | } |
| 24251 | if( c==0x1b && p->eEscMode==SHELL_ESC_OFF && (k = isVt100(&z[i]))>0 ){ |
| 24252 | i += k; |
| 24253 | j += k; |
| 24254 | }else{ |
| 24255 | n++; |
| 24256 | j += 3; |
| 24257 | i++; |
| 24258 | } |
| 24259 | } |
| 24260 | if( n>=mxWidth && bWordWrap ){ |
| 24261 | /* Perhaps try to back up to a better place to break the line */ |
| 24262 | for(k=i; k>i/2; k--){ |
| 24263 | if( IsSpace(z[k-1]) ) break; |
| @@ -24265,13 +24319,21 @@ | |
| 24319 | break; |
| 24320 | case SHELL_ESC_ASCII: |
| 24321 | zOut[j++] = '^'; |
| 24322 | zOut[j++] = 0x40 + c; |
| 24323 | break; |
| 24324 | case SHELL_ESC_OFF: { |
| 24325 | int nn; |
| 24326 | if( c==0x1b && (nn = isVt100(&z[i]))>0 ){ |
| 24327 | memcpy(&zOut[j], &z[i], nn); |
| 24328 | j += nn; |
| 24329 | i += nn - 1; |
| 24330 | }else{ |
| 24331 | zOut[j++] = c; |
| 24332 | } |
| 24333 | break; |
| 24334 | } |
| 24335 | } |
| 24336 | i++; |
| 24337 | } |
| 24338 | zOut[j] = 0; |
| 24339 | return (char*)zOut; |
| @@ -27021,11 +27083,11 @@ | |
| 27083 | for(i=0; i<pgSz; i+=16){ |
| 27084 | const u8 *aLine = aData+i; |
| 27085 | for(j=0; j<16 && aLine[j]==0; j++){} |
| 27086 | if( j==16 ) continue; |
| 27087 | if( !seenPageLabel ){ |
| 27088 | sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz); |
| 27089 | seenPageLabel = 1; |
| 27090 | } |
| 27091 | sqlite3_fprintf(p->out, "| %5d:", i); |
| 27092 | for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]); |
| 27093 | sqlite3_fprintf(p->out, " "); |
| 27094 |
+428
-115
| --- 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 | -** 18bda13e197e4b4ec7464b3e70012f71edc0 with changes in files: | |
| 21 | +** 121f4d97f9a855131859d342bc2ade5f8c34 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-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185" | |
| 470 | +#define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85" | |
| 471 | 471 | |
| 472 | 472 | /* |
| 473 | 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | 475 | ** |
| @@ -30270,10 +30270,12 @@ | ||
| 30270 | 30270 | */ |
| 30271 | 30271 | #include "windows.h" |
| 30272 | 30272 | |
| 30273 | 30273 | #ifdef __CYGWIN__ |
| 30274 | 30274 | # include <sys/cygwin.h> |
| 30275 | +# include <sys/stat.h> /* amalgamator: dontcache */ | |
| 30276 | +# include <unistd.h> /* amalgamator: dontcache */ | |
| 30275 | 30277 | # include <errno.h> /* amalgamator: dontcache */ |
| 30276 | 30278 | #endif |
| 30277 | 30279 | |
| 30278 | 30280 | /* |
| 30279 | 30281 | ** Determine if we are dealing with Windows NT. |
| @@ -47726,20 +47728,20 @@ | ||
| 47726 | 47728 | { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 }, |
| 47727 | 47729 | #else |
| 47728 | 47730 | { "FileTimeToLocalFileTime", (SYSCALL)0, 0 }, |
| 47729 | 47731 | #endif |
| 47730 | 47732 | |
| 47731 | -#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \ | |
| 47733 | +#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \ | |
| 47732 | 47734 | LPFILETIME))aSyscall[11].pCurrent) |
| 47733 | 47735 | |
| 47734 | 47736 | #if SQLITE_OS_WINCE |
| 47735 | 47737 | { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 }, |
| 47736 | 47738 | #else |
| 47737 | 47739 | { "FileTimeToSystemTime", (SYSCALL)0, 0 }, |
| 47738 | 47740 | #endif |
| 47739 | 47741 | |
| 47740 | -#define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \ | |
| 47742 | +#define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \ | |
| 47741 | 47743 | LPSYSTEMTIME))aSyscall[12].pCurrent) |
| 47742 | 47744 | |
| 47743 | 47745 | { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 }, |
| 47744 | 47746 | |
| 47745 | 47747 | #define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent) |
| @@ -48015,11 +48017,11 @@ | ||
| 48015 | 48017 | { "LockFile", (SYSCALL)LockFile, 0 }, |
| 48016 | 48018 | #else |
| 48017 | 48019 | { "LockFile", (SYSCALL)0, 0 }, |
| 48018 | 48020 | #endif |
| 48019 | 48021 | |
| 48020 | -#ifndef osLockFile | |
| 48022 | +#if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI) | |
| 48021 | 48023 | #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48022 | 48024 | DWORD))aSyscall[47].pCurrent) |
| 48023 | 48025 | #endif |
| 48024 | 48026 | |
| 48025 | 48027 | #if !SQLITE_OS_WINCE |
| @@ -48079,20 +48081,20 @@ | ||
| 48079 | 48081 | |
| 48080 | 48082 | #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) |
| 48081 | 48083 | |
| 48082 | 48084 | { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, |
| 48083 | 48085 | |
| 48084 | -#define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \ | |
| 48086 | +#define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ | |
| 48085 | 48087 | LPFILETIME))aSyscall[56].pCurrent) |
| 48086 | 48088 | |
| 48087 | 48089 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT |
| 48088 | 48090 | { "UnlockFile", (SYSCALL)UnlockFile, 0 }, |
| 48089 | 48091 | #else |
| 48090 | 48092 | { "UnlockFile", (SYSCALL)0, 0 }, |
| 48091 | 48093 | #endif |
| 48092 | 48094 | |
| 48093 | -#ifndef osUnlockFile | |
| 48095 | +#if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI) | |
| 48094 | 48096 | #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48095 | 48097 | DWORD))aSyscall[57].pCurrent) |
| 48096 | 48098 | #endif |
| 48097 | 48099 | |
| 48098 | 48100 | #if !SQLITE_OS_WINCE |
| @@ -48316,10 +48318,67 @@ | ||
| 48316 | 48318 | { "CancelIo", (SYSCALL)0, 0 }, |
| 48317 | 48319 | #endif |
| 48318 | 48320 | |
| 48319 | 48321 | #define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) |
| 48320 | 48322 | |
| 48323 | +#if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) | |
| 48324 | + { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, | |
| 48325 | +#else | |
| 48326 | + { "GetModuleHandleW", (SYSCALL)0, 0 }, | |
| 48327 | +#endif | |
| 48328 | + | |
| 48329 | +#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent) | |
| 48330 | + | |
| 48331 | +#ifndef _WIN32 | |
| 48332 | + { "getenv", (SYSCALL)getenv, 0 }, | |
| 48333 | +#else | |
| 48334 | + { "getenv", (SYSCALL)0, 0 }, | |
| 48335 | +#endif | |
| 48336 | + | |
| 48337 | +#define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent) | |
| 48338 | + | |
| 48339 | +#ifndef _WIN32 | |
| 48340 | + { "getcwd", (SYSCALL)getcwd, 0 }, | |
| 48341 | +#else | |
| 48342 | + { "getcwd", (SYSCALL)0, 0 }, | |
| 48343 | +#endif | |
| 48344 | + | |
| 48345 | +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent) | |
| 48346 | + | |
| 48347 | +#ifndef _WIN32 | |
| 48348 | + { "readlink", (SYSCALL)readlink, 0 }, | |
| 48349 | +#else | |
| 48350 | + { "readlink", (SYSCALL)0, 0 }, | |
| 48351 | +#endif | |
| 48352 | + | |
| 48353 | +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent) | |
| 48354 | + | |
| 48355 | +#ifndef _WIN32 | |
| 48356 | + { "lstat", (SYSCALL)lstat, 0 }, | |
| 48357 | +#else | |
| 48358 | + { "lstat", (SYSCALL)0, 0 }, | |
| 48359 | +#endif | |
| 48360 | + | |
| 48361 | +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent) | |
| 48362 | + | |
| 48363 | +#ifndef _WIN32 | |
| 48364 | + { "__errno", (SYSCALL)__errno, 0 }, | |
| 48365 | +#else | |
| 48366 | + { "__errno", (SYSCALL)0, 0 }, | |
| 48367 | +#endif | |
| 48368 | + | |
| 48369 | +#define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)()) | |
| 48370 | + | |
| 48371 | +#ifndef _WIN32 | |
| 48372 | + { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, | |
| 48373 | +#else | |
| 48374 | + { "cygwin_conv_path", (SYSCALL)0, 0 }, | |
| 48375 | +#endif | |
| 48376 | + | |
| 48377 | +#define osCygwin_conv_path ((size_t(*)(unsigned int, \ | |
| 48378 | + const void *, void *, size_t))aSyscall[88].pCurrent) | |
| 48379 | + | |
| 48321 | 48380 | }; /* End of the overrideable system calls */ |
| 48322 | 48381 | |
| 48323 | 48382 | /* |
| 48324 | 48383 | ** This is the xSetSystemCall() method of sqlite3_vfs for all of the |
| 48325 | 48384 | ** "win32" VFSes. Return SQLITE_OK upon successfully updating the |
| @@ -48489,10 +48548,11 @@ | ||
| 48489 | 48548 | sqlite3_mutex_leave(pMainMtx); |
| 48490 | 48549 | return rc; |
| 48491 | 48550 | } |
| 48492 | 48551 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48493 | 48552 | |
| 48553 | +#ifdef _WIN32 | |
| 48494 | 48554 | /* |
| 48495 | 48555 | ** This function outputs the specified (ANSI) string to the Win32 debugger |
| 48496 | 48556 | ** (if available). |
| 48497 | 48557 | */ |
| 48498 | 48558 | |
| @@ -48531,10 +48591,11 @@ | ||
| 48531 | 48591 | }else{ |
| 48532 | 48592 | fprintf(stderr, "%s", zBuf); |
| 48533 | 48593 | } |
| 48534 | 48594 | #endif |
| 48535 | 48595 | } |
| 48596 | +#endif /* _WIN32 */ | |
| 48536 | 48597 | |
| 48537 | 48598 | /* |
| 48538 | 48599 | ** The following routine suspends the current thread for at least ms |
| 48539 | 48600 | ** milliseconds. This is equivalent to the Win32 Sleep() interface. |
| 48540 | 48601 | */ |
| @@ -48831,10 +48892,11 @@ | ||
| 48831 | 48892 | SQLITE_PRIVATE void sqlite3MemSetDefault(void){ |
| 48832 | 48893 | sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32()); |
| 48833 | 48894 | } |
| 48834 | 48895 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48835 | 48896 | |
| 48897 | +#ifdef _WIN32 | |
| 48836 | 48898 | /* |
| 48837 | 48899 | ** Convert a UTF-8 string to Microsoft Unicode. |
| 48838 | 48900 | ** |
| 48839 | 48901 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48840 | 48902 | */ |
| @@ -48856,10 +48918,11 @@ | ||
| 48856 | 48918 | sqlite3_free(zWideText); |
| 48857 | 48919 | zWideText = 0; |
| 48858 | 48920 | } |
| 48859 | 48921 | return zWideText; |
| 48860 | 48922 | } |
| 48923 | +#endif /* _WIN32 */ | |
| 48861 | 48924 | |
| 48862 | 48925 | /* |
| 48863 | 48926 | ** Convert a Microsoft Unicode string to UTF-8. |
| 48864 | 48927 | ** |
| 48865 | 48928 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48890,32 +48953,33 @@ | ||
| 48890 | 48953 | ** code page. |
| 48891 | 48954 | ** |
| 48892 | 48955 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48893 | 48956 | */ |
| 48894 | 48957 | static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ |
| 48895 | - int nByte; | |
| 48958 | + int nWideChar; | |
| 48896 | 48959 | LPWSTR zMbcsText; |
| 48897 | 48960 | int codepage = useAnsi ? CP_ACP : CP_OEMCP; |
| 48898 | 48961 | |
| 48899 | - nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, | |
| 48900 | - 0)*sizeof(WCHAR); | |
| 48901 | - if( nByte==0 ){ | |
| 48962 | + nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, | |
| 48963 | + 0); | |
| 48964 | + if( nWideChar==0 ){ | |
| 48902 | 48965 | return 0; |
| 48903 | 48966 | } |
| 48904 | - zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) ); | |
| 48967 | + zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) ); | |
| 48905 | 48968 | if( zMbcsText==0 ){ |
| 48906 | 48969 | return 0; |
| 48907 | 48970 | } |
| 48908 | - nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, | |
| 48909 | - nByte); | |
| 48910 | - if( nByte==0 ){ | |
| 48971 | + nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, | |
| 48972 | + nWideChar); | |
| 48973 | + if( nWideChar==0 ){ | |
| 48911 | 48974 | sqlite3_free(zMbcsText); |
| 48912 | 48975 | zMbcsText = 0; |
| 48913 | 48976 | } |
| 48914 | 48977 | return zMbcsText; |
| 48915 | 48978 | } |
| 48916 | 48979 | |
| 48980 | +#ifdef _WIN32 | |
| 48917 | 48981 | /* |
| 48918 | 48982 | ** Convert a Microsoft Unicode string to a multi-byte character string, |
| 48919 | 48983 | ** using the ANSI or OEM code page. |
| 48920 | 48984 | ** |
| 48921 | 48985 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48939,10 +49003,11 @@ | ||
| 48939 | 49003 | sqlite3_free(zText); |
| 48940 | 49004 | zText = 0; |
| 48941 | 49005 | } |
| 48942 | 49006 | return zText; |
| 48943 | 49007 | } |
| 49008 | +#endif /* _WIN32 */ | |
| 48944 | 49009 | |
| 48945 | 49010 | /* |
| 48946 | 49011 | ** Convert a multi-byte character string to UTF-8. |
| 48947 | 49012 | ** |
| 48948 | 49013 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48958,10 +49023,11 @@ | ||
| 48958 | 49023 | zTextUtf8 = winUnicodeToUtf8(zTmpWide); |
| 48959 | 49024 | sqlite3_free(zTmpWide); |
| 48960 | 49025 | return zTextUtf8; |
| 48961 | 49026 | } |
| 48962 | 49027 | |
| 49028 | +#ifdef _WIN32 | |
| 48963 | 49029 | /* |
| 48964 | 49030 | ** Convert a UTF-8 string to a multi-byte character string. |
| 48965 | 49031 | ** |
| 48966 | 49032 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48967 | 49033 | */ |
| @@ -49007,10 +49073,11 @@ | ||
| 49007 | 49073 | #ifndef SQLITE_OMIT_AUTOINIT |
| 49008 | 49074 | if( sqlite3_initialize() ) return 0; |
| 49009 | 49075 | #endif |
| 49010 | 49076 | return winUnicodeToUtf8(zWideText); |
| 49011 | 49077 | } |
| 49078 | +#endif /* _WIN32 */ | |
| 49012 | 49079 | |
| 49013 | 49080 | /* |
| 49014 | 49081 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49015 | 49082 | */ |
| 49016 | 49083 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){ |
| @@ -49024,10 +49091,11 @@ | ||
| 49024 | 49091 | if( sqlite3_initialize() ) return 0; |
| 49025 | 49092 | #endif |
| 49026 | 49093 | return winMbcsToUtf8(zText, osAreFileApisANSI()); |
| 49027 | 49094 | } |
| 49028 | 49095 | |
| 49096 | +#ifdef _WIN32 | |
| 49029 | 49097 | /* |
| 49030 | 49098 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49031 | 49099 | */ |
| 49032 | 49100 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){ |
| 49033 | 49101 | #ifdef SQLITE_ENABLE_API_ARMOR |
| @@ -49148,10 +49216,11 @@ | ||
| 49148 | 49216 | unsigned long type, /* Identifier for directory being set or reset */ |
| 49149 | 49217 | void *zValue /* New value for directory being set or reset */ |
| 49150 | 49218 | ){ |
| 49151 | 49219 | return sqlite3_win32_set_directory16(type, zValue); |
| 49152 | 49220 | } |
| 49221 | +#endif /* _WIN32 */ | |
| 49153 | 49222 | |
| 49154 | 49223 | /* |
| 49155 | 49224 | ** The return value of winGetLastErrorMsg |
| 49156 | 49225 | ** is zero if the error message fits in the buffer, or non-zero |
| 49157 | 49226 | ** otherwise (if the message was truncated). |
| @@ -49696,13 +49765,15 @@ | ||
| 49696 | 49765 | OVERLAPPED ovlp; |
| 49697 | 49766 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49698 | 49767 | ovlp.Offset = offsetLow; |
| 49699 | 49768 | ovlp.OffsetHigh = offsetHigh; |
| 49700 | 49769 | return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49770 | +#ifdef SQLITE_WIN32_HAS_ANSI | |
| 49701 | 49771 | }else{ |
| 49702 | 49772 | return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49703 | 49773 | numBytesHigh); |
| 49774 | +#endif | |
| 49704 | 49775 | } |
| 49705 | 49776 | #endif |
| 49706 | 49777 | } |
| 49707 | 49778 | |
| 49708 | 49779 | /* |
| @@ -49806,13 +49877,15 @@ | ||
| 49806 | 49877 | OVERLAPPED ovlp; |
| 49807 | 49878 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49808 | 49879 | ovlp.Offset = offsetLow; |
| 49809 | 49880 | ovlp.OffsetHigh = offsetHigh; |
| 49810 | 49881 | return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49882 | +#ifdef SQLITE_WIN32_HAS_ANSI | |
| 49811 | 49883 | }else{ |
| 49812 | 49884 | return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49813 | 49885 | numBytesHigh); |
| 49886 | +#endif | |
| 49814 | 49887 | } |
| 49815 | 49888 | #endif |
| 49816 | 49889 | } |
| 49817 | 49890 | |
| 49818 | 49891 | /* |
| @@ -51222,18 +51295,95 @@ | ||
| 51222 | 51295 | |
| 51223 | 51296 | /* |
| 51224 | 51297 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51225 | 51298 | ** operating system wants filenames in. Space to hold the result |
| 51226 | 51299 | ** is obtained from malloc and must be freed by the calling |
| 51227 | -** function. | |
| 51300 | +** function | |
| 51301 | +** | |
| 51302 | +** On Cygwin, 3 possible input forms are accepted: | |
| 51303 | +** - If the filename starts with "<drive>:/" or "<drive>:\", | |
| 51304 | +** it is converted to UTF-16 as-is. | |
| 51305 | +** - If the filename contains '/', it is assumed to be a | |
| 51306 | +** Cygwin absolute path, it is converted to a win32 | |
| 51307 | +** absolute path in UTF-16. | |
| 51308 | +** - Otherwise it must be a filename only, the win32 filename | |
| 51309 | +** is returned in UTF-16. | |
| 51310 | +** Note: If the function cygwin_conv_path() fails, only | |
| 51311 | +** UTF-8 -> UTF-16 conversion will be done. This can only | |
| 51312 | +** happen when the file path >32k, in which case winUtf8ToUnicode() | |
| 51313 | +** will fail too. | |
| 51228 | 51314 | */ |
| 51229 | 51315 | static void *winConvertFromUtf8Filename(const char *zFilename){ |
| 51230 | 51316 | void *zConverted = 0; |
| 51231 | 51317 | if( osIsNT() ){ |
| 51318 | +#ifdef __CYGWIN__ | |
| 51319 | + int nChar; | |
| 51320 | + LPWSTR zWideFilename; | |
| 51321 | + | |
| 51322 | + if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename) | |
| 51323 | + && winIsDirSep(zFilename[2])) ){ | |
| 51324 | + int nByte; | |
| 51325 | + int convertflag = CCP_POSIX_TO_WIN_W; | |
| 51326 | + if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE; | |
| 51327 | + nByte = (int)osCygwin_conv_path(convertflag, | |
| 51328 | + zFilename, 0, 0); | |
| 51329 | + if( nByte>0 ){ | |
| 51330 | + zConverted = sqlite3MallocZero(nByte+12); | |
| 51331 | + if ( zConverted==0 ){ | |
| 51332 | + return zConverted; | |
| 51333 | + } | |
| 51334 | + zWideFilename = zConverted; | |
| 51335 | + /* Filenames should be prefixed, except when converted | |
| 51336 | + * full path already starts with "\\?\". */ | |
| 51337 | + if( osCygwin_conv_path(convertflag, zFilename, | |
| 51338 | + zWideFilename+4, nByte)==0 ){ | |
| 51339 | + if( (convertflag&CCP_RELATIVE) ){ | |
| 51340 | + memmove(zWideFilename, zWideFilename+4, nByte); | |
| 51341 | + }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){ | |
| 51342 | + memcpy(zWideFilename, L"\\\\?\\", 8); | |
| 51343 | + }else if( zWideFilename[6]!='?' ){ | |
| 51344 | + memmove(zWideFilename+6, zWideFilename+4, nByte); | |
| 51345 | + memcpy(zWideFilename, L"\\\\?\\UNC", 14); | |
| 51346 | + }else{ | |
| 51347 | + memmove(zWideFilename, zWideFilename+4, nByte); | |
| 51348 | + } | |
| 51349 | + return zConverted; | |
| 51350 | + } | |
| 51351 | + sqlite3_free(zConverted); | |
| 51352 | + } | |
| 51353 | + } | |
| 51354 | + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); | |
| 51355 | + if( nChar==0 ){ | |
| 51356 | + return 0; | |
| 51357 | + } | |
| 51358 | + zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 ); | |
| 51359 | + if( zWideFilename==0 ){ | |
| 51360 | + return 0; | |
| 51361 | + } | |
| 51362 | + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, | |
| 51363 | + zWideFilename, nChar); | |
| 51364 | + if( nChar==0 ){ | |
| 51365 | + sqlite3_free(zWideFilename); | |
| 51366 | + zWideFilename = 0; | |
| 51367 | + }else if( nChar>MAX_PATH | |
| 51368 | + && winIsDriveLetterAndColon(zFilename) | |
| 51369 | + && winIsDirSep(zFilename[2]) ){ | |
| 51370 | + memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR)); | |
| 51371 | + zWideFilename[2] = '\\'; | |
| 51372 | + memcpy(zWideFilename, L"\\\\?\\", 8); | |
| 51373 | + }else if( nChar>MAX_PATH | |
| 51374 | + && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1]) | |
| 51375 | + && zFilename[2] != '?' ){ | |
| 51376 | + memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR)); | |
| 51377 | + memcpy(zWideFilename, L"\\\\?\\UNC", 14); | |
| 51378 | + } | |
| 51379 | + zConverted = zWideFilename; | |
| 51380 | +#else | |
| 51232 | 51381 | zConverted = winUtf8ToUnicode(zFilename); |
| 51382 | +#endif /* __CYGWIN__ */ | |
| 51233 | 51383 | } |
| 51234 | -#ifdef SQLITE_WIN32_HAS_ANSI | |
| 51384 | +#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32) | |
| 51235 | 51385 | else{ |
| 51236 | 51386 | zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); |
| 51237 | 51387 | } |
| 51238 | 51388 | #endif |
| 51239 | 51389 | /* caller will handle out of memory */ |
| @@ -52058,11 +52208,11 @@ | ||
| 52058 | 52208 | ** |
| 52059 | 52209 | ** This division contains the implementation of methods on the |
| 52060 | 52210 | ** sqlite3_vfs object. |
| 52061 | 52211 | */ |
| 52062 | 52212 | |
| 52063 | -#if defined(__CYGWIN__) | |
| 52213 | +#if 0 /* No longer necessary */ | |
| 52064 | 52214 | /* |
| 52065 | 52215 | ** Convert a filename from whatever the underlying operating system |
| 52066 | 52216 | ** supports for filenames into UTF-8. Space to hold the result is |
| 52067 | 52217 | ** obtained from malloc and must be freed by the calling function. |
| 52068 | 52218 | */ |
| @@ -52091,11 +52241,18 @@ | ||
| 52091 | 52241 | int nLen = sqlite3Strlen30(zBuf); |
| 52092 | 52242 | if( nLen>0 ){ |
| 52093 | 52243 | if( winIsDirSep(zBuf[nLen-1]) ){ |
| 52094 | 52244 | return 1; |
| 52095 | 52245 | }else if( nLen+1<nBuf ){ |
| 52096 | - zBuf[nLen] = winGetDirSep(); | |
| 52246 | + if( !osGetenv ){ | |
| 52247 | + zBuf[nLen] = winGetDirSep(); | |
| 52248 | + }else if( winIsDriveLetterAndColon(zBuf) && winIsDirSep(zBuf[2]) ){ | |
| 52249 | + zBuf[nLen] = '\\'; | |
| 52250 | + zBuf[2]='\\'; | |
| 52251 | + }else{ | |
| 52252 | + zBuf[nLen] = '/'; | |
| 52253 | + } | |
| 52097 | 52254 | zBuf[nLen+1] = '\0'; |
| 52098 | 52255 | return 1; |
| 52099 | 52256 | } |
| 52100 | 52257 | } |
| 52101 | 52258 | } |
| @@ -52118,11 +52275,11 @@ | ||
| 52118 | 52275 | /* |
| 52119 | 52276 | ** Create a temporary file name and store the resulting pointer into pzBuf. |
| 52120 | 52277 | ** The pointer returned in pzBuf must be freed via sqlite3_free(). |
| 52121 | 52278 | */ |
| 52122 | 52279 | static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ |
| 52123 | - static char zChars[] = | |
| 52280 | + static const char zChars[] = | |
| 52124 | 52281 | "abcdefghijklmnopqrstuvwxyz" |
| 52125 | 52282 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 52126 | 52283 | "0123456789"; |
| 52127 | 52284 | size_t i, j; |
| 52128 | 52285 | DWORD pid; |
| @@ -52169,11 +52326,11 @@ | ||
| 52169 | 52326 | } |
| 52170 | 52327 | sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); |
| 52171 | 52328 | } |
| 52172 | 52329 | |
| 52173 | 52330 | #if defined(__CYGWIN__) |
| 52174 | - else{ | |
| 52331 | + else if( osGetenv!=NULL ){ | |
| 52175 | 52332 | static const char *azDirs[] = { |
| 52176 | 52333 | 0, /* getenv("SQLITE_TMPDIR") */ |
| 52177 | 52334 | 0, /* getenv("TMPDIR") */ |
| 52178 | 52335 | 0, /* getenv("TMP") */ |
| 52179 | 52336 | 0, /* getenv("TEMP") */ |
| @@ -52185,24 +52342,24 @@ | ||
| 52185 | 52342 | 0 /* List terminator */ |
| 52186 | 52343 | }; |
| 52187 | 52344 | unsigned int i; |
| 52188 | 52345 | const char *zDir = 0; |
| 52189 | 52346 | |
| 52190 | - if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); | |
| 52191 | - if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); | |
| 52192 | - if( !azDirs[2] ) azDirs[2] = getenv("TMP"); | |
| 52193 | - if( !azDirs[3] ) azDirs[3] = getenv("TEMP"); | |
| 52194 | - if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE"); | |
| 52347 | + if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR"); | |
| 52348 | + if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR"); | |
| 52349 | + if( !azDirs[2] ) azDirs[2] = osGetenv("TMP"); | |
| 52350 | + if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP"); | |
| 52351 | + if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE"); | |
| 52195 | 52352 | for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){ |
| 52196 | 52353 | void *zConverted; |
| 52197 | 52354 | if( zDir==0 ) continue; |
| 52198 | 52355 | /* If the path starts with a drive letter followed by the colon |
| 52199 | 52356 | ** character, assume it is already a native Win32 path; otherwise, |
| 52200 | 52357 | ** it must be converted to a native Win32 path via the Cygwin API |
| 52201 | 52358 | ** prior to using it. |
| 52202 | 52359 | */ |
| 52203 | - if( winIsDriveLetterAndColon(zDir) ){ | |
| 52360 | + { | |
| 52204 | 52361 | zConverted = winConvertFromUtf8Filename(zDir); |
| 52205 | 52362 | if( !zConverted ){ |
| 52206 | 52363 | sqlite3_free(zBuf); |
| 52207 | 52364 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52208 | 52365 | return SQLITE_IOERR_NOMEM_BKPT; |
| @@ -52211,19 +52368,20 @@ | ||
| 52211 | 52368 | sqlite3_snprintf(nMax, zBuf, "%s", zDir); |
| 52212 | 52369 | sqlite3_free(zConverted); |
| 52213 | 52370 | break; |
| 52214 | 52371 | } |
| 52215 | 52372 | sqlite3_free(zConverted); |
| 52373 | +#if 0 /* No longer necessary */ | |
| 52216 | 52374 | }else{ |
| 52217 | 52375 | zConverted = sqlite3MallocZero( nMax+1 ); |
| 52218 | 52376 | if( !zConverted ){ |
| 52219 | 52377 | sqlite3_free(zBuf); |
| 52220 | 52378 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52221 | 52379 | return SQLITE_IOERR_NOMEM_BKPT; |
| 52222 | 52380 | } |
| 52223 | - if( cygwin_conv_path( | |
| 52224 | - osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir, | |
| 52381 | + if( osCygwin_conv_path( | |
| 52382 | + CCP_POSIX_TO_WIN_W, zDir, | |
| 52225 | 52383 | zConverted, nMax+1)<0 ){ |
| 52226 | 52384 | sqlite3_free(zConverted); |
| 52227 | 52385 | sqlite3_free(zBuf); |
| 52228 | 52386 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n")); |
| 52229 | 52387 | return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno, |
| @@ -52245,14 +52403,17 @@ | ||
| 52245 | 52403 | sqlite3_free(zUtf8); |
| 52246 | 52404 | sqlite3_free(zConverted); |
| 52247 | 52405 | break; |
| 52248 | 52406 | } |
| 52249 | 52407 | sqlite3_free(zConverted); |
| 52408 | +#endif /* No longer necessary */ | |
| 52250 | 52409 | } |
| 52251 | 52410 | } |
| 52252 | 52411 | } |
| 52253 | -#elif !SQLITE_OS_WINRT && !defined(__CYGWIN__) | |
| 52412 | +#endif | |
| 52413 | + | |
| 52414 | +#if !SQLITE_OS_WINRT && defined(_WIN32) | |
| 52254 | 52415 | else if( osIsNT() ){ |
| 52255 | 52416 | char *zMulti; |
| 52256 | 52417 | LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); |
| 52257 | 52418 | if( !zWidePath ){ |
| 52258 | 52419 | sqlite3_free(zBuf); |
| @@ -52372,11 +52533,11 @@ | ||
| 52372 | 52533 | &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} |
| 52373 | 52534 | if( !rc ){ |
| 52374 | 52535 | return 0; /* Invalid name? */ |
| 52375 | 52536 | } |
| 52376 | 52537 | attr = sAttrData.dwFileAttributes; |
| 52377 | -#if SQLITE_OS_WINCE==0 | |
| 52538 | +#if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI) | |
| 52378 | 52539 | }else{ |
| 52379 | 52540 | attr = osGetFileAttributesA((char*)zConverted); |
| 52380 | 52541 | #endif |
| 52381 | 52542 | } |
| 52382 | 52543 | return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY); |
| @@ -52388,10 +52549,16 @@ | ||
| 52388 | 52549 | const char *zFilename, /* Name of file to check */ |
| 52389 | 52550 | int flags, /* Type of test to make on this file */ |
| 52390 | 52551 | int *pResOut /* OUT: Result */ |
| 52391 | 52552 | ); |
| 52392 | 52553 | |
| 52554 | +/* | |
| 52555 | +** The Windows version of xAccess() accepts an extra bit in the flags | |
| 52556 | +** parameter that prevents an anti-virus retry loop. | |
| 52557 | +*/ | |
| 52558 | +#define NORETRY 0x4000 | |
| 52559 | + | |
| 52393 | 52560 | /* |
| 52394 | 52561 | ** Open a file. |
| 52395 | 52562 | */ |
| 52396 | 52563 | static int winOpen( |
| 52397 | 52564 | sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */ |
| @@ -52412,10 +52579,11 @@ | ||
| 52412 | 52579 | winVfsAppData *pAppData; |
| 52413 | 52580 | winFile *pFile = (winFile*)id; |
| 52414 | 52581 | void *zConverted; /* Filename in OS encoding */ |
| 52415 | 52582 | const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ |
| 52416 | 52583 | int cnt = 0; |
| 52584 | + int isRO = 0; /* file is known to be accessible readonly */ | |
| 52417 | 52585 | |
| 52418 | 52586 | /* If argument zPath is a NULL pointer, this function is required to open |
| 52419 | 52587 | ** a temporary file. Use this buffer to store the file name in. |
| 52420 | 52588 | */ |
| 52421 | 52589 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| @@ -52576,13 +52744,13 @@ | ||
| 52576 | 52744 | dwShareMode, |
| 52577 | 52745 | dwCreationDisposition, |
| 52578 | 52746 | &extendedParameters); |
| 52579 | 52747 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52580 | 52748 | if( isReadWrite ){ |
| 52581 | - int rc2, isRO = 0; | |
| 52749 | + int rc2; | |
| 52582 | 52750 | sqlite3BeginBenignMalloc(); |
| 52583 | - rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); | |
| 52751 | + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); | |
| 52584 | 52752 | sqlite3EndBenignMalloc(); |
| 52585 | 52753 | if( rc2==SQLITE_OK && isRO ) break; |
| 52586 | 52754 | } |
| 52587 | 52755 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52588 | 52756 | #else |
| @@ -52593,13 +52761,13 @@ | ||
| 52593 | 52761 | dwCreationDisposition, |
| 52594 | 52762 | dwFlagsAndAttributes, |
| 52595 | 52763 | NULL); |
| 52596 | 52764 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52597 | 52765 | if( isReadWrite ){ |
| 52598 | - int rc2, isRO = 0; | |
| 52766 | + int rc2; | |
| 52599 | 52767 | sqlite3BeginBenignMalloc(); |
| 52600 | - rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); | |
| 52768 | + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); | |
| 52601 | 52769 | sqlite3EndBenignMalloc(); |
| 52602 | 52770 | if( rc2==SQLITE_OK && isRO ) break; |
| 52603 | 52771 | } |
| 52604 | 52772 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52605 | 52773 | #endif |
| @@ -52613,13 +52781,13 @@ | ||
| 52613 | 52781 | dwCreationDisposition, |
| 52614 | 52782 | dwFlagsAndAttributes, |
| 52615 | 52783 | NULL); |
| 52616 | 52784 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52617 | 52785 | if( isReadWrite ){ |
| 52618 | - int rc2, isRO = 0; | |
| 52786 | + int rc2; | |
| 52619 | 52787 | sqlite3BeginBenignMalloc(); |
| 52620 | - rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); | |
| 52788 | + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); | |
| 52621 | 52789 | sqlite3EndBenignMalloc(); |
| 52622 | 52790 | if( rc2==SQLITE_OK && isRO ) break; |
| 52623 | 52791 | } |
| 52624 | 52792 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52625 | 52793 | } |
| @@ -52630,11 +52798,11 @@ | ||
| 52630 | 52798 | dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); |
| 52631 | 52799 | |
| 52632 | 52800 | if( h==INVALID_HANDLE_VALUE ){ |
| 52633 | 52801 | sqlite3_free(zConverted); |
| 52634 | 52802 | sqlite3_free(zTmpname); |
| 52635 | - if( isReadWrite && !isExclusive ){ | |
| 52803 | + if( isReadWrite && isRO && !isExclusive ){ | |
| 52636 | 52804 | return winOpen(pVfs, zName, id, |
| 52637 | 52805 | ((flags|SQLITE_OPEN_READONLY) & |
| 52638 | 52806 | ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), |
| 52639 | 52807 | pOutFlags); |
| 52640 | 52808 | }else{ |
| @@ -52832,11 +53000,17 @@ | ||
| 52832 | 53000 | ){ |
| 52833 | 53001 | DWORD attr; |
| 52834 | 53002 | int rc = 0; |
| 52835 | 53003 | DWORD lastErrno = 0; |
| 52836 | 53004 | void *zConverted; |
| 53005 | + int noRetry = 0; /* Do not use winRetryIoerr() */ | |
| 52837 | 53006 | UNUSED_PARAMETER(pVfs); |
| 53007 | + | |
| 53008 | + if( (flags & NORETRY)!=0 ){ | |
| 53009 | + noRetry = 1; | |
| 53010 | + flags &= ~NORETRY; | |
| 53011 | + } | |
| 52838 | 53012 | |
| 52839 | 53013 | SimulateIOError( return SQLITE_IOERR_ACCESS; ); |
| 52840 | 53014 | OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", |
| 52841 | 53015 | zFilename, flags, pResOut)); |
| 52842 | 53016 | |
| @@ -52856,11 +53030,14 @@ | ||
| 52856 | 53030 | int cnt = 0; |
| 52857 | 53031 | WIN32_FILE_ATTRIBUTE_DATA sAttrData; |
| 52858 | 53032 | memset(&sAttrData, 0, sizeof(sAttrData)); |
| 52859 | 53033 | while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, |
| 52860 | 53034 | GetFileExInfoStandard, |
| 52861 | - &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} | |
| 53035 | + &sAttrData)) | |
| 53036 | + && !noRetry | |
| 53037 | + && winRetryIoerr(&cnt, &lastErrno) | |
| 53038 | + ){ /* Loop until true */} | |
| 52862 | 53039 | if( rc ){ |
| 52863 | 53040 | /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file |
| 52864 | 53041 | ** as if it does not exist. |
| 52865 | 53042 | */ |
| 52866 | 53043 | if( flags==SQLITE_ACCESS_EXISTS |
| @@ -52924,10 +53101,11 @@ | ||
| 52924 | 53101 | const char *zPathname |
| 52925 | 53102 | ){ |
| 52926 | 53103 | return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' ); |
| 52927 | 53104 | } |
| 52928 | 53105 | |
| 53106 | +#ifdef _WIN32 | |
| 52929 | 53107 | /* |
| 52930 | 53108 | ** Returns non-zero if the specified path name should be used verbatim. If |
| 52931 | 53109 | ** non-zero is returned from this function, the calling function must simply |
| 52932 | 53110 | ** use the provided path name verbatim -OR- resolve it into a full path name |
| 52933 | 53111 | ** using the GetFullPathName Win32 API function (if available). |
| @@ -52960,10 +53138,74 @@ | ||
| 52960 | 53138 | ** If we get to this point, the path name should almost certainly be a purely |
| 52961 | 53139 | ** relative one (i.e. not a UNC name, not absolute, and not volume relative). |
| 52962 | 53140 | */ |
| 52963 | 53141 | return FALSE; |
| 52964 | 53142 | } |
| 53143 | +#endif /* _WIN32 */ | |
| 53144 | + | |
| 53145 | +#ifdef __CYGWIN__ | |
| 53146 | +/* | |
| 53147 | +** Simplify a filename into its canonical form | |
| 53148 | +** by making the following changes: | |
| 53149 | +** | |
| 53150 | +** * convert any '/' to '\' (win32) or reverse (Cygwin) | |
| 53151 | +** * removing any trailing and duplicate / (except for UNC paths) | |
| 53152 | +** * convert /./ into just / | |
| 53153 | +** | |
| 53154 | +** Changes are made in-place. Return the new name length. | |
| 53155 | +** | |
| 53156 | +** The original filename is in z[0..]. If the path is shortened, | |
| 53157 | +** no-longer used bytes will be written by '\0'. | |
| 53158 | +*/ | |
| 53159 | +static void winSimplifyName(char *z){ | |
| 53160 | + int i, j; | |
| 53161 | + for(i=j=0; z[i]; ++i){ | |
| 53162 | + if( winIsDirSep(z[i]) ){ | |
| 53163 | +#if !defined(SQLITE_TEST) | |
| 53164 | + /* Some test-cases assume that "./foo" and "foo" are different */ | |
| 53165 | + if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){ | |
| 53166 | + ++i; | |
| 53167 | + continue; | |
| 53168 | + } | |
| 53169 | +#endif | |
| 53170 | + if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){ | |
| 53171 | + continue; | |
| 53172 | + } | |
| 53173 | + z[j++] = osGetenv?'/':'\\'; | |
| 53174 | + }else{ | |
| 53175 | + z[j++] = z[i]; | |
| 53176 | + } | |
| 53177 | + } | |
| 53178 | + while(j<i) z[j++] = '\0'; | |
| 53179 | +} | |
| 53180 | + | |
| 53181 | +#define SQLITE_MAX_SYMLINKS 100 | |
| 53182 | + | |
| 53183 | +static int mkFullPathname( | |
| 53184 | + const char *zPath, /* Input path */ | |
| 53185 | + char *zOut, /* Output buffer */ | |
| 53186 | + int nOut /* Allocated size of buffer zOut */ | |
| 53187 | +){ | |
| 53188 | + int nPath = sqlite3Strlen30(zPath); | |
| 53189 | + int iOff = 0; | |
| 53190 | + if( zPath[0]!='/' ){ | |
| 53191 | + if( osGetcwd(zOut, nOut-2)==0 ){ | |
| 53192 | + return winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "getcwd", zPath); | |
| 53193 | + } | |
| 53194 | + iOff = sqlite3Strlen30(zOut); | |
| 53195 | + zOut[iOff++] = '/'; | |
| 53196 | + } | |
| 53197 | + if( (iOff+nPath+1)>nOut ){ | |
| 53198 | + /* SQLite assumes that xFullPathname() nul-terminates the output buffer | |
| 53199 | + ** even if it returns an error. */ | |
| 53200 | + zOut[iOff] = '\0'; | |
| 53201 | + return SQLITE_CANTOPEN_BKPT; | |
| 53202 | + } | |
| 53203 | + sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); | |
| 53204 | + return SQLITE_OK; | |
| 53205 | +} | |
| 53206 | +#endif /* __CYGWIN__ */ | |
| 52965 | 53207 | |
| 52966 | 53208 | /* |
| 52967 | 53209 | ** Turn a relative pathname into a full pathname. Write the full |
| 52968 | 53210 | ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname |
| 52969 | 53211 | ** bytes in size. |
| @@ -52972,12 +53214,12 @@ | ||
| 52972 | 53214 | sqlite3_vfs *pVfs, /* Pointer to vfs object */ |
| 52973 | 53215 | const char *zRelative, /* Possibly relative input path */ |
| 52974 | 53216 | int nFull, /* Size of output buffer in bytes */ |
| 52975 | 53217 | char *zFull /* Output buffer */ |
| 52976 | 53218 | ){ |
| 52977 | -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) | |
| 52978 | - DWORD nByte; | |
| 53219 | +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT | |
| 53220 | + int nByte; | |
| 52979 | 53221 | void *zConverted; |
| 52980 | 53222 | char *zOut; |
| 52981 | 53223 | #endif |
| 52982 | 53224 | |
| 52983 | 53225 | /* If this path name begins with "/X:" or "\\?\", where "X" is any |
| @@ -52986,68 +53228,114 @@ | ||
| 52986 | 53228 | if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1) |
| 52987 | 53229 | || winIsLongPathPrefix(zRelative+1)) ){ |
| 52988 | 53230 | zRelative++; |
| 52989 | 53231 | } |
| 52990 | 53232 | |
| 52991 | -#if defined(__CYGWIN__) | |
| 53233 | + SimulateIOError( return SQLITE_ERROR ); | |
| 53234 | + | |
| 53235 | +#ifdef __CYGWIN__ | |
| 53236 | + if( osGetcwd ){ | |
| 53237 | + zFull[nFull-1] = '\0'; | |
| 53238 | + if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){ | |
| 53239 | + int rc = SQLITE_OK; | |
| 53240 | + int nLink = 1; /* Number of symbolic links followed so far */ | |
| 53241 | + const char *zIn = zRelative; /* Input path for each iteration of loop */ | |
| 53242 | + char *zDel = 0; | |
| 53243 | + struct stat buf; | |
| 53244 | + | |
| 53245 | + UNUSED_PARAMETER(pVfs); | |
| 53246 | + | |
| 53247 | + do { | |
| 53248 | + /* Call lstat() on path zIn. Set bLink to true if the path is a symbolic | |
| 53249 | + ** link, or false otherwise. */ | |
| 53250 | + int bLink = 0; | |
| 53251 | + if( osLstat && osReadlink ) { | |
| 53252 | + if( osLstat(zIn, &buf)!=0 ){ | |
| 53253 | + int myErrno = osErrno; | |
| 53254 | + if( myErrno!=ENOENT ){ | |
| 53255 | + rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn); | |
| 53256 | + } | |
| 53257 | + }else{ | |
| 53258 | + bLink = ((buf.st_mode & 0170000) == 0120000); | |
| 53259 | + } | |
| 53260 | + | |
| 53261 | + if( bLink ){ | |
| 53262 | + if( zDel==0 ){ | |
| 53263 | + zDel = sqlite3MallocZero(nFull); | |
| 53264 | + if( zDel==0 ) rc = SQLITE_NOMEM; | |
| 53265 | + }else if( ++nLink>SQLITE_MAX_SYMLINKS ){ | |
| 53266 | + rc = SQLITE_CANTOPEN_BKPT; | |
| 53267 | + } | |
| 53268 | + | |
| 53269 | + if( rc==SQLITE_OK ){ | |
| 53270 | + nByte = osReadlink(zIn, zDel, nFull-1); | |
| 53271 | + if( nByte ==(DWORD)-1 ){ | |
| 53272 | + rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn); | |
| 53273 | + }else{ | |
| 53274 | + if( zDel[0]!='/' ){ | |
| 53275 | + int n; | |
| 53276 | + for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); | |
| 53277 | + if( nByte+n+1>nFull ){ | |
| 53278 | + rc = SQLITE_CANTOPEN_BKPT; | |
| 53279 | + }else{ | |
| 53280 | + memmove(&zDel[n], zDel, nByte+1); | |
| 53281 | + memcpy(zDel, zIn, n); | |
| 53282 | + nByte += n; | |
| 53283 | + } | |
| 53284 | + } | |
| 53285 | + zDel[nByte] = '\0'; | |
| 53286 | + } | |
| 53287 | + } | |
| 53288 | + | |
| 53289 | + zIn = zDel; | |
| 53290 | + } | |
| 53291 | + } | |
| 53292 | + | |
| 53293 | + assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' ); | |
| 53294 | + if( rc==SQLITE_OK && zIn!=zFull ){ | |
| 53295 | + rc = mkFullPathname(zIn, zFull, nFull); | |
| 53296 | + } | |
| 53297 | + if( bLink==0 ) break; | |
| 53298 | + zIn = zFull; | |
| 53299 | + }while( rc==SQLITE_OK ); | |
| 53300 | + | |
| 53301 | + sqlite3_free(zDel); | |
| 53302 | + winSimplifyName(zFull); | |
| 53303 | + return rc; | |
| 53304 | + } | |
| 53305 | + } | |
| 53306 | +#endif /* __CYGWIN__ */ | |
| 53307 | +#if 0 /* This doesn't work correctly at all! See: | |
| 53308 | + <https://marc.info/?l=sqlite-users&m=139299149416314&w=2> | |
| 53309 | +*/ | |
| 52992 | 53310 | SimulateIOError( return SQLITE_ERROR ); |
| 52993 | 53311 | UNUSED_PARAMETER(nFull); |
| 52994 | 53312 | assert( nFull>=pVfs->mxPathname ); |
| 52995 | - if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ | |
| 52996 | - /* | |
| 52997 | - ** NOTE: We are dealing with a relative path name and the data | |
| 52998 | - ** directory has been set. Therefore, use it as the basis | |
| 52999 | - ** for converting the relative path name to an absolute | |
| 53000 | - ** one by prepending the data directory and a slash. | |
| 53001 | - */ | |
| 53002 | - char *zOut = sqlite3MallocZero( 1+(u64)pVfs->mxPathname ); | |
| 53003 | - if( !zOut ){ | |
| 53004 | - return SQLITE_IOERR_NOMEM_BKPT; | |
| 53005 | - } | |
| 53006 | - if( cygwin_conv_path( | |
| 53007 | - (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | | |
| 53008 | - CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){ | |
| 53009 | - sqlite3_free(zOut); | |
| 53010 | - return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, | |
| 53011 | - "winFullPathname1", zRelative); | |
| 53012 | - }else{ | |
| 53013 | - char *zUtf8 = winConvertToUtf8Filename(zOut); | |
| 53014 | - if( !zUtf8 ){ | |
| 53015 | - sqlite3_free(zOut); | |
| 53016 | - return SQLITE_IOERR_NOMEM_BKPT; | |
| 53017 | - } | |
| 53018 | - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", | |
| 53019 | - sqlite3_data_directory, winGetDirSep(), zUtf8); | |
| 53020 | - sqlite3_free(zUtf8); | |
| 53021 | - sqlite3_free(zOut); | |
| 53022 | - } | |
| 53023 | - }else{ | |
| 53024 | - char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); | |
| 53025 | - if( !zOut ){ | |
| 53026 | - return SQLITE_IOERR_NOMEM_BKPT; | |
| 53027 | - } | |
| 53028 | - if( cygwin_conv_path( | |
| 53029 | - (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), | |
| 53030 | - zRelative, zOut, pVfs->mxPathname+1)<0 ){ | |
| 53031 | - sqlite3_free(zOut); | |
| 53032 | - return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, | |
| 53033 | - "winFullPathname2", zRelative); | |
| 53034 | - }else{ | |
| 53035 | - char *zUtf8 = winConvertToUtf8Filename(zOut); | |
| 53036 | - if( !zUtf8 ){ | |
| 53037 | - sqlite3_free(zOut); | |
| 53038 | - return SQLITE_IOERR_NOMEM_BKPT; | |
| 53039 | - } | |
| 53040 | - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); | |
| 53041 | - sqlite3_free(zUtf8); | |
| 53042 | - sqlite3_free(zOut); | |
| 53043 | - } | |
| 53044 | - } | |
| 53045 | - return SQLITE_OK; | |
| 53046 | -#endif | |
| 53047 | - | |
| 53048 | -#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__) | |
| 53313 | + char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); | |
| 53314 | + if( !zOut ){ | |
| 53315 | + return SQLITE_IOERR_NOMEM_BKPT; | |
| 53316 | + } | |
| 53317 | + if( osCygwin_conv_path( | |
| 53318 | + CCP_POSIX_TO_WIN_W, | |
| 53319 | + zRelative, zOut, pVfs->mxPathname+1)<0 ){ | |
| 53320 | + sqlite3_free(zOut); | |
| 53321 | + return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, | |
| 53322 | + "winFullPathname2", zRelative); | |
| 53323 | + }else{ | |
| 53324 | + char *zUtf8 = winConvertToUtf8Filename(zOut); | |
| 53325 | + if( !zUtf8 ){ | |
| 53326 | + sqlite3_free(zOut); | |
| 53327 | + return SQLITE_IOERR_NOMEM_BKPT; | |
| 53328 | + } | |
| 53329 | + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); | |
| 53330 | + sqlite3_free(zUtf8); | |
| 53331 | + sqlite3_free(zOut); | |
| 53332 | + } | |
| 53333 | + return SQLITE_OK; | |
| 53334 | +#endif | |
| 53335 | + | |
| 53336 | +#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32) | |
| 53049 | 53337 | SimulateIOError( return SQLITE_ERROR ); |
| 53050 | 53338 | /* WinCE has no concept of a relative pathname, or so I am told. */ |
| 53051 | 53339 | /* WinRT has no way to convert a relative path to an absolute one. */ |
| 53052 | 53340 | if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ |
| 53053 | 53341 | /* |
| @@ -53062,11 +53350,12 @@ | ||
| 53062 | 53350 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative); |
| 53063 | 53351 | } |
| 53064 | 53352 | return SQLITE_OK; |
| 53065 | 53353 | #endif |
| 53066 | 53354 | |
| 53067 | -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) | |
| 53355 | +#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT | |
| 53356 | +#if defined(_WIN32) | |
| 53068 | 53357 | /* It's odd to simulate an io-error here, but really this is just |
| 53069 | 53358 | ** using the io-error infrastructure to test that SQLite handles this |
| 53070 | 53359 | ** function failing. This function could fail if, for example, the |
| 53071 | 53360 | ** current working directory has been unlinked. |
| 53072 | 53361 | */ |
| @@ -53080,10 +53369,11 @@ | ||
| 53080 | 53369 | */ |
| 53081 | 53370 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", |
| 53082 | 53371 | sqlite3_data_directory, winGetDirSep(), zRelative); |
| 53083 | 53372 | return SQLITE_OK; |
| 53084 | 53373 | } |
| 53374 | +#endif | |
| 53085 | 53375 | zConverted = winConvertFromUtf8Filename(zRelative); |
| 53086 | 53376 | if( zConverted==0 ){ |
| 53087 | 53377 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53088 | 53378 | } |
| 53089 | 53379 | if( osIsNT() ){ |
| @@ -53092,16 +53382,17 @@ | ||
| 53092 | 53382 | if( nByte==0 ){ |
| 53093 | 53383 | sqlite3_free(zConverted); |
| 53094 | 53384 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53095 | 53385 | "winFullPathname1", zRelative); |
| 53096 | 53386 | } |
| 53097 | - zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) + 3*sizeof(zTemp[0]) ); | |
| 53387 | + nByte += 3; | |
| 53388 | + zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); | |
| 53098 | 53389 | if( zTemp==0 ){ |
| 53099 | 53390 | sqlite3_free(zConverted); |
| 53100 | 53391 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53101 | 53392 | } |
| 53102 | - nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte+3, zTemp, 0); | |
| 53393 | + nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0); | |
| 53103 | 53394 | if( nByte==0 ){ |
| 53104 | 53395 | sqlite3_free(zConverted); |
| 53105 | 53396 | sqlite3_free(zTemp); |
| 53106 | 53397 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53107 | 53398 | "winFullPathname2", zRelative); |
| @@ -53135,11 +53426,30 @@ | ||
| 53135 | 53426 | zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); |
| 53136 | 53427 | sqlite3_free(zTemp); |
| 53137 | 53428 | } |
| 53138 | 53429 | #endif |
| 53139 | 53430 | if( zOut ){ |
| 53431 | +#ifdef __CYGWIN__ | |
| 53432 | + if( memcmp(zOut, "\\\\?\\", 4) ){ | |
| 53433 | + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); | |
| 53434 | + }else if( memcmp(zOut+4, "UNC\\", 4) ){ | |
| 53435 | + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4); | |
| 53436 | + }else{ | |
| 53437 | + char *p = zOut+6; | |
| 53438 | + *p = '\\'; | |
| 53439 | + if( osGetcwd ){ | |
| 53440 | + /* On Cygwin, UNC paths use forward slashes */ | |
| 53441 | + while( *p ){ | |
| 53442 | + if( *p=='\\' ) *p = '/'; | |
| 53443 | + ++p; | |
| 53444 | + } | |
| 53445 | + } | |
| 53446 | + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6); | |
| 53447 | + } | |
| 53448 | +#else | |
| 53140 | 53449 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); |
| 53450 | +#endif /* __CYGWIN__ */ | |
| 53141 | 53451 | sqlite3_free(zOut); |
| 53142 | 53452 | return SQLITE_OK; |
| 53143 | 53453 | }else{ |
| 53144 | 53454 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53145 | 53455 | } |
| @@ -53165,11 +53475,13 @@ | ||
| 53165 | 53475 | ** Interfaces for opening a shared library, finding entry points |
| 53166 | 53476 | ** within the shared library, and closing the shared library. |
| 53167 | 53477 | */ |
| 53168 | 53478 | static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ |
| 53169 | 53479 | HANDLE h; |
| 53170 | -#if defined(__CYGWIN__) | |
| 53480 | +#if 0 /* This doesn't work correctly at all! See: | |
| 53481 | + <https://marc.info/?l=sqlite-users&m=139299149416314&w=2> | |
| 53482 | +*/ | |
| 53171 | 53483 | int nFull = pVfs->mxPathname+1; |
| 53172 | 53484 | char *zFull = sqlite3MallocZero( nFull ); |
| 53173 | 53485 | void *zConverted = 0; |
| 53174 | 53486 | if( zFull==0 ){ |
| 53175 | 53487 | OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); |
| @@ -53532,11 +53844,11 @@ | ||
| 53532 | 53844 | }; |
| 53533 | 53845 | #endif |
| 53534 | 53846 | |
| 53535 | 53847 | /* Double-check that the aSyscall[] array has been constructed |
| 53536 | 53848 | ** correctly. See ticket [bb3a86e890c8e96ab] */ |
| 53537 | - assert( ArraySize(aSyscall)==82 ); | |
| 53849 | + assert( ArraySize(aSyscall)==89 ); | |
| 53538 | 53850 | |
| 53539 | 53851 | /* get memory map allocation granularity */ |
| 53540 | 53852 | memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); |
| 53541 | 53853 | #if SQLITE_OS_WINRT |
| 53542 | 53854 | osGetNativeSystemInfo(&winSysInfo); |
| @@ -66508,14 +66820,12 @@ | ||
| 66508 | 66820 | s2 = aIn[1]; |
| 66509 | 66821 | }else{ |
| 66510 | 66822 | s1 = s2 = 0; |
| 66511 | 66823 | } |
| 66512 | 66824 | |
| 66513 | - assert( nByte>=8 ); | |
| 66514 | - assert( (nByte&0x00000007)==0 ); | |
| 66515 | - assert( nByte<=65536 ); | |
| 66516 | - assert( nByte%4==0 ); | |
| 66825 | + /* nByte is a multiple of 8 between 8 and 65536 */ | |
| 66826 | + assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 ); | |
| 66517 | 66827 | |
| 66518 | 66828 | if( !nativeCksum ){ |
| 66519 | 66829 | do { |
| 66520 | 66830 | s1 += BYTESWAP32(aData[0]) + s2; |
| 66521 | 66831 | s2 += BYTESWAP32(aData[1]) + s1; |
| @@ -147785,10 +148095,11 @@ | ||
| 147785 | 148095 | } |
| 147786 | 148096 | |
| 147787 | 148097 | multi_select_end: |
| 147788 | 148098 | pDest->iSdst = dest.iSdst; |
| 147789 | 148099 | pDest->nSdst = dest.nSdst; |
| 148100 | + pDest->iSDParm2 = dest.iSDParm2; | |
| 147790 | 148101 | if( pDelete ){ |
| 147791 | 148102 | sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete); |
| 147792 | 148103 | } |
| 147793 | 148104 | return rc; |
| 147794 | 148105 | } |
| @@ -188065,10 +188376,17 @@ | ||
| 188065 | 188376 | ****************************************************************************** |
| 188066 | 188377 | ** |
| 188067 | 188378 | */ |
| 188068 | 188379 | #ifndef _FTSINT_H |
| 188069 | 188380 | #define _FTSINT_H |
| 188381 | + | |
| 188382 | +/* #include <assert.h> */ | |
| 188383 | +/* #include <stdlib.h> */ | |
| 188384 | +/* #include <stddef.h> */ | |
| 188385 | +/* #include <stdio.h> */ | |
| 188386 | +/* #include <string.h> */ | |
| 188387 | +/* #include <stdarg.h> */ | |
| 188070 | 188388 | |
| 188071 | 188389 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 188072 | 188390 | # define NDEBUG 1 |
| 188073 | 188391 | #endif |
| 188074 | 188392 | |
| @@ -189017,16 +189335,10 @@ | ||
| 189017 | 189335 | |
| 189018 | 189336 | #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) |
| 189019 | 189337 | # define SQLITE_CORE 1 |
| 189020 | 189338 | #endif |
| 189021 | 189339 | |
| 189022 | -/* #include <assert.h> */ | |
| 189023 | -/* #include <stdlib.h> */ | |
| 189024 | -/* #include <stddef.h> */ | |
| 189025 | -/* #include <stdio.h> */ | |
| 189026 | -/* #include <string.h> */ | |
| 189027 | -/* #include <stdarg.h> */ | |
| 189028 | 189340 | |
| 189029 | 189341 | /* #include "fts3.h" */ |
| 189030 | 189342 | #ifndef SQLITE_CORE |
| 189031 | 189343 | /* # include "sqlite3ext.h" */ |
| 189032 | 189344 | SQLITE_EXTENSION_INIT1 |
| @@ -227533,12 +227845,12 @@ | ||
| 227533 | 227845 | ){ |
| 227534 | 227846 | if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ |
| 227535 | 227847 | /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and |
| 227536 | 227848 | ** all subsequent pages to be deleted. */ |
| 227537 | 227849 | pTab->iDbTrunc = iDb; |
| 227538 | - pgno--; | |
| 227539 | - pTab->pgnoTrunc = pgno; | |
| 227850 | + pTab->pgnoTrunc = pgno-1; | |
| 227851 | + pgno = 1; | |
| 227540 | 227852 | }else{ |
| 227541 | 227853 | zErr = "bad page value"; |
| 227542 | 227854 | goto update_fail; |
| 227543 | 227855 | } |
| 227544 | 227856 | } |
| @@ -241869,11 +242181,12 @@ | ||
| 241869 | 242181 | sqlite3Fts5ParseError( |
| 241870 | 242182 | pParse, "expected integer, got \"%.*s\"", p->n, p->p |
| 241871 | 242183 | ); |
| 241872 | 242184 | return; |
| 241873 | 242185 | } |
| 241874 | - nNear = nNear * 10 + (p->p[i] - '0'); | |
| 242186 | + if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0'); | |
| 242187 | + /* ^^^^^^^^^^^^^^^--- Prevent integer overflow */ | |
| 241875 | 242188 | } |
| 241876 | 242189 | }else{ |
| 241877 | 242190 | nNear = FTS5_DEFAULT_NEARDIST; |
| 241878 | 242191 | } |
| 241879 | 242192 | pNear->nNear = nNear; |
| @@ -256775,11 +257088,11 @@ | ||
| 256775 | 257088 | int nArg, /* Number of args */ |
| 256776 | 257089 | sqlite3_value **apUnused /* Function arguments */ |
| 256777 | 257090 | ){ |
| 256778 | 257091 | assert( nArg==0 ); |
| 256779 | 257092 | UNUSED_PARAM2(nArg, apUnused); |
| 256780 | - sqlite3_result_text(pCtx, "fts5: 2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185", -1, SQLITE_TRANSIENT); | |
| 257093 | + sqlite3_result_text(pCtx, "fts5: 2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85", -1, SQLITE_TRANSIENT); | |
| 256781 | 257094 | } |
| 256782 | 257095 | |
| 256783 | 257096 | /* |
| 256784 | 257097 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 256785 | 257098 | ** |
| 256786 | 257099 |
| --- 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 | ** 18bda13e197e4b4ec7464b3e70012f71edc0 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-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -30270,10 +30270,12 @@ | |
| 30270 | */ |
| 30271 | #include "windows.h" |
| 30272 | |
| 30273 | #ifdef __CYGWIN__ |
| 30274 | # include <sys/cygwin.h> |
| 30275 | # include <errno.h> /* amalgamator: dontcache */ |
| 30276 | #endif |
| 30277 | |
| 30278 | /* |
| 30279 | ** Determine if we are dealing with Windows NT. |
| @@ -47726,20 +47728,20 @@ | |
| 47726 | { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 }, |
| 47727 | #else |
| 47728 | { "FileTimeToLocalFileTime", (SYSCALL)0, 0 }, |
| 47729 | #endif |
| 47730 | |
| 47731 | #define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \ |
| 47732 | LPFILETIME))aSyscall[11].pCurrent) |
| 47733 | |
| 47734 | #if SQLITE_OS_WINCE |
| 47735 | { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 }, |
| 47736 | #else |
| 47737 | { "FileTimeToSystemTime", (SYSCALL)0, 0 }, |
| 47738 | #endif |
| 47739 | |
| 47740 | #define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \ |
| 47741 | LPSYSTEMTIME))aSyscall[12].pCurrent) |
| 47742 | |
| 47743 | { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 }, |
| 47744 | |
| 47745 | #define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent) |
| @@ -48015,11 +48017,11 @@ | |
| 48015 | { "LockFile", (SYSCALL)LockFile, 0 }, |
| 48016 | #else |
| 48017 | { "LockFile", (SYSCALL)0, 0 }, |
| 48018 | #endif |
| 48019 | |
| 48020 | #ifndef osLockFile |
| 48021 | #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48022 | DWORD))aSyscall[47].pCurrent) |
| 48023 | #endif |
| 48024 | |
| 48025 | #if !SQLITE_OS_WINCE |
| @@ -48079,20 +48081,20 @@ | |
| 48079 | |
| 48080 | #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) |
| 48081 | |
| 48082 | { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, |
| 48083 | |
| 48084 | #define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \ |
| 48085 | LPFILETIME))aSyscall[56].pCurrent) |
| 48086 | |
| 48087 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT |
| 48088 | { "UnlockFile", (SYSCALL)UnlockFile, 0 }, |
| 48089 | #else |
| 48090 | { "UnlockFile", (SYSCALL)0, 0 }, |
| 48091 | #endif |
| 48092 | |
| 48093 | #ifndef osUnlockFile |
| 48094 | #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48095 | DWORD))aSyscall[57].pCurrent) |
| 48096 | #endif |
| 48097 | |
| 48098 | #if !SQLITE_OS_WINCE |
| @@ -48316,10 +48318,67 @@ | |
| 48316 | { "CancelIo", (SYSCALL)0, 0 }, |
| 48317 | #endif |
| 48318 | |
| 48319 | #define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) |
| 48320 | |
| 48321 | }; /* End of the overrideable system calls */ |
| 48322 | |
| 48323 | /* |
| 48324 | ** This is the xSetSystemCall() method of sqlite3_vfs for all of the |
| 48325 | ** "win32" VFSes. Return SQLITE_OK upon successfully updating the |
| @@ -48489,10 +48548,11 @@ | |
| 48489 | sqlite3_mutex_leave(pMainMtx); |
| 48490 | return rc; |
| 48491 | } |
| 48492 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48493 | |
| 48494 | /* |
| 48495 | ** This function outputs the specified (ANSI) string to the Win32 debugger |
| 48496 | ** (if available). |
| 48497 | */ |
| 48498 | |
| @@ -48531,10 +48591,11 @@ | |
| 48531 | }else{ |
| 48532 | fprintf(stderr, "%s", zBuf); |
| 48533 | } |
| 48534 | #endif |
| 48535 | } |
| 48536 | |
| 48537 | /* |
| 48538 | ** The following routine suspends the current thread for at least ms |
| 48539 | ** milliseconds. This is equivalent to the Win32 Sleep() interface. |
| 48540 | */ |
| @@ -48831,10 +48892,11 @@ | |
| 48831 | SQLITE_PRIVATE void sqlite3MemSetDefault(void){ |
| 48832 | sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32()); |
| 48833 | } |
| 48834 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48835 | |
| 48836 | /* |
| 48837 | ** Convert a UTF-8 string to Microsoft Unicode. |
| 48838 | ** |
| 48839 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48840 | */ |
| @@ -48856,10 +48918,11 @@ | |
| 48856 | sqlite3_free(zWideText); |
| 48857 | zWideText = 0; |
| 48858 | } |
| 48859 | return zWideText; |
| 48860 | } |
| 48861 | |
| 48862 | /* |
| 48863 | ** Convert a Microsoft Unicode string to UTF-8. |
| 48864 | ** |
| 48865 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48890,32 +48953,33 @@ | |
| 48890 | ** code page. |
| 48891 | ** |
| 48892 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48893 | */ |
| 48894 | static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ |
| 48895 | int nByte; |
| 48896 | LPWSTR zMbcsText; |
| 48897 | int codepage = useAnsi ? CP_ACP : CP_OEMCP; |
| 48898 | |
| 48899 | nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, |
| 48900 | 0)*sizeof(WCHAR); |
| 48901 | if( nByte==0 ){ |
| 48902 | return 0; |
| 48903 | } |
| 48904 | zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) ); |
| 48905 | if( zMbcsText==0 ){ |
| 48906 | return 0; |
| 48907 | } |
| 48908 | nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, |
| 48909 | nByte); |
| 48910 | if( nByte==0 ){ |
| 48911 | sqlite3_free(zMbcsText); |
| 48912 | zMbcsText = 0; |
| 48913 | } |
| 48914 | return zMbcsText; |
| 48915 | } |
| 48916 | |
| 48917 | /* |
| 48918 | ** Convert a Microsoft Unicode string to a multi-byte character string, |
| 48919 | ** using the ANSI or OEM code page. |
| 48920 | ** |
| 48921 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48939,10 +49003,11 @@ | |
| 48939 | sqlite3_free(zText); |
| 48940 | zText = 0; |
| 48941 | } |
| 48942 | return zText; |
| 48943 | } |
| 48944 | |
| 48945 | /* |
| 48946 | ** Convert a multi-byte character string to UTF-8. |
| 48947 | ** |
| 48948 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48958,10 +49023,11 @@ | |
| 48958 | zTextUtf8 = winUnicodeToUtf8(zTmpWide); |
| 48959 | sqlite3_free(zTmpWide); |
| 48960 | return zTextUtf8; |
| 48961 | } |
| 48962 | |
| 48963 | /* |
| 48964 | ** Convert a UTF-8 string to a multi-byte character string. |
| 48965 | ** |
| 48966 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48967 | */ |
| @@ -49007,10 +49073,11 @@ | |
| 49007 | #ifndef SQLITE_OMIT_AUTOINIT |
| 49008 | if( sqlite3_initialize() ) return 0; |
| 49009 | #endif |
| 49010 | return winUnicodeToUtf8(zWideText); |
| 49011 | } |
| 49012 | |
| 49013 | /* |
| 49014 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49015 | */ |
| 49016 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){ |
| @@ -49024,10 +49091,11 @@ | |
| 49024 | if( sqlite3_initialize() ) return 0; |
| 49025 | #endif |
| 49026 | return winMbcsToUtf8(zText, osAreFileApisANSI()); |
| 49027 | } |
| 49028 | |
| 49029 | /* |
| 49030 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49031 | */ |
| 49032 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){ |
| 49033 | #ifdef SQLITE_ENABLE_API_ARMOR |
| @@ -49148,10 +49216,11 @@ | |
| 49148 | unsigned long type, /* Identifier for directory being set or reset */ |
| 49149 | void *zValue /* New value for directory being set or reset */ |
| 49150 | ){ |
| 49151 | return sqlite3_win32_set_directory16(type, zValue); |
| 49152 | } |
| 49153 | |
| 49154 | /* |
| 49155 | ** The return value of winGetLastErrorMsg |
| 49156 | ** is zero if the error message fits in the buffer, or non-zero |
| 49157 | ** otherwise (if the message was truncated). |
| @@ -49696,13 +49765,15 @@ | |
| 49696 | OVERLAPPED ovlp; |
| 49697 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49698 | ovlp.Offset = offsetLow; |
| 49699 | ovlp.OffsetHigh = offsetHigh; |
| 49700 | return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49701 | }else{ |
| 49702 | return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49703 | numBytesHigh); |
| 49704 | } |
| 49705 | #endif |
| 49706 | } |
| 49707 | |
| 49708 | /* |
| @@ -49806,13 +49877,15 @@ | |
| 49806 | OVERLAPPED ovlp; |
| 49807 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49808 | ovlp.Offset = offsetLow; |
| 49809 | ovlp.OffsetHigh = offsetHigh; |
| 49810 | return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49811 | }else{ |
| 49812 | return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49813 | numBytesHigh); |
| 49814 | } |
| 49815 | #endif |
| 49816 | } |
| 49817 | |
| 49818 | /* |
| @@ -51222,18 +51295,95 @@ | |
| 51222 | |
| 51223 | /* |
| 51224 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51225 | ** operating system wants filenames in. Space to hold the result |
| 51226 | ** is obtained from malloc and must be freed by the calling |
| 51227 | ** function. |
| 51228 | */ |
| 51229 | static void *winConvertFromUtf8Filename(const char *zFilename){ |
| 51230 | void *zConverted = 0; |
| 51231 | if( osIsNT() ){ |
| 51232 | zConverted = winUtf8ToUnicode(zFilename); |
| 51233 | } |
| 51234 | #ifdef SQLITE_WIN32_HAS_ANSI |
| 51235 | else{ |
| 51236 | zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); |
| 51237 | } |
| 51238 | #endif |
| 51239 | /* caller will handle out of memory */ |
| @@ -52058,11 +52208,11 @@ | |
| 52058 | ** |
| 52059 | ** This division contains the implementation of methods on the |
| 52060 | ** sqlite3_vfs object. |
| 52061 | */ |
| 52062 | |
| 52063 | #if defined(__CYGWIN__) |
| 52064 | /* |
| 52065 | ** Convert a filename from whatever the underlying operating system |
| 52066 | ** supports for filenames into UTF-8. Space to hold the result is |
| 52067 | ** obtained from malloc and must be freed by the calling function. |
| 52068 | */ |
| @@ -52091,11 +52241,18 @@ | |
| 52091 | int nLen = sqlite3Strlen30(zBuf); |
| 52092 | if( nLen>0 ){ |
| 52093 | if( winIsDirSep(zBuf[nLen-1]) ){ |
| 52094 | return 1; |
| 52095 | }else if( nLen+1<nBuf ){ |
| 52096 | zBuf[nLen] = winGetDirSep(); |
| 52097 | zBuf[nLen+1] = '\0'; |
| 52098 | return 1; |
| 52099 | } |
| 52100 | } |
| 52101 | } |
| @@ -52118,11 +52275,11 @@ | |
| 52118 | /* |
| 52119 | ** Create a temporary file name and store the resulting pointer into pzBuf. |
| 52120 | ** The pointer returned in pzBuf must be freed via sqlite3_free(). |
| 52121 | */ |
| 52122 | static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ |
| 52123 | static char zChars[] = |
| 52124 | "abcdefghijklmnopqrstuvwxyz" |
| 52125 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 52126 | "0123456789"; |
| 52127 | size_t i, j; |
| 52128 | DWORD pid; |
| @@ -52169,11 +52326,11 @@ | |
| 52169 | } |
| 52170 | sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); |
| 52171 | } |
| 52172 | |
| 52173 | #if defined(__CYGWIN__) |
| 52174 | else{ |
| 52175 | static const char *azDirs[] = { |
| 52176 | 0, /* getenv("SQLITE_TMPDIR") */ |
| 52177 | 0, /* getenv("TMPDIR") */ |
| 52178 | 0, /* getenv("TMP") */ |
| 52179 | 0, /* getenv("TEMP") */ |
| @@ -52185,24 +52342,24 @@ | |
| 52185 | 0 /* List terminator */ |
| 52186 | }; |
| 52187 | unsigned int i; |
| 52188 | const char *zDir = 0; |
| 52189 | |
| 52190 | if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); |
| 52191 | if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); |
| 52192 | if( !azDirs[2] ) azDirs[2] = getenv("TMP"); |
| 52193 | if( !azDirs[3] ) azDirs[3] = getenv("TEMP"); |
| 52194 | if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE"); |
| 52195 | for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){ |
| 52196 | void *zConverted; |
| 52197 | if( zDir==0 ) continue; |
| 52198 | /* If the path starts with a drive letter followed by the colon |
| 52199 | ** character, assume it is already a native Win32 path; otherwise, |
| 52200 | ** it must be converted to a native Win32 path via the Cygwin API |
| 52201 | ** prior to using it. |
| 52202 | */ |
| 52203 | if( winIsDriveLetterAndColon(zDir) ){ |
| 52204 | zConverted = winConvertFromUtf8Filename(zDir); |
| 52205 | if( !zConverted ){ |
| 52206 | sqlite3_free(zBuf); |
| 52207 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52208 | return SQLITE_IOERR_NOMEM_BKPT; |
| @@ -52211,19 +52368,20 @@ | |
| 52211 | sqlite3_snprintf(nMax, zBuf, "%s", zDir); |
| 52212 | sqlite3_free(zConverted); |
| 52213 | break; |
| 52214 | } |
| 52215 | sqlite3_free(zConverted); |
| 52216 | }else{ |
| 52217 | zConverted = sqlite3MallocZero( nMax+1 ); |
| 52218 | if( !zConverted ){ |
| 52219 | sqlite3_free(zBuf); |
| 52220 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52221 | return SQLITE_IOERR_NOMEM_BKPT; |
| 52222 | } |
| 52223 | if( cygwin_conv_path( |
| 52224 | osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir, |
| 52225 | zConverted, nMax+1)<0 ){ |
| 52226 | sqlite3_free(zConverted); |
| 52227 | sqlite3_free(zBuf); |
| 52228 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n")); |
| 52229 | return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno, |
| @@ -52245,14 +52403,17 @@ | |
| 52245 | sqlite3_free(zUtf8); |
| 52246 | sqlite3_free(zConverted); |
| 52247 | break; |
| 52248 | } |
| 52249 | sqlite3_free(zConverted); |
| 52250 | } |
| 52251 | } |
| 52252 | } |
| 52253 | #elif !SQLITE_OS_WINRT && !defined(__CYGWIN__) |
| 52254 | else if( osIsNT() ){ |
| 52255 | char *zMulti; |
| 52256 | LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); |
| 52257 | if( !zWidePath ){ |
| 52258 | sqlite3_free(zBuf); |
| @@ -52372,11 +52533,11 @@ | |
| 52372 | &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} |
| 52373 | if( !rc ){ |
| 52374 | return 0; /* Invalid name? */ |
| 52375 | } |
| 52376 | attr = sAttrData.dwFileAttributes; |
| 52377 | #if SQLITE_OS_WINCE==0 |
| 52378 | }else{ |
| 52379 | attr = osGetFileAttributesA((char*)zConverted); |
| 52380 | #endif |
| 52381 | } |
| 52382 | return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY); |
| @@ -52388,10 +52549,16 @@ | |
| 52388 | const char *zFilename, /* Name of file to check */ |
| 52389 | int flags, /* Type of test to make on this file */ |
| 52390 | int *pResOut /* OUT: Result */ |
| 52391 | ); |
| 52392 | |
| 52393 | /* |
| 52394 | ** Open a file. |
| 52395 | */ |
| 52396 | static int winOpen( |
| 52397 | sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */ |
| @@ -52412,10 +52579,11 @@ | |
| 52412 | winVfsAppData *pAppData; |
| 52413 | winFile *pFile = (winFile*)id; |
| 52414 | void *zConverted; /* Filename in OS encoding */ |
| 52415 | const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ |
| 52416 | int cnt = 0; |
| 52417 | |
| 52418 | /* If argument zPath is a NULL pointer, this function is required to open |
| 52419 | ** a temporary file. Use this buffer to store the file name in. |
| 52420 | */ |
| 52421 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| @@ -52576,13 +52744,13 @@ | |
| 52576 | dwShareMode, |
| 52577 | dwCreationDisposition, |
| 52578 | &extendedParameters); |
| 52579 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52580 | if( isReadWrite ){ |
| 52581 | int rc2, isRO = 0; |
| 52582 | sqlite3BeginBenignMalloc(); |
| 52583 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); |
| 52584 | sqlite3EndBenignMalloc(); |
| 52585 | if( rc2==SQLITE_OK && isRO ) break; |
| 52586 | } |
| 52587 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52588 | #else |
| @@ -52593,13 +52761,13 @@ | |
| 52593 | dwCreationDisposition, |
| 52594 | dwFlagsAndAttributes, |
| 52595 | NULL); |
| 52596 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52597 | if( isReadWrite ){ |
| 52598 | int rc2, isRO = 0; |
| 52599 | sqlite3BeginBenignMalloc(); |
| 52600 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); |
| 52601 | sqlite3EndBenignMalloc(); |
| 52602 | if( rc2==SQLITE_OK && isRO ) break; |
| 52603 | } |
| 52604 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52605 | #endif |
| @@ -52613,13 +52781,13 @@ | |
| 52613 | dwCreationDisposition, |
| 52614 | dwFlagsAndAttributes, |
| 52615 | NULL); |
| 52616 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52617 | if( isReadWrite ){ |
| 52618 | int rc2, isRO = 0; |
| 52619 | sqlite3BeginBenignMalloc(); |
| 52620 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ, &isRO); |
| 52621 | sqlite3EndBenignMalloc(); |
| 52622 | if( rc2==SQLITE_OK && isRO ) break; |
| 52623 | } |
| 52624 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52625 | } |
| @@ -52630,11 +52798,11 @@ | |
| 52630 | dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); |
| 52631 | |
| 52632 | if( h==INVALID_HANDLE_VALUE ){ |
| 52633 | sqlite3_free(zConverted); |
| 52634 | sqlite3_free(zTmpname); |
| 52635 | if( isReadWrite && !isExclusive ){ |
| 52636 | return winOpen(pVfs, zName, id, |
| 52637 | ((flags|SQLITE_OPEN_READONLY) & |
| 52638 | ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), |
| 52639 | pOutFlags); |
| 52640 | }else{ |
| @@ -52832,11 +53000,17 @@ | |
| 52832 | ){ |
| 52833 | DWORD attr; |
| 52834 | int rc = 0; |
| 52835 | DWORD lastErrno = 0; |
| 52836 | void *zConverted; |
| 52837 | UNUSED_PARAMETER(pVfs); |
| 52838 | |
| 52839 | SimulateIOError( return SQLITE_IOERR_ACCESS; ); |
| 52840 | OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", |
| 52841 | zFilename, flags, pResOut)); |
| 52842 | |
| @@ -52856,11 +53030,14 @@ | |
| 52856 | int cnt = 0; |
| 52857 | WIN32_FILE_ATTRIBUTE_DATA sAttrData; |
| 52858 | memset(&sAttrData, 0, sizeof(sAttrData)); |
| 52859 | while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, |
| 52860 | GetFileExInfoStandard, |
| 52861 | &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} |
| 52862 | if( rc ){ |
| 52863 | /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file |
| 52864 | ** as if it does not exist. |
| 52865 | */ |
| 52866 | if( flags==SQLITE_ACCESS_EXISTS |
| @@ -52924,10 +53101,11 @@ | |
| 52924 | const char *zPathname |
| 52925 | ){ |
| 52926 | return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' ); |
| 52927 | } |
| 52928 | |
| 52929 | /* |
| 52930 | ** Returns non-zero if the specified path name should be used verbatim. If |
| 52931 | ** non-zero is returned from this function, the calling function must simply |
| 52932 | ** use the provided path name verbatim -OR- resolve it into a full path name |
| 52933 | ** using the GetFullPathName Win32 API function (if available). |
| @@ -52960,10 +53138,74 @@ | |
| 52960 | ** If we get to this point, the path name should almost certainly be a purely |
| 52961 | ** relative one (i.e. not a UNC name, not absolute, and not volume relative). |
| 52962 | */ |
| 52963 | return FALSE; |
| 52964 | } |
| 52965 | |
| 52966 | /* |
| 52967 | ** Turn a relative pathname into a full pathname. Write the full |
| 52968 | ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname |
| 52969 | ** bytes in size. |
| @@ -52972,12 +53214,12 @@ | |
| 52972 | sqlite3_vfs *pVfs, /* Pointer to vfs object */ |
| 52973 | const char *zRelative, /* Possibly relative input path */ |
| 52974 | int nFull, /* Size of output buffer in bytes */ |
| 52975 | char *zFull /* Output buffer */ |
| 52976 | ){ |
| 52977 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) |
| 52978 | DWORD nByte; |
| 52979 | void *zConverted; |
| 52980 | char *zOut; |
| 52981 | #endif |
| 52982 | |
| 52983 | /* If this path name begins with "/X:" or "\\?\", where "X" is any |
| @@ -52986,68 +53228,114 @@ | |
| 52986 | if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1) |
| 52987 | || winIsLongPathPrefix(zRelative+1)) ){ |
| 52988 | zRelative++; |
| 52989 | } |
| 52990 | |
| 52991 | #if defined(__CYGWIN__) |
| 52992 | SimulateIOError( return SQLITE_ERROR ); |
| 52993 | UNUSED_PARAMETER(nFull); |
| 52994 | assert( nFull>=pVfs->mxPathname ); |
| 52995 | if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ |
| 52996 | /* |
| 52997 | ** NOTE: We are dealing with a relative path name and the data |
| 52998 | ** directory has been set. Therefore, use it as the basis |
| 52999 | ** for converting the relative path name to an absolute |
| 53000 | ** one by prepending the data directory and a slash. |
| 53001 | */ |
| 53002 | char *zOut = sqlite3MallocZero( 1+(u64)pVfs->mxPathname ); |
| 53003 | if( !zOut ){ |
| 53004 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53005 | } |
| 53006 | if( cygwin_conv_path( |
| 53007 | (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | |
| 53008 | CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){ |
| 53009 | sqlite3_free(zOut); |
| 53010 | return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, |
| 53011 | "winFullPathname1", zRelative); |
| 53012 | }else{ |
| 53013 | char *zUtf8 = winConvertToUtf8Filename(zOut); |
| 53014 | if( !zUtf8 ){ |
| 53015 | sqlite3_free(zOut); |
| 53016 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53017 | } |
| 53018 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", |
| 53019 | sqlite3_data_directory, winGetDirSep(), zUtf8); |
| 53020 | sqlite3_free(zUtf8); |
| 53021 | sqlite3_free(zOut); |
| 53022 | } |
| 53023 | }else{ |
| 53024 | char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); |
| 53025 | if( !zOut ){ |
| 53026 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53027 | } |
| 53028 | if( cygwin_conv_path( |
| 53029 | (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), |
| 53030 | zRelative, zOut, pVfs->mxPathname+1)<0 ){ |
| 53031 | sqlite3_free(zOut); |
| 53032 | return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, |
| 53033 | "winFullPathname2", zRelative); |
| 53034 | }else{ |
| 53035 | char *zUtf8 = winConvertToUtf8Filename(zOut); |
| 53036 | if( !zUtf8 ){ |
| 53037 | sqlite3_free(zOut); |
| 53038 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53039 | } |
| 53040 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); |
| 53041 | sqlite3_free(zUtf8); |
| 53042 | sqlite3_free(zOut); |
| 53043 | } |
| 53044 | } |
| 53045 | return SQLITE_OK; |
| 53046 | #endif |
| 53047 | |
| 53048 | #if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__) |
| 53049 | SimulateIOError( return SQLITE_ERROR ); |
| 53050 | /* WinCE has no concept of a relative pathname, or so I am told. */ |
| 53051 | /* WinRT has no way to convert a relative path to an absolute one. */ |
| 53052 | if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ |
| 53053 | /* |
| @@ -53062,11 +53350,12 @@ | |
| 53062 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative); |
| 53063 | } |
| 53064 | return SQLITE_OK; |
| 53065 | #endif |
| 53066 | |
| 53067 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) |
| 53068 | /* It's odd to simulate an io-error here, but really this is just |
| 53069 | ** using the io-error infrastructure to test that SQLite handles this |
| 53070 | ** function failing. This function could fail if, for example, the |
| 53071 | ** current working directory has been unlinked. |
| 53072 | */ |
| @@ -53080,10 +53369,11 @@ | |
| 53080 | */ |
| 53081 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", |
| 53082 | sqlite3_data_directory, winGetDirSep(), zRelative); |
| 53083 | return SQLITE_OK; |
| 53084 | } |
| 53085 | zConverted = winConvertFromUtf8Filename(zRelative); |
| 53086 | if( zConverted==0 ){ |
| 53087 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53088 | } |
| 53089 | if( osIsNT() ){ |
| @@ -53092,16 +53382,17 @@ | |
| 53092 | if( nByte==0 ){ |
| 53093 | sqlite3_free(zConverted); |
| 53094 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53095 | "winFullPathname1", zRelative); |
| 53096 | } |
| 53097 | zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) + 3*sizeof(zTemp[0]) ); |
| 53098 | if( zTemp==0 ){ |
| 53099 | sqlite3_free(zConverted); |
| 53100 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53101 | } |
| 53102 | nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte+3, zTemp, 0); |
| 53103 | if( nByte==0 ){ |
| 53104 | sqlite3_free(zConverted); |
| 53105 | sqlite3_free(zTemp); |
| 53106 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53107 | "winFullPathname2", zRelative); |
| @@ -53135,11 +53426,30 @@ | |
| 53135 | zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); |
| 53136 | sqlite3_free(zTemp); |
| 53137 | } |
| 53138 | #endif |
| 53139 | if( zOut ){ |
| 53140 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); |
| 53141 | sqlite3_free(zOut); |
| 53142 | return SQLITE_OK; |
| 53143 | }else{ |
| 53144 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53145 | } |
| @@ -53165,11 +53475,13 @@ | |
| 53165 | ** Interfaces for opening a shared library, finding entry points |
| 53166 | ** within the shared library, and closing the shared library. |
| 53167 | */ |
| 53168 | static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ |
| 53169 | HANDLE h; |
| 53170 | #if defined(__CYGWIN__) |
| 53171 | int nFull = pVfs->mxPathname+1; |
| 53172 | char *zFull = sqlite3MallocZero( nFull ); |
| 53173 | void *zConverted = 0; |
| 53174 | if( zFull==0 ){ |
| 53175 | OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); |
| @@ -53532,11 +53844,11 @@ | |
| 53532 | }; |
| 53533 | #endif |
| 53534 | |
| 53535 | /* Double-check that the aSyscall[] array has been constructed |
| 53536 | ** correctly. See ticket [bb3a86e890c8e96ab] */ |
| 53537 | assert( ArraySize(aSyscall)==82 ); |
| 53538 | |
| 53539 | /* get memory map allocation granularity */ |
| 53540 | memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); |
| 53541 | #if SQLITE_OS_WINRT |
| 53542 | osGetNativeSystemInfo(&winSysInfo); |
| @@ -66508,14 +66820,12 @@ | |
| 66508 | s2 = aIn[1]; |
| 66509 | }else{ |
| 66510 | s1 = s2 = 0; |
| 66511 | } |
| 66512 | |
| 66513 | assert( nByte>=8 ); |
| 66514 | assert( (nByte&0x00000007)==0 ); |
| 66515 | assert( nByte<=65536 ); |
| 66516 | assert( nByte%4==0 ); |
| 66517 | |
| 66518 | if( !nativeCksum ){ |
| 66519 | do { |
| 66520 | s1 += BYTESWAP32(aData[0]) + s2; |
| 66521 | s2 += BYTESWAP32(aData[1]) + s1; |
| @@ -147785,10 +148095,11 @@ | |
| 147785 | } |
| 147786 | |
| 147787 | multi_select_end: |
| 147788 | pDest->iSdst = dest.iSdst; |
| 147789 | pDest->nSdst = dest.nSdst; |
| 147790 | if( pDelete ){ |
| 147791 | sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete); |
| 147792 | } |
| 147793 | return rc; |
| 147794 | } |
| @@ -188065,10 +188376,17 @@ | |
| 188065 | ****************************************************************************** |
| 188066 | ** |
| 188067 | */ |
| 188068 | #ifndef _FTSINT_H |
| 188069 | #define _FTSINT_H |
| 188070 | |
| 188071 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 188072 | # define NDEBUG 1 |
| 188073 | #endif |
| 188074 | |
| @@ -189017,16 +189335,10 @@ | |
| 189017 | |
| 189018 | #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) |
| 189019 | # define SQLITE_CORE 1 |
| 189020 | #endif |
| 189021 | |
| 189022 | /* #include <assert.h> */ |
| 189023 | /* #include <stdlib.h> */ |
| 189024 | /* #include <stddef.h> */ |
| 189025 | /* #include <stdio.h> */ |
| 189026 | /* #include <string.h> */ |
| 189027 | /* #include <stdarg.h> */ |
| 189028 | |
| 189029 | /* #include "fts3.h" */ |
| 189030 | #ifndef SQLITE_CORE |
| 189031 | /* # include "sqlite3ext.h" */ |
| 189032 | SQLITE_EXTENSION_INIT1 |
| @@ -227533,12 +227845,12 @@ | |
| 227533 | ){ |
| 227534 | if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ |
| 227535 | /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and |
| 227536 | ** all subsequent pages to be deleted. */ |
| 227537 | pTab->iDbTrunc = iDb; |
| 227538 | pgno--; |
| 227539 | pTab->pgnoTrunc = pgno; |
| 227540 | }else{ |
| 227541 | zErr = "bad page value"; |
| 227542 | goto update_fail; |
| 227543 | } |
| 227544 | } |
| @@ -241869,11 +242181,12 @@ | |
| 241869 | sqlite3Fts5ParseError( |
| 241870 | pParse, "expected integer, got \"%.*s\"", p->n, p->p |
| 241871 | ); |
| 241872 | return; |
| 241873 | } |
| 241874 | nNear = nNear * 10 + (p->p[i] - '0'); |
| 241875 | } |
| 241876 | }else{ |
| 241877 | nNear = FTS5_DEFAULT_NEARDIST; |
| 241878 | } |
| 241879 | pNear->nNear = nNear; |
| @@ -256775,11 +257088,11 @@ | |
| 256775 | int nArg, /* Number of args */ |
| 256776 | sqlite3_value **apUnused /* Function arguments */ |
| 256777 | ){ |
| 256778 | assert( nArg==0 ); |
| 256779 | UNUSED_PARAM2(nArg, apUnused); |
| 256780 | sqlite3_result_text(pCtx, "fts5: 2025-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185", -1, SQLITE_TRANSIENT); |
| 256781 | } |
| 256782 | |
| 256783 | /* |
| 256784 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 256785 | ** |
| 256786 |
| --- 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 | ** 121f4d97f9a855131859d342bc2ade5f8c34 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-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85" |
| 471 | |
| 472 | /* |
| 473 | ** CAPI3REF: Run-Time Library Version Numbers |
| 474 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 475 | ** |
| @@ -30270,10 +30270,12 @@ | |
| 30270 | */ |
| 30271 | #include "windows.h" |
| 30272 | |
| 30273 | #ifdef __CYGWIN__ |
| 30274 | # include <sys/cygwin.h> |
| 30275 | # include <sys/stat.h> /* amalgamator: dontcache */ |
| 30276 | # include <unistd.h> /* amalgamator: dontcache */ |
| 30277 | # include <errno.h> /* amalgamator: dontcache */ |
| 30278 | #endif |
| 30279 | |
| 30280 | /* |
| 30281 | ** Determine if we are dealing with Windows NT. |
| @@ -47726,20 +47728,20 @@ | |
| 47728 | { "FileTimeToLocalFileTime", (SYSCALL)FileTimeToLocalFileTime, 0 }, |
| 47729 | #else |
| 47730 | { "FileTimeToLocalFileTime", (SYSCALL)0, 0 }, |
| 47731 | #endif |
| 47732 | |
| 47733 | #define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \ |
| 47734 | LPFILETIME))aSyscall[11].pCurrent) |
| 47735 | |
| 47736 | #if SQLITE_OS_WINCE |
| 47737 | { "FileTimeToSystemTime", (SYSCALL)FileTimeToSystemTime, 0 }, |
| 47738 | #else |
| 47739 | { "FileTimeToSystemTime", (SYSCALL)0, 0 }, |
| 47740 | #endif |
| 47741 | |
| 47742 | #define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \ |
| 47743 | LPSYSTEMTIME))aSyscall[12].pCurrent) |
| 47744 | |
| 47745 | { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 }, |
| 47746 | |
| 47747 | #define osFlushFileBuffers ((BOOL(WINAPI*)(HANDLE))aSyscall[13].pCurrent) |
| @@ -48015,11 +48017,11 @@ | |
| 48017 | { "LockFile", (SYSCALL)LockFile, 0 }, |
| 48018 | #else |
| 48019 | { "LockFile", (SYSCALL)0, 0 }, |
| 48020 | #endif |
| 48021 | |
| 48022 | #if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI) |
| 48023 | #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48024 | DWORD))aSyscall[47].pCurrent) |
| 48025 | #endif |
| 48026 | |
| 48027 | #if !SQLITE_OS_WINCE |
| @@ -48079,20 +48081,20 @@ | |
| 48081 | |
| 48082 | #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) |
| 48083 | |
| 48084 | { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, |
| 48085 | |
| 48086 | #define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ |
| 48087 | LPFILETIME))aSyscall[56].pCurrent) |
| 48088 | |
| 48089 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT |
| 48090 | { "UnlockFile", (SYSCALL)UnlockFile, 0 }, |
| 48091 | #else |
| 48092 | { "UnlockFile", (SYSCALL)0, 0 }, |
| 48093 | #endif |
| 48094 | |
| 48095 | #if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI) |
| 48096 | #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ |
| 48097 | DWORD))aSyscall[57].pCurrent) |
| 48098 | #endif |
| 48099 | |
| 48100 | #if !SQLITE_OS_WINCE |
| @@ -48316,10 +48318,67 @@ | |
| 48318 | { "CancelIo", (SYSCALL)0, 0 }, |
| 48319 | #endif |
| 48320 | |
| 48321 | #define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[81].pCurrent) |
| 48322 | |
| 48323 | #if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) |
| 48324 | { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, |
| 48325 | #else |
| 48326 | { "GetModuleHandleW", (SYSCALL)0, 0 }, |
| 48327 | #endif |
| 48328 | |
| 48329 | #define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[82].pCurrent) |
| 48330 | |
| 48331 | #ifndef _WIN32 |
| 48332 | { "getenv", (SYSCALL)getenv, 0 }, |
| 48333 | #else |
| 48334 | { "getenv", (SYSCALL)0, 0 }, |
| 48335 | #endif |
| 48336 | |
| 48337 | #define osGetenv ((const char *(*)(const char *))aSyscall[83].pCurrent) |
| 48338 | |
| 48339 | #ifndef _WIN32 |
| 48340 | { "getcwd", (SYSCALL)getcwd, 0 }, |
| 48341 | #else |
| 48342 | { "getcwd", (SYSCALL)0, 0 }, |
| 48343 | #endif |
| 48344 | |
| 48345 | #define osGetcwd ((char*(*)(char*,size_t))aSyscall[84].pCurrent) |
| 48346 | |
| 48347 | #ifndef _WIN32 |
| 48348 | { "readlink", (SYSCALL)readlink, 0 }, |
| 48349 | #else |
| 48350 | { "readlink", (SYSCALL)0, 0 }, |
| 48351 | #endif |
| 48352 | |
| 48353 | #define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[85].pCurrent) |
| 48354 | |
| 48355 | #ifndef _WIN32 |
| 48356 | { "lstat", (SYSCALL)lstat, 0 }, |
| 48357 | #else |
| 48358 | { "lstat", (SYSCALL)0, 0 }, |
| 48359 | #endif |
| 48360 | |
| 48361 | #define osLstat ((int(*)(const char*,struct stat*))aSyscall[86].pCurrent) |
| 48362 | |
| 48363 | #ifndef _WIN32 |
| 48364 | { "__errno", (SYSCALL)__errno, 0 }, |
| 48365 | #else |
| 48366 | { "__errno", (SYSCALL)0, 0 }, |
| 48367 | #endif |
| 48368 | |
| 48369 | #define osErrno (*((int*(*)(void))aSyscall[87].pCurrent)()) |
| 48370 | |
| 48371 | #ifndef _WIN32 |
| 48372 | { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, |
| 48373 | #else |
| 48374 | { "cygwin_conv_path", (SYSCALL)0, 0 }, |
| 48375 | #endif |
| 48376 | |
| 48377 | #define osCygwin_conv_path ((size_t(*)(unsigned int, \ |
| 48378 | const void *, void *, size_t))aSyscall[88].pCurrent) |
| 48379 | |
| 48380 | }; /* End of the overrideable system calls */ |
| 48381 | |
| 48382 | /* |
| 48383 | ** This is the xSetSystemCall() method of sqlite3_vfs for all of the |
| 48384 | ** "win32" VFSes. Return SQLITE_OK upon successfully updating the |
| @@ -48489,10 +48548,11 @@ | |
| 48548 | sqlite3_mutex_leave(pMainMtx); |
| 48549 | return rc; |
| 48550 | } |
| 48551 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48552 | |
| 48553 | #ifdef _WIN32 |
| 48554 | /* |
| 48555 | ** This function outputs the specified (ANSI) string to the Win32 debugger |
| 48556 | ** (if available). |
| 48557 | */ |
| 48558 | |
| @@ -48531,10 +48591,11 @@ | |
| 48591 | }else{ |
| 48592 | fprintf(stderr, "%s", zBuf); |
| 48593 | } |
| 48594 | #endif |
| 48595 | } |
| 48596 | #endif /* _WIN32 */ |
| 48597 | |
| 48598 | /* |
| 48599 | ** The following routine suspends the current thread for at least ms |
| 48600 | ** milliseconds. This is equivalent to the Win32 Sleep() interface. |
| 48601 | */ |
| @@ -48831,10 +48892,11 @@ | |
| 48892 | SQLITE_PRIVATE void sqlite3MemSetDefault(void){ |
| 48893 | sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32()); |
| 48894 | } |
| 48895 | #endif /* SQLITE_WIN32_MALLOC */ |
| 48896 | |
| 48897 | #ifdef _WIN32 |
| 48898 | /* |
| 48899 | ** Convert a UTF-8 string to Microsoft Unicode. |
| 48900 | ** |
| 48901 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48902 | */ |
| @@ -48856,10 +48918,11 @@ | |
| 48918 | sqlite3_free(zWideText); |
| 48919 | zWideText = 0; |
| 48920 | } |
| 48921 | return zWideText; |
| 48922 | } |
| 48923 | #endif /* _WIN32 */ |
| 48924 | |
| 48925 | /* |
| 48926 | ** Convert a Microsoft Unicode string to UTF-8. |
| 48927 | ** |
| 48928 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48890,32 +48953,33 @@ | |
| 48953 | ** code page. |
| 48954 | ** |
| 48955 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 48956 | */ |
| 48957 | static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ |
| 48958 | int nWideChar; |
| 48959 | LPWSTR zMbcsText; |
| 48960 | int codepage = useAnsi ? CP_ACP : CP_OEMCP; |
| 48961 | |
| 48962 | nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, |
| 48963 | 0); |
| 48964 | if( nWideChar==0 ){ |
| 48965 | return 0; |
| 48966 | } |
| 48967 | zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) ); |
| 48968 | if( zMbcsText==0 ){ |
| 48969 | return 0; |
| 48970 | } |
| 48971 | nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, |
| 48972 | nWideChar); |
| 48973 | if( nWideChar==0 ){ |
| 48974 | sqlite3_free(zMbcsText); |
| 48975 | zMbcsText = 0; |
| 48976 | } |
| 48977 | return zMbcsText; |
| 48978 | } |
| 48979 | |
| 48980 | #ifdef _WIN32 |
| 48981 | /* |
| 48982 | ** Convert a Microsoft Unicode string to a multi-byte character string, |
| 48983 | ** using the ANSI or OEM code page. |
| 48984 | ** |
| 48985 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48939,10 +49003,11 @@ | |
| 49003 | sqlite3_free(zText); |
| 49004 | zText = 0; |
| 49005 | } |
| 49006 | return zText; |
| 49007 | } |
| 49008 | #endif /* _WIN32 */ |
| 49009 | |
| 49010 | /* |
| 49011 | ** Convert a multi-byte character string to UTF-8. |
| 49012 | ** |
| 49013 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| @@ -48958,10 +49023,11 @@ | |
| 49023 | zTextUtf8 = winUnicodeToUtf8(zTmpWide); |
| 49024 | sqlite3_free(zTmpWide); |
| 49025 | return zTextUtf8; |
| 49026 | } |
| 49027 | |
| 49028 | #ifdef _WIN32 |
| 49029 | /* |
| 49030 | ** Convert a UTF-8 string to a multi-byte character string. |
| 49031 | ** |
| 49032 | ** Space to hold the returned string is obtained from sqlite3_malloc(). |
| 49033 | */ |
| @@ -49007,10 +49073,11 @@ | |
| 49073 | #ifndef SQLITE_OMIT_AUTOINIT |
| 49074 | if( sqlite3_initialize() ) return 0; |
| 49075 | #endif |
| 49076 | return winUnicodeToUtf8(zWideText); |
| 49077 | } |
| 49078 | #endif /* _WIN32 */ |
| 49079 | |
| 49080 | /* |
| 49081 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49082 | */ |
| 49083 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){ |
| @@ -49024,10 +49091,11 @@ | |
| 49091 | if( sqlite3_initialize() ) return 0; |
| 49092 | #endif |
| 49093 | return winMbcsToUtf8(zText, osAreFileApisANSI()); |
| 49094 | } |
| 49095 | |
| 49096 | #ifdef _WIN32 |
| 49097 | /* |
| 49098 | ** This is a public wrapper for the winMbcsToUtf8() function. |
| 49099 | */ |
| 49100 | SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){ |
| 49101 | #ifdef SQLITE_ENABLE_API_ARMOR |
| @@ -49148,10 +49216,11 @@ | |
| 49216 | unsigned long type, /* Identifier for directory being set or reset */ |
| 49217 | void *zValue /* New value for directory being set or reset */ |
| 49218 | ){ |
| 49219 | return sqlite3_win32_set_directory16(type, zValue); |
| 49220 | } |
| 49221 | #endif /* _WIN32 */ |
| 49222 | |
| 49223 | /* |
| 49224 | ** The return value of winGetLastErrorMsg |
| 49225 | ** is zero if the error message fits in the buffer, or non-zero |
| 49226 | ** otherwise (if the message was truncated). |
| @@ -49696,13 +49765,15 @@ | |
| 49765 | OVERLAPPED ovlp; |
| 49766 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49767 | ovlp.Offset = offsetLow; |
| 49768 | ovlp.OffsetHigh = offsetHigh; |
| 49769 | return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49770 | #ifdef SQLITE_WIN32_HAS_ANSI |
| 49771 | }else{ |
| 49772 | return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49773 | numBytesHigh); |
| 49774 | #endif |
| 49775 | } |
| 49776 | #endif |
| 49777 | } |
| 49778 | |
| 49779 | /* |
| @@ -49806,13 +49877,15 @@ | |
| 49877 | OVERLAPPED ovlp; |
| 49878 | memset(&ovlp, 0, sizeof(OVERLAPPED)); |
| 49879 | ovlp.Offset = offsetLow; |
| 49880 | ovlp.OffsetHigh = offsetHigh; |
| 49881 | return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp); |
| 49882 | #ifdef SQLITE_WIN32_HAS_ANSI |
| 49883 | }else{ |
| 49884 | return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow, |
| 49885 | numBytesHigh); |
| 49886 | #endif |
| 49887 | } |
| 49888 | #endif |
| 49889 | } |
| 49890 | |
| 49891 | /* |
| @@ -51222,18 +51295,95 @@ | |
| 51295 | |
| 51296 | /* |
| 51297 | ** Convert a UTF-8 filename into whatever form the underlying |
| 51298 | ** operating system wants filenames in. Space to hold the result |
| 51299 | ** is obtained from malloc and must be freed by the calling |
| 51300 | ** function |
| 51301 | ** |
| 51302 | ** On Cygwin, 3 possible input forms are accepted: |
| 51303 | ** - If the filename starts with "<drive>:/" or "<drive>:\", |
| 51304 | ** it is converted to UTF-16 as-is. |
| 51305 | ** - If the filename contains '/', it is assumed to be a |
| 51306 | ** Cygwin absolute path, it is converted to a win32 |
| 51307 | ** absolute path in UTF-16. |
| 51308 | ** - Otherwise it must be a filename only, the win32 filename |
| 51309 | ** is returned in UTF-16. |
| 51310 | ** Note: If the function cygwin_conv_path() fails, only |
| 51311 | ** UTF-8 -> UTF-16 conversion will be done. This can only |
| 51312 | ** happen when the file path >32k, in which case winUtf8ToUnicode() |
| 51313 | ** will fail too. |
| 51314 | */ |
| 51315 | static void *winConvertFromUtf8Filename(const char *zFilename){ |
| 51316 | void *zConverted = 0; |
| 51317 | if( osIsNT() ){ |
| 51318 | #ifdef __CYGWIN__ |
| 51319 | int nChar; |
| 51320 | LPWSTR zWideFilename; |
| 51321 | |
| 51322 | if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename) |
| 51323 | && winIsDirSep(zFilename[2])) ){ |
| 51324 | int nByte; |
| 51325 | int convertflag = CCP_POSIX_TO_WIN_W; |
| 51326 | if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE; |
| 51327 | nByte = (int)osCygwin_conv_path(convertflag, |
| 51328 | zFilename, 0, 0); |
| 51329 | if( nByte>0 ){ |
| 51330 | zConverted = sqlite3MallocZero(nByte+12); |
| 51331 | if ( zConverted==0 ){ |
| 51332 | return zConverted; |
| 51333 | } |
| 51334 | zWideFilename = zConverted; |
| 51335 | /* Filenames should be prefixed, except when converted |
| 51336 | * full path already starts with "\\?\". */ |
| 51337 | if( osCygwin_conv_path(convertflag, zFilename, |
| 51338 | zWideFilename+4, nByte)==0 ){ |
| 51339 | if( (convertflag&CCP_RELATIVE) ){ |
| 51340 | memmove(zWideFilename, zWideFilename+4, nByte); |
| 51341 | }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){ |
| 51342 | memcpy(zWideFilename, L"\\\\?\\", 8); |
| 51343 | }else if( zWideFilename[6]!='?' ){ |
| 51344 | memmove(zWideFilename+6, zWideFilename+4, nByte); |
| 51345 | memcpy(zWideFilename, L"\\\\?\\UNC", 14); |
| 51346 | }else{ |
| 51347 | memmove(zWideFilename, zWideFilename+4, nByte); |
| 51348 | } |
| 51349 | return zConverted; |
| 51350 | } |
| 51351 | sqlite3_free(zConverted); |
| 51352 | } |
| 51353 | } |
| 51354 | nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); |
| 51355 | if( nChar==0 ){ |
| 51356 | return 0; |
| 51357 | } |
| 51358 | zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 ); |
| 51359 | if( zWideFilename==0 ){ |
| 51360 | return 0; |
| 51361 | } |
| 51362 | nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, |
| 51363 | zWideFilename, nChar); |
| 51364 | if( nChar==0 ){ |
| 51365 | sqlite3_free(zWideFilename); |
| 51366 | zWideFilename = 0; |
| 51367 | }else if( nChar>MAX_PATH |
| 51368 | && winIsDriveLetterAndColon(zFilename) |
| 51369 | && winIsDirSep(zFilename[2]) ){ |
| 51370 | memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR)); |
| 51371 | zWideFilename[2] = '\\'; |
| 51372 | memcpy(zWideFilename, L"\\\\?\\", 8); |
| 51373 | }else if( nChar>MAX_PATH |
| 51374 | && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1]) |
| 51375 | && zFilename[2] != '?' ){ |
| 51376 | memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR)); |
| 51377 | memcpy(zWideFilename, L"\\\\?\\UNC", 14); |
| 51378 | } |
| 51379 | zConverted = zWideFilename; |
| 51380 | #else |
| 51381 | zConverted = winUtf8ToUnicode(zFilename); |
| 51382 | #endif /* __CYGWIN__ */ |
| 51383 | } |
| 51384 | #if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32) |
| 51385 | else{ |
| 51386 | zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); |
| 51387 | } |
| 51388 | #endif |
| 51389 | /* caller will handle out of memory */ |
| @@ -52058,11 +52208,11 @@ | |
| 52208 | ** |
| 52209 | ** This division contains the implementation of methods on the |
| 52210 | ** sqlite3_vfs object. |
| 52211 | */ |
| 52212 | |
| 52213 | #if 0 /* No longer necessary */ |
| 52214 | /* |
| 52215 | ** Convert a filename from whatever the underlying operating system |
| 52216 | ** supports for filenames into UTF-8. Space to hold the result is |
| 52217 | ** obtained from malloc and must be freed by the calling function. |
| 52218 | */ |
| @@ -52091,11 +52241,18 @@ | |
| 52241 | int nLen = sqlite3Strlen30(zBuf); |
| 52242 | if( nLen>0 ){ |
| 52243 | if( winIsDirSep(zBuf[nLen-1]) ){ |
| 52244 | return 1; |
| 52245 | }else if( nLen+1<nBuf ){ |
| 52246 | if( !osGetenv ){ |
| 52247 | zBuf[nLen] = winGetDirSep(); |
| 52248 | }else if( winIsDriveLetterAndColon(zBuf) && winIsDirSep(zBuf[2]) ){ |
| 52249 | zBuf[nLen] = '\\'; |
| 52250 | zBuf[2]='\\'; |
| 52251 | }else{ |
| 52252 | zBuf[nLen] = '/'; |
| 52253 | } |
| 52254 | zBuf[nLen+1] = '\0'; |
| 52255 | return 1; |
| 52256 | } |
| 52257 | } |
| 52258 | } |
| @@ -52118,11 +52275,11 @@ | |
| 52275 | /* |
| 52276 | ** Create a temporary file name and store the resulting pointer into pzBuf. |
| 52277 | ** The pointer returned in pzBuf must be freed via sqlite3_free(). |
| 52278 | */ |
| 52279 | static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ |
| 52280 | static const char zChars[] = |
| 52281 | "abcdefghijklmnopqrstuvwxyz" |
| 52282 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 52283 | "0123456789"; |
| 52284 | size_t i, j; |
| 52285 | DWORD pid; |
| @@ -52169,11 +52326,11 @@ | |
| 52326 | } |
| 52327 | sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); |
| 52328 | } |
| 52329 | |
| 52330 | #if defined(__CYGWIN__) |
| 52331 | else if( osGetenv!=NULL ){ |
| 52332 | static const char *azDirs[] = { |
| 52333 | 0, /* getenv("SQLITE_TMPDIR") */ |
| 52334 | 0, /* getenv("TMPDIR") */ |
| 52335 | 0, /* getenv("TMP") */ |
| 52336 | 0, /* getenv("TEMP") */ |
| @@ -52185,24 +52342,24 @@ | |
| 52342 | 0 /* List terminator */ |
| 52343 | }; |
| 52344 | unsigned int i; |
| 52345 | const char *zDir = 0; |
| 52346 | |
| 52347 | if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR"); |
| 52348 | if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR"); |
| 52349 | if( !azDirs[2] ) azDirs[2] = osGetenv("TMP"); |
| 52350 | if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP"); |
| 52351 | if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE"); |
| 52352 | for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); zDir=azDirs[i++]){ |
| 52353 | void *zConverted; |
| 52354 | if( zDir==0 ) continue; |
| 52355 | /* If the path starts with a drive letter followed by the colon |
| 52356 | ** character, assume it is already a native Win32 path; otherwise, |
| 52357 | ** it must be converted to a native Win32 path via the Cygwin API |
| 52358 | ** prior to using it. |
| 52359 | */ |
| 52360 | { |
| 52361 | zConverted = winConvertFromUtf8Filename(zDir); |
| 52362 | if( !zConverted ){ |
| 52363 | sqlite3_free(zBuf); |
| 52364 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52365 | return SQLITE_IOERR_NOMEM_BKPT; |
| @@ -52211,19 +52368,20 @@ | |
| 52368 | sqlite3_snprintf(nMax, zBuf, "%s", zDir); |
| 52369 | sqlite3_free(zConverted); |
| 52370 | break; |
| 52371 | } |
| 52372 | sqlite3_free(zConverted); |
| 52373 | #if 0 /* No longer necessary */ |
| 52374 | }else{ |
| 52375 | zConverted = sqlite3MallocZero( nMax+1 ); |
| 52376 | if( !zConverted ){ |
| 52377 | sqlite3_free(zBuf); |
| 52378 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); |
| 52379 | return SQLITE_IOERR_NOMEM_BKPT; |
| 52380 | } |
| 52381 | if( osCygwin_conv_path( |
| 52382 | CCP_POSIX_TO_WIN_W, zDir, |
| 52383 | zConverted, nMax+1)<0 ){ |
| 52384 | sqlite3_free(zConverted); |
| 52385 | sqlite3_free(zBuf); |
| 52386 | OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n")); |
| 52387 | return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno, |
| @@ -52245,14 +52403,17 @@ | |
| 52403 | sqlite3_free(zUtf8); |
| 52404 | sqlite3_free(zConverted); |
| 52405 | break; |
| 52406 | } |
| 52407 | sqlite3_free(zConverted); |
| 52408 | #endif /* No longer necessary */ |
| 52409 | } |
| 52410 | } |
| 52411 | } |
| 52412 | #endif |
| 52413 | |
| 52414 | #if !SQLITE_OS_WINRT && defined(_WIN32) |
| 52415 | else if( osIsNT() ){ |
| 52416 | char *zMulti; |
| 52417 | LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) ); |
| 52418 | if( !zWidePath ){ |
| 52419 | sqlite3_free(zBuf); |
| @@ -52372,11 +52533,11 @@ | |
| 52533 | &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} |
| 52534 | if( !rc ){ |
| 52535 | return 0; /* Invalid name? */ |
| 52536 | } |
| 52537 | attr = sAttrData.dwFileAttributes; |
| 52538 | #if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI) |
| 52539 | }else{ |
| 52540 | attr = osGetFileAttributesA((char*)zConverted); |
| 52541 | #endif |
| 52542 | } |
| 52543 | return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY); |
| @@ -52388,10 +52549,16 @@ | |
| 52549 | const char *zFilename, /* Name of file to check */ |
| 52550 | int flags, /* Type of test to make on this file */ |
| 52551 | int *pResOut /* OUT: Result */ |
| 52552 | ); |
| 52553 | |
| 52554 | /* |
| 52555 | ** The Windows version of xAccess() accepts an extra bit in the flags |
| 52556 | ** parameter that prevents an anti-virus retry loop. |
| 52557 | */ |
| 52558 | #define NORETRY 0x4000 |
| 52559 | |
| 52560 | /* |
| 52561 | ** Open a file. |
| 52562 | */ |
| 52563 | static int winOpen( |
| 52564 | sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */ |
| @@ -52412,10 +52579,11 @@ | |
| 52579 | winVfsAppData *pAppData; |
| 52580 | winFile *pFile = (winFile*)id; |
| 52581 | void *zConverted; /* Filename in OS encoding */ |
| 52582 | const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ |
| 52583 | int cnt = 0; |
| 52584 | int isRO = 0; /* file is known to be accessible readonly */ |
| 52585 | |
| 52586 | /* If argument zPath is a NULL pointer, this function is required to open |
| 52587 | ** a temporary file. Use this buffer to store the file name in. |
| 52588 | */ |
| 52589 | char *zTmpname = 0; /* For temporary filename, if necessary. */ |
| @@ -52576,13 +52744,13 @@ | |
| 52744 | dwShareMode, |
| 52745 | dwCreationDisposition, |
| 52746 | &extendedParameters); |
| 52747 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52748 | if( isReadWrite ){ |
| 52749 | int rc2; |
| 52750 | sqlite3BeginBenignMalloc(); |
| 52751 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); |
| 52752 | sqlite3EndBenignMalloc(); |
| 52753 | if( rc2==SQLITE_OK && isRO ) break; |
| 52754 | } |
| 52755 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52756 | #else |
| @@ -52593,13 +52761,13 @@ | |
| 52761 | dwCreationDisposition, |
| 52762 | dwFlagsAndAttributes, |
| 52763 | NULL); |
| 52764 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52765 | if( isReadWrite ){ |
| 52766 | int rc2; |
| 52767 | sqlite3BeginBenignMalloc(); |
| 52768 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); |
| 52769 | sqlite3EndBenignMalloc(); |
| 52770 | if( rc2==SQLITE_OK && isRO ) break; |
| 52771 | } |
| 52772 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52773 | #endif |
| @@ -52613,13 +52781,13 @@ | |
| 52781 | dwCreationDisposition, |
| 52782 | dwFlagsAndAttributes, |
| 52783 | NULL); |
| 52784 | if( h!=INVALID_HANDLE_VALUE ) break; |
| 52785 | if( isReadWrite ){ |
| 52786 | int rc2; |
| 52787 | sqlite3BeginBenignMalloc(); |
| 52788 | rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); |
| 52789 | sqlite3EndBenignMalloc(); |
| 52790 | if( rc2==SQLITE_OK && isRO ) break; |
| 52791 | } |
| 52792 | }while( winRetryIoerr(&cnt, &lastErrno) ); |
| 52793 | } |
| @@ -52630,11 +52798,11 @@ | |
| 52798 | dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok")); |
| 52799 | |
| 52800 | if( h==INVALID_HANDLE_VALUE ){ |
| 52801 | sqlite3_free(zConverted); |
| 52802 | sqlite3_free(zTmpname); |
| 52803 | if( isReadWrite && isRO && !isExclusive ){ |
| 52804 | return winOpen(pVfs, zName, id, |
| 52805 | ((flags|SQLITE_OPEN_READONLY) & |
| 52806 | ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), |
| 52807 | pOutFlags); |
| 52808 | }else{ |
| @@ -52832,11 +53000,17 @@ | |
| 53000 | ){ |
| 53001 | DWORD attr; |
| 53002 | int rc = 0; |
| 53003 | DWORD lastErrno = 0; |
| 53004 | void *zConverted; |
| 53005 | int noRetry = 0; /* Do not use winRetryIoerr() */ |
| 53006 | UNUSED_PARAMETER(pVfs); |
| 53007 | |
| 53008 | if( (flags & NORETRY)!=0 ){ |
| 53009 | noRetry = 1; |
| 53010 | flags &= ~NORETRY; |
| 53011 | } |
| 53012 | |
| 53013 | SimulateIOError( return SQLITE_IOERR_ACCESS; ); |
| 53014 | OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", |
| 53015 | zFilename, flags, pResOut)); |
| 53016 | |
| @@ -52856,11 +53030,14 @@ | |
| 53030 | int cnt = 0; |
| 53031 | WIN32_FILE_ATTRIBUTE_DATA sAttrData; |
| 53032 | memset(&sAttrData, 0, sizeof(sAttrData)); |
| 53033 | while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, |
| 53034 | GetFileExInfoStandard, |
| 53035 | &sAttrData)) |
| 53036 | && !noRetry |
| 53037 | && winRetryIoerr(&cnt, &lastErrno) |
| 53038 | ){ /* Loop until true */} |
| 53039 | if( rc ){ |
| 53040 | /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file |
| 53041 | ** as if it does not exist. |
| 53042 | */ |
| 53043 | if( flags==SQLITE_ACCESS_EXISTS |
| @@ -52924,10 +53101,11 @@ | |
| 53101 | const char *zPathname |
| 53102 | ){ |
| 53103 | return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' ); |
| 53104 | } |
| 53105 | |
| 53106 | #ifdef _WIN32 |
| 53107 | /* |
| 53108 | ** Returns non-zero if the specified path name should be used verbatim. If |
| 53109 | ** non-zero is returned from this function, the calling function must simply |
| 53110 | ** use the provided path name verbatim -OR- resolve it into a full path name |
| 53111 | ** using the GetFullPathName Win32 API function (if available). |
| @@ -52960,10 +53138,74 @@ | |
| 53138 | ** If we get to this point, the path name should almost certainly be a purely |
| 53139 | ** relative one (i.e. not a UNC name, not absolute, and not volume relative). |
| 53140 | */ |
| 53141 | return FALSE; |
| 53142 | } |
| 53143 | #endif /* _WIN32 */ |
| 53144 | |
| 53145 | #ifdef __CYGWIN__ |
| 53146 | /* |
| 53147 | ** Simplify a filename into its canonical form |
| 53148 | ** by making the following changes: |
| 53149 | ** |
| 53150 | ** * convert any '/' to '\' (win32) or reverse (Cygwin) |
| 53151 | ** * removing any trailing and duplicate / (except for UNC paths) |
| 53152 | ** * convert /./ into just / |
| 53153 | ** |
| 53154 | ** Changes are made in-place. Return the new name length. |
| 53155 | ** |
| 53156 | ** The original filename is in z[0..]. If the path is shortened, |
| 53157 | ** no-longer used bytes will be written by '\0'. |
| 53158 | */ |
| 53159 | static void winSimplifyName(char *z){ |
| 53160 | int i, j; |
| 53161 | for(i=j=0; z[i]; ++i){ |
| 53162 | if( winIsDirSep(z[i]) ){ |
| 53163 | #if !defined(SQLITE_TEST) |
| 53164 | /* Some test-cases assume that "./foo" and "foo" are different */ |
| 53165 | if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){ |
| 53166 | ++i; |
| 53167 | continue; |
| 53168 | } |
| 53169 | #endif |
| 53170 | if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){ |
| 53171 | continue; |
| 53172 | } |
| 53173 | z[j++] = osGetenv?'/':'\\'; |
| 53174 | }else{ |
| 53175 | z[j++] = z[i]; |
| 53176 | } |
| 53177 | } |
| 53178 | while(j<i) z[j++] = '\0'; |
| 53179 | } |
| 53180 | |
| 53181 | #define SQLITE_MAX_SYMLINKS 100 |
| 53182 | |
| 53183 | static int mkFullPathname( |
| 53184 | const char *zPath, /* Input path */ |
| 53185 | char *zOut, /* Output buffer */ |
| 53186 | int nOut /* Allocated size of buffer zOut */ |
| 53187 | ){ |
| 53188 | int nPath = sqlite3Strlen30(zPath); |
| 53189 | int iOff = 0; |
| 53190 | if( zPath[0]!='/' ){ |
| 53191 | if( osGetcwd(zOut, nOut-2)==0 ){ |
| 53192 | return winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "getcwd", zPath); |
| 53193 | } |
| 53194 | iOff = sqlite3Strlen30(zOut); |
| 53195 | zOut[iOff++] = '/'; |
| 53196 | } |
| 53197 | if( (iOff+nPath+1)>nOut ){ |
| 53198 | /* SQLite assumes that xFullPathname() nul-terminates the output buffer |
| 53199 | ** even if it returns an error. */ |
| 53200 | zOut[iOff] = '\0'; |
| 53201 | return SQLITE_CANTOPEN_BKPT; |
| 53202 | } |
| 53203 | sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); |
| 53204 | return SQLITE_OK; |
| 53205 | } |
| 53206 | #endif /* __CYGWIN__ */ |
| 53207 | |
| 53208 | /* |
| 53209 | ** Turn a relative pathname into a full pathname. Write the full |
| 53210 | ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname |
| 53211 | ** bytes in size. |
| @@ -52972,12 +53214,12 @@ | |
| 53214 | sqlite3_vfs *pVfs, /* Pointer to vfs object */ |
| 53215 | const char *zRelative, /* Possibly relative input path */ |
| 53216 | int nFull, /* Size of output buffer in bytes */ |
| 53217 | char *zFull /* Output buffer */ |
| 53218 | ){ |
| 53219 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT |
| 53220 | int nByte; |
| 53221 | void *zConverted; |
| 53222 | char *zOut; |
| 53223 | #endif |
| 53224 | |
| 53225 | /* If this path name begins with "/X:" or "\\?\", where "X" is any |
| @@ -52986,68 +53228,114 @@ | |
| 53228 | if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1) |
| 53229 | || winIsLongPathPrefix(zRelative+1)) ){ |
| 53230 | zRelative++; |
| 53231 | } |
| 53232 | |
| 53233 | SimulateIOError( return SQLITE_ERROR ); |
| 53234 | |
| 53235 | #ifdef __CYGWIN__ |
| 53236 | if( osGetcwd ){ |
| 53237 | zFull[nFull-1] = '\0'; |
| 53238 | if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){ |
| 53239 | int rc = SQLITE_OK; |
| 53240 | int nLink = 1; /* Number of symbolic links followed so far */ |
| 53241 | const char *zIn = zRelative; /* Input path for each iteration of loop */ |
| 53242 | char *zDel = 0; |
| 53243 | struct stat buf; |
| 53244 | |
| 53245 | UNUSED_PARAMETER(pVfs); |
| 53246 | |
| 53247 | do { |
| 53248 | /* Call lstat() on path zIn. Set bLink to true if the path is a symbolic |
| 53249 | ** link, or false otherwise. */ |
| 53250 | int bLink = 0; |
| 53251 | if( osLstat && osReadlink ) { |
| 53252 | if( osLstat(zIn, &buf)!=0 ){ |
| 53253 | int myErrno = osErrno; |
| 53254 | if( myErrno!=ENOENT ){ |
| 53255 | rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn); |
| 53256 | } |
| 53257 | }else{ |
| 53258 | bLink = ((buf.st_mode & 0170000) == 0120000); |
| 53259 | } |
| 53260 | |
| 53261 | if( bLink ){ |
| 53262 | if( zDel==0 ){ |
| 53263 | zDel = sqlite3MallocZero(nFull); |
| 53264 | if( zDel==0 ) rc = SQLITE_NOMEM; |
| 53265 | }else if( ++nLink>SQLITE_MAX_SYMLINKS ){ |
| 53266 | rc = SQLITE_CANTOPEN_BKPT; |
| 53267 | } |
| 53268 | |
| 53269 | if( rc==SQLITE_OK ){ |
| 53270 | nByte = osReadlink(zIn, zDel, nFull-1); |
| 53271 | if( nByte ==(DWORD)-1 ){ |
| 53272 | rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn); |
| 53273 | }else{ |
| 53274 | if( zDel[0]!='/' ){ |
| 53275 | int n; |
| 53276 | for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); |
| 53277 | if( nByte+n+1>nFull ){ |
| 53278 | rc = SQLITE_CANTOPEN_BKPT; |
| 53279 | }else{ |
| 53280 | memmove(&zDel[n], zDel, nByte+1); |
| 53281 | memcpy(zDel, zIn, n); |
| 53282 | nByte += n; |
| 53283 | } |
| 53284 | } |
| 53285 | zDel[nByte] = '\0'; |
| 53286 | } |
| 53287 | } |
| 53288 | |
| 53289 | zIn = zDel; |
| 53290 | } |
| 53291 | } |
| 53292 | |
| 53293 | assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' ); |
| 53294 | if( rc==SQLITE_OK && zIn!=zFull ){ |
| 53295 | rc = mkFullPathname(zIn, zFull, nFull); |
| 53296 | } |
| 53297 | if( bLink==0 ) break; |
| 53298 | zIn = zFull; |
| 53299 | }while( rc==SQLITE_OK ); |
| 53300 | |
| 53301 | sqlite3_free(zDel); |
| 53302 | winSimplifyName(zFull); |
| 53303 | return rc; |
| 53304 | } |
| 53305 | } |
| 53306 | #endif /* __CYGWIN__ */ |
| 53307 | #if 0 /* This doesn't work correctly at all! See: |
| 53308 | <https://marc.info/?l=sqlite-users&m=139299149416314&w=2> |
| 53309 | */ |
| 53310 | SimulateIOError( return SQLITE_ERROR ); |
| 53311 | UNUSED_PARAMETER(nFull); |
| 53312 | assert( nFull>=pVfs->mxPathname ); |
| 53313 | char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); |
| 53314 | if( !zOut ){ |
| 53315 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53316 | } |
| 53317 | if( osCygwin_conv_path( |
| 53318 | CCP_POSIX_TO_WIN_W, |
| 53319 | zRelative, zOut, pVfs->mxPathname+1)<0 ){ |
| 53320 | sqlite3_free(zOut); |
| 53321 | return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, |
| 53322 | "winFullPathname2", zRelative); |
| 53323 | }else{ |
| 53324 | char *zUtf8 = winConvertToUtf8Filename(zOut); |
| 53325 | if( !zUtf8 ){ |
| 53326 | sqlite3_free(zOut); |
| 53327 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53328 | } |
| 53329 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); |
| 53330 | sqlite3_free(zUtf8); |
| 53331 | sqlite3_free(zOut); |
| 53332 | } |
| 53333 | return SQLITE_OK; |
| 53334 | #endif |
| 53335 | |
| 53336 | #if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && defined(_WIN32) |
| 53337 | SimulateIOError( return SQLITE_ERROR ); |
| 53338 | /* WinCE has no concept of a relative pathname, or so I am told. */ |
| 53339 | /* WinRT has no way to convert a relative path to an absolute one. */ |
| 53340 | if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ |
| 53341 | /* |
| @@ -53062,11 +53350,12 @@ | |
| 53350 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative); |
| 53351 | } |
| 53352 | return SQLITE_OK; |
| 53353 | #endif |
| 53354 | |
| 53355 | #if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT |
| 53356 | #if defined(_WIN32) |
| 53357 | /* It's odd to simulate an io-error here, but really this is just |
| 53358 | ** using the io-error infrastructure to test that SQLite handles this |
| 53359 | ** function failing. This function could fail if, for example, the |
| 53360 | ** current working directory has been unlinked. |
| 53361 | */ |
| @@ -53080,10 +53369,11 @@ | |
| 53369 | */ |
| 53370 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", |
| 53371 | sqlite3_data_directory, winGetDirSep(), zRelative); |
| 53372 | return SQLITE_OK; |
| 53373 | } |
| 53374 | #endif |
| 53375 | zConverted = winConvertFromUtf8Filename(zRelative); |
| 53376 | if( zConverted==0 ){ |
| 53377 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53378 | } |
| 53379 | if( osIsNT() ){ |
| @@ -53092,16 +53382,17 @@ | |
| 53382 | if( nByte==0 ){ |
| 53383 | sqlite3_free(zConverted); |
| 53384 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53385 | "winFullPathname1", zRelative); |
| 53386 | } |
| 53387 | nByte += 3; |
| 53388 | zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); |
| 53389 | if( zTemp==0 ){ |
| 53390 | sqlite3_free(zConverted); |
| 53391 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53392 | } |
| 53393 | nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0); |
| 53394 | if( nByte==0 ){ |
| 53395 | sqlite3_free(zConverted); |
| 53396 | sqlite3_free(zTemp); |
| 53397 | return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), |
| 53398 | "winFullPathname2", zRelative); |
| @@ -53135,11 +53426,30 @@ | |
| 53426 | zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI()); |
| 53427 | sqlite3_free(zTemp); |
| 53428 | } |
| 53429 | #endif |
| 53430 | if( zOut ){ |
| 53431 | #ifdef __CYGWIN__ |
| 53432 | if( memcmp(zOut, "\\\\?\\", 4) ){ |
| 53433 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); |
| 53434 | }else if( memcmp(zOut+4, "UNC\\", 4) ){ |
| 53435 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4); |
| 53436 | }else{ |
| 53437 | char *p = zOut+6; |
| 53438 | *p = '\\'; |
| 53439 | if( osGetcwd ){ |
| 53440 | /* On Cygwin, UNC paths use forward slashes */ |
| 53441 | while( *p ){ |
| 53442 | if( *p=='\\' ) *p = '/'; |
| 53443 | ++p; |
| 53444 | } |
| 53445 | } |
| 53446 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6); |
| 53447 | } |
| 53448 | #else |
| 53449 | sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); |
| 53450 | #endif /* __CYGWIN__ */ |
| 53451 | sqlite3_free(zOut); |
| 53452 | return SQLITE_OK; |
| 53453 | }else{ |
| 53454 | return SQLITE_IOERR_NOMEM_BKPT; |
| 53455 | } |
| @@ -53165,11 +53475,13 @@ | |
| 53475 | ** Interfaces for opening a shared library, finding entry points |
| 53476 | ** within the shared library, and closing the shared library. |
| 53477 | */ |
| 53478 | static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ |
| 53479 | HANDLE h; |
| 53480 | #if 0 /* This doesn't work correctly at all! See: |
| 53481 | <https://marc.info/?l=sqlite-users&m=139299149416314&w=2> |
| 53482 | */ |
| 53483 | int nFull = pVfs->mxPathname+1; |
| 53484 | char *zFull = sqlite3MallocZero( nFull ); |
| 53485 | void *zConverted = 0; |
| 53486 | if( zFull==0 ){ |
| 53487 | OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); |
| @@ -53532,11 +53844,11 @@ | |
| 53844 | }; |
| 53845 | #endif |
| 53846 | |
| 53847 | /* Double-check that the aSyscall[] array has been constructed |
| 53848 | ** correctly. See ticket [bb3a86e890c8e96ab] */ |
| 53849 | assert( ArraySize(aSyscall)==89 ); |
| 53850 | |
| 53851 | /* get memory map allocation granularity */ |
| 53852 | memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); |
| 53853 | #if SQLITE_OS_WINRT |
| 53854 | osGetNativeSystemInfo(&winSysInfo); |
| @@ -66508,14 +66820,12 @@ | |
| 66820 | s2 = aIn[1]; |
| 66821 | }else{ |
| 66822 | s1 = s2 = 0; |
| 66823 | } |
| 66824 | |
| 66825 | /* nByte is a multiple of 8 between 8 and 65536 */ |
| 66826 | assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 ); |
| 66827 | |
| 66828 | if( !nativeCksum ){ |
| 66829 | do { |
| 66830 | s1 += BYTESWAP32(aData[0]) + s2; |
| 66831 | s2 += BYTESWAP32(aData[1]) + s1; |
| @@ -147785,10 +148095,11 @@ | |
| 148095 | } |
| 148096 | |
| 148097 | multi_select_end: |
| 148098 | pDest->iSdst = dest.iSdst; |
| 148099 | pDest->nSdst = dest.nSdst; |
| 148100 | pDest->iSDParm2 = dest.iSDParm2; |
| 148101 | if( pDelete ){ |
| 148102 | sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete); |
| 148103 | } |
| 148104 | return rc; |
| 148105 | } |
| @@ -188065,10 +188376,17 @@ | |
| 188376 | ****************************************************************************** |
| 188377 | ** |
| 188378 | */ |
| 188379 | #ifndef _FTSINT_H |
| 188380 | #define _FTSINT_H |
| 188381 | |
| 188382 | /* #include <assert.h> */ |
| 188383 | /* #include <stdlib.h> */ |
| 188384 | /* #include <stddef.h> */ |
| 188385 | /* #include <stdio.h> */ |
| 188386 | /* #include <string.h> */ |
| 188387 | /* #include <stdarg.h> */ |
| 188388 | |
| 188389 | #if !defined(NDEBUG) && !defined(SQLITE_DEBUG) |
| 188390 | # define NDEBUG 1 |
| 188391 | #endif |
| 188392 | |
| @@ -189017,16 +189335,10 @@ | |
| 189335 | |
| 189336 | #if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_CORE) |
| 189337 | # define SQLITE_CORE 1 |
| 189338 | #endif |
| 189339 | |
| 189340 | |
| 189341 | /* #include "fts3.h" */ |
| 189342 | #ifndef SQLITE_CORE |
| 189343 | /* # include "sqlite3ext.h" */ |
| 189344 | SQLITE_EXTENSION_INIT1 |
| @@ -227533,12 +227845,12 @@ | |
| 227845 | ){ |
| 227846 | if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ |
| 227847 | /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and |
| 227848 | ** all subsequent pages to be deleted. */ |
| 227849 | pTab->iDbTrunc = iDb; |
| 227850 | pTab->pgnoTrunc = pgno-1; |
| 227851 | pgno = 1; |
| 227852 | }else{ |
| 227853 | zErr = "bad page value"; |
| 227854 | goto update_fail; |
| 227855 | } |
| 227856 | } |
| @@ -241869,11 +242181,12 @@ | |
| 242181 | sqlite3Fts5ParseError( |
| 242182 | pParse, "expected integer, got \"%.*s\"", p->n, p->p |
| 242183 | ); |
| 242184 | return; |
| 242185 | } |
| 242186 | if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0'); |
| 242187 | /* ^^^^^^^^^^^^^^^--- Prevent integer overflow */ |
| 242188 | } |
| 242189 | }else{ |
| 242190 | nNear = FTS5_DEFAULT_NEARDIST; |
| 242191 | } |
| 242192 | pNear->nNear = nNear; |
| @@ -256775,11 +257088,11 @@ | |
| 257088 | int nArg, /* Number of args */ |
| 257089 | sqlite3_value **apUnused /* Function arguments */ |
| 257090 | ){ |
| 257091 | assert( nArg==0 ); |
| 257092 | UNUSED_PARAM2(nArg, apUnused); |
| 257093 | sqlite3_result_text(pCtx, "fts5: 2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85", -1, SQLITE_TRANSIENT); |
| 257094 | } |
| 257095 | |
| 257096 | /* |
| 257097 | ** Implementation of fts5_locale(LOCALE, TEXT) function. |
| 257098 | ** |
| 257099 |
+1
-1
| --- 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-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185" | |
| 151 | +#define SQLITE_SOURCE_ID "2025-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85" | |
| 152 | 152 | |
| 153 | 153 | /* |
| 154 | 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | 156 | ** |
| 157 | 157 |
| --- 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-03-16 00:13:29 18bda13e197e4b4ec7464b3e70012f71edc05f73d8b14bb48bad452f81c7e185" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| 157 |
| --- 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-03-27 23:29:25 121f4d97f9a855131859d342bc2ade5f8c34ba7732029ae156d02cec7cb6dd85" |
| 152 | |
| 153 | /* |
| 154 | ** CAPI3REF: Run-Time Library Version Numbers |
| 155 | ** KEYWORDS: sqlite3_version sqlite3_sourceid |
| 156 | ** |
| 157 |
+2
-2
| --- src/backlink.c | ||
| +++ src/backlink.c | ||
| @@ -431,13 +431,13 @@ | ||
| 431 | 431 | blob_reset(&in); |
| 432 | 432 | } |
| 433 | 433 | |
| 434 | 434 | |
| 435 | 435 | /* |
| 436 | -** COMMAND: test-wiki-relink | |
| 436 | +** COMMAND: test-relink-wiki | |
| 437 | 437 | ** |
| 438 | -** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME | |
| 438 | +** Usage: %fossil test-relink-wiki WIKI-PAGE-NAME | |
| 439 | 439 | ** |
| 440 | 440 | ** Run the backlink_wiki_refresh() procedure on the wiki page |
| 441 | 441 | ** named. WIKI-PAGE-NAME can be a glob pattern or a prefix |
| 442 | 442 | ** of the wiki page. |
| 443 | 443 | */ |
| 444 | 444 |
| --- src/backlink.c | |
| +++ src/backlink.c | |
| @@ -431,13 +431,13 @@ | |
| 431 | blob_reset(&in); |
| 432 | } |
| 433 | |
| 434 | |
| 435 | /* |
| 436 | ** COMMAND: test-wiki-relink |
| 437 | ** |
| 438 | ** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME |
| 439 | ** |
| 440 | ** Run the backlink_wiki_refresh() procedure on the wiki page |
| 441 | ** named. WIKI-PAGE-NAME can be a glob pattern or a prefix |
| 442 | ** of the wiki page. |
| 443 | */ |
| 444 |
| --- src/backlink.c | |
| +++ src/backlink.c | |
| @@ -431,13 +431,13 @@ | |
| 431 | blob_reset(&in); |
| 432 | } |
| 433 | |
| 434 | |
| 435 | /* |
| 436 | ** COMMAND: test-relink-wiki |
| 437 | ** |
| 438 | ** Usage: %fossil test-relink-wiki WIKI-PAGE-NAME |
| 439 | ** |
| 440 | ** Run the backlink_wiki_refresh() procedure on the wiki page |
| 441 | ** named. WIKI-PAGE-NAME can be a glob pattern or a prefix |
| 442 | ** of the wiki page. |
| 443 | */ |
| 444 |
+2
-1
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -870,11 +870,12 @@ | ||
| 870 | 870 | const char *zLastCkin = db_column_text(&q, 5); |
| 871 | 871 | const char *zBgClr = db_column_text(&q, 6); |
| 872 | 872 | char *zAge = human_readable_age(rNow - rMtime); |
| 873 | 873 | sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); |
| 874 | 874 | if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; |
| 875 | - if( zBgClr == 0 ){ | |
| 875 | + if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0); | |
| 876 | + if( zBgClr==0 ){ | |
| 876 | 877 | if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){ |
| 877 | 878 | zBgClr = 0; |
| 878 | 879 | }else{ |
| 879 | 880 | zBgClr = hash_color(zBranch); |
| 880 | 881 | } |
| 881 | 882 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -870,11 +870,12 @@ | |
| 870 | const char *zLastCkin = db_column_text(&q, 5); |
| 871 | const char *zBgClr = db_column_text(&q, 6); |
| 872 | char *zAge = human_readable_age(rNow - rMtime); |
| 873 | sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); |
| 874 | if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; |
| 875 | if( zBgClr == 0 ){ |
| 876 | if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){ |
| 877 | zBgClr = 0; |
| 878 | }else{ |
| 879 | zBgClr = hash_color(zBranch); |
| 880 | } |
| 881 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -870,11 +870,12 @@ | |
| 870 | const char *zLastCkin = db_column_text(&q, 5); |
| 871 | const char *zBgClr = db_column_text(&q, 6); |
| 872 | char *zAge = human_readable_age(rNow - rMtime); |
| 873 | sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); |
| 874 | if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; |
| 875 | if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0); |
| 876 | if( zBgClr==0 ){ |
| 877 | if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){ |
| 878 | zBgClr = 0; |
| 879 | }else{ |
| 880 | zBgClr = hash_color(zBranch); |
| 881 | } |
| 882 |
+95
-125
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -1467,10 +1467,11 @@ | ||
| 1467 | 1467 | CheckinInfo *p, |
| 1468 | 1468 | int parent_rid, |
| 1469 | 1469 | int dryRunFlag |
| 1470 | 1470 | ){ |
| 1471 | 1471 | Blob prompt; |
| 1472 | + int wikiFlags; | |
| 1472 | 1473 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1473 | 1474 | int bomSize; |
| 1474 | 1475 | const unsigned char *bom = get_utf8_bom(&bomSize); |
| 1475 | 1476 | blob_init(&prompt, (const char *) bom, bomSize); |
| 1476 | 1477 | if( zInit && zInit[0]){ |
| @@ -1479,14 +1480,37 @@ | ||
| 1479 | 1480 | #else |
| 1480 | 1481 | blob_init(&prompt, zInit, -1); |
| 1481 | 1482 | #endif |
| 1482 | 1483 | blob_append(&prompt, |
| 1483 | 1484 | "\n" |
| 1484 | - "# Enter a commit message for this check-in." | |
| 1485 | - " Lines beginning with # are ignored.\n" | |
| 1486 | - "#\n", -1 | |
| 1485 | + "# Enter the commit message. Formatting rules:\n" | |
| 1486 | + "# * Lines beginning with # are ignored.\n", | |
| 1487 | + -1 | |
| 1487 | 1488 | ); |
| 1489 | + wikiFlags = wiki_convert_flags(1); | |
| 1490 | + if( wikiFlags & WIKI_LINKSONLY ){ | |
| 1491 | + blob_append(&prompt,"# * Hyperlinks inside of [...]\n", -1); | |
| 1492 | + if( wikiFlags & WIKI_NEWLINE ){ | |
| 1493 | + blob_append(&prompt, | |
| 1494 | + "# * Newlines are significant and are displayed as written\n", -1); | |
| 1495 | + }else{ | |
| 1496 | + blob_append(&prompt, | |
| 1497 | + "# * Newlines are interpreted as ordinary spaces\n", | |
| 1498 | + -1 | |
| 1499 | + ); | |
| 1500 | + } | |
| 1501 | + blob_append(&prompt, | |
| 1502 | + "# * All other text will be displayed as written\n", -1); | |
| 1503 | + }else{ | |
| 1504 | + blob_append(&prompt, | |
| 1505 | + "# * Hyperlinks: [target] or [target|display-text]\n" | |
| 1506 | + "# * Blank lines cause a paragraph break\n" | |
| 1507 | + "# * Other text rendered as if it where HTML\n", -1 | |
| 1508 | + ); | |
| 1509 | + } | |
| 1510 | + blob_append(&prompt, "#\n", 2); | |
| 1511 | + | |
| 1488 | 1512 | if( dryRunFlag ){ |
| 1489 | 1513 | blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes " |
| 1490 | 1514 | "will be made to the repository\n#\n"); |
| 1491 | 1515 | } |
| 1492 | 1516 | blob_appendf(&prompt, "# user: %s\n", |
| @@ -2289,134 +2313,82 @@ | ||
| 2289 | 2313 | ** |
| 2290 | 2314 | ** This setting determines how much sanity checking, if any, the |
| 2291 | 2315 | ** "fossil commit" and "fossil amend" commands do against check-in |
| 2292 | 2316 | ** comments. Recognized values: |
| 2293 | 2317 | ** |
| 2294 | -** on (Default) Check for bad syntax in check-in comments | |
| 2295 | -** and offer the user a chance to continue editing for | |
| 2296 | -** interactive sessions, or simply abort the commit if | |
| 2297 | -** commit was entered using -m or -M | |
| 2298 | -** | |
| 2299 | -** off Do not do syntax checking of any kind | |
| 2300 | -** | |
| 2301 | -** links Similar to "on", except only check for bad hyperlinks | |
| 2302 | -** | |
| 2303 | -** preview Do all the same checks as "on" but also preview the | |
| 2304 | -** check-in comment to the user during interactive sessions | |
| 2305 | -** and provide an opportunity to accept or re-edit | |
| 2318 | +** on (Default) Check for bad syntax and/or broken hyperlinks | |
| 2319 | +** in check-in comments and offer the user a chance to | |
| 2320 | +** continue editing for interactive sessions, or simply | |
| 2321 | +** abort the commit if the comment was entered using -m or -M | |
| 2322 | +** | |
| 2323 | +** off Do not do syntax checking of any kind | |
| 2324 | +** | |
| 2325 | +** preview Do all the same checks as "on" but also always preview the | |
| 2326 | +** check-in comment to the user during interactive sessions | |
| 2327 | +** even if no obvious errors are found, and provide an | |
| 2328 | +** opportunity to accept or re-edit | |
| 2306 | 2329 | */ |
| 2307 | 2330 | |
| 2308 | 2331 | #if INTERFACE |
| 2309 | -#define COMCK_LINKS 0x01 /* Check for back hyperlinks */ | |
| 2310 | -#define COMCK_MARKUP 0x02 /* Check markup */ | |
| 2311 | -#define COMCK_PREVIEW 0x04 /* Always preview, even if no issues found */ | |
| 2312 | -#define COMCK_NOPREVIEW 0x08 /* Never preview, even for other errors */ | |
| 2332 | +#define COMCK_MARKUP 0x01 /* Check for mistakes */ | |
| 2333 | +#define COMCK_PREVIEW 0x02 /* Always preview, even if no issues found */ | |
| 2313 | 2334 | #endif /* INTERFACE */ |
| 2314 | 2335 | |
| 2315 | 2336 | /* |
| 2316 | 2337 | ** Check for possible formatting errors in the comment string pComment. |
| 2317 | 2338 | ** |
| 2318 | -** If concerns are found, write a description of the problem(s) to | |
| 2319 | -** stdout and return non-zero. The return value is some combination | |
| 2320 | -** of the COMCK_* flags, depending on what went wrong. | |
| 2339 | +** If issues are found, write an appropriate error notice, probably also | |
| 2340 | +** including the complete text of the comment formatted to highlight the | |
| 2341 | +** problem, to stdout and return non-zero. The return value is some | |
| 2342 | +** combination of the COMCK_* flags, depending on what went wrong. | |
| 2321 | 2343 | ** |
| 2322 | 2344 | ** If no issues are seen, do not output anything and return zero. |
| 2323 | 2345 | */ |
| 2324 | -int suspicious_comment(Blob *pComment, int mFlags){ | |
| 2325 | - char *zStart = blob_str(pComment); | |
| 2326 | - char *z; | |
| 2327 | - char *zEnd, *zEnd2; | |
| 2328 | - char *zSep; | |
| 2329 | - char cSave1; | |
| 2330 | - int nIssue = 0; | |
| 2346 | +int verify_comment(Blob *pComment, int mFlags){ | |
| 2347 | + Blob in, html; | |
| 2348 | + int mResult; | |
| 2331 | 2349 | int rc = mFlags & COMCK_PREVIEW; |
| 2332 | - Blob out; | |
| 2333 | - static const char zSpecial[] = "\\&<*_`["; | |
| 2350 | + int wFlags; | |
| 2334 | 2351 | |
| 2335 | 2352 | if( mFlags==0 ) return 0; |
| 2336 | - z = zStart; | |
| 2337 | - blob_init(&out, 0, 0); | |
| 2338 | - if( mFlags & COMCK_LINKS ){ | |
| 2339 | - while( (z = strchr(z,'['))!=0 ){ | |
| 2340 | - zEnd = strchr(z,']'); | |
| 2341 | - if( zEnd==0 ){ | |
| 2342 | - blob_appendf(&out,"\n (%d) ", ++nIssue); | |
| 2343 | - blob_appendf(&out, "Unterminated hyperlink \"%.12s...\"", z); | |
| 2344 | - break; | |
| 2345 | - } | |
| 2346 | - if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){ | |
| 2347 | - blob_appendf(&out,"\n (%d) ", ++nIssue); | |
| 2348 | - blob_appendf(&out, "Markdown hyperlink syntax: %.*s", | |
| 2349 | - (int)(zEnd2+1-z), z); | |
| 2350 | - z = zEnd2; | |
| 2351 | - continue; | |
| 2352 | - } | |
| 2353 | - zSep = strchr(z+1,'|'); | |
| 2354 | - if( zSep==0 || zSep>zEnd ) zSep = zEnd; | |
| 2355 | - while( zSep>z && fossil_isspace(zSep[-1]) ) zSep--; | |
| 2356 | - cSave1 = zSep[0]; | |
| 2357 | - zSep[0] = 0; | |
| 2358 | - if( !wiki_valid_link_target(z+1) ){ | |
| 2359 | - blob_appendf(&out,"\n (%d) ", ++nIssue); | |
| 2360 | - blob_appendf(&out, "Broken hyperlink: [%s]", z+1); | |
| 2361 | - } | |
| 2362 | - zSep[0] = cSave1; | |
| 2363 | - z = zEnd; | |
| 2364 | - } | |
| 2365 | - } | |
| 2366 | - | |
| 2367 | - if( nIssue>0 | |
| 2368 | - || (mFlags & COMCK_PREVIEW)!=0 | |
| 2369 | - || ((mFlags & COMCK_MARKUP)!=0 && strcspn(zStart,zSpecial)<strlen(zStart)) | |
| 2370 | - ){ | |
| 2371 | - char zGot[16]; | |
| 2372 | - int nGot = 0; | |
| 2373 | - int i; | |
| 2374 | - if( (mFlags & COMCK_MARKUP)!=0 ){ | |
| 2375 | - for(i=0; zSpecial[i]; i++){ | |
| 2376 | - if( strchr(zStart,zSpecial[i]) ) zGot[nGot++] = zSpecial[i]; | |
| 2377 | - } | |
| 2378 | - } | |
| 2379 | - zGot[nGot] = 0; | |
| 2380 | - if( nGot>0 ) rc |= COMCK_MARKUP; | |
| 2381 | - if( nGot>0 && nIssue>0 ){ | |
| 2382 | - blob_appendf(&out,"\n (%d) Comment uses special character%s \"%s\"", | |
| 2383 | - ++nIssue, (nGot>1 ? "s" : ""), zGot); | |
| 2384 | - nGot = 0; | |
| 2385 | - } | |
| 2386 | - if( nIssue ){ | |
| 2387 | - rc |= COMCK_LINKS; | |
| 2388 | - fossil_print( | |
| 2389 | - "Possible comment formatting error%s:%b\n", | |
| 2390 | - nIssue>1 ? "s" : "", &out | |
| 2391 | - ); | |
| 2392 | - } | |
| 2393 | - if( (mFlags & COMCK_NOPREVIEW)==0 ){ | |
| 2394 | - Blob in, html, txt; | |
| 2395 | - blob_init(&in, blob_str(pComment), -1); | |
| 2396 | - blob_init(&html, 0, 0); | |
| 2397 | - blob_init(&txt, 0, 0); | |
| 2398 | - wiki_convert(&in, &html, WIKI_INLINE); | |
| 2399 | - html_to_plaintext(blob_str(&html), &txt); | |
| 2400 | - if( nGot>0 ){ | |
| 2401 | - fossil_print( | |
| 2402 | - "The comment uses special character%s \"%s\". " | |
| 2403 | - "Does it render as you expect?\n\n ", | |
| 2404 | - (nGot>1 ? "s" : ""), zGot | |
| 2405 | - ); | |
| 2406 | - }else{ | |
| 2407 | - fossil_print("Preview of the check-in comment:\n\n "); | |
| 2408 | - } | |
| 2353 | + blob_init(&in, blob_str(pComment), -1); | |
| 2354 | + blob_init(&html, 0, 0); | |
| 2355 | + wFlags = wiki_convert_flags(0); | |
| 2356 | + wFlags &= ~WIKI_NOBADLINKS; | |
| 2357 | + wFlags |= WIKI_MARK; | |
| 2358 | + mResult = wiki_convert(&in, &html, wFlags); | |
| 2359 | + if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP; | |
| 2360 | + if( rc ){ | |
| 2361 | + int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM; | |
| 2362 | + Blob txt; | |
| 2363 | + if( terminal_is_vt100() ) htot |= HTOT_VT100; | |
| 2364 | + blob_init(&txt, 0, 0); | |
| 2365 | + html_to_plaintext(blob_str(&html), &txt, htot); | |
| 2366 | + if( rc & COMCK_MARKUP ){ | |
| 2367 | + fossil_print("Possible format errors in the check-in comment:\n\n "); | |
| 2368 | + }else{ | |
| 2369 | + fossil_print("Preview of the check-in comment:\n\n "); | |
| 2370 | + } | |
| 2371 | + if( wFlags & WIKI_NEWLINE ){ | |
| 2372 | + Blob line; | |
| 2373 | + char *zIndent = ""; | |
| 2374 | + while( blob_line(&txt, &line) ){ | |
| 2375 | + fossil_print("%s%b", zIndent, &line); | |
| 2376 | + zIndent = " "; | |
| 2377 | + } | |
| 2378 | + fossil_print("\n"); | |
| 2379 | + }else{ | |
| 2409 | 2380 | comment_print(blob_str(&txt), 0, 3, -1, get_comment_format()); |
| 2410 | - blob_reset(&in); | |
| 2411 | - blob_reset(&html); | |
| 2412 | - blob_reset(&txt); | |
| 2413 | 2381 | } |
| 2382 | + fossil_print("\n"); | |
| 2383 | + fflush(stdout); | |
| 2384 | + blob_reset(&txt); | |
| 2414 | 2385 | } |
| 2415 | - blob_reset(&out); | |
| 2386 | + blob_reset(&html); | |
| 2387 | + blob_reset(&in); | |
| 2416 | 2388 | return rc; |
| 2417 | -} | |
| 2389 | +} | |
| 2418 | 2390 | |
| 2419 | 2391 | /* |
| 2420 | 2392 | ** COMMAND: ci# |
| 2421 | 2393 | ** COMMAND: commit |
| 2422 | 2394 | ** |
| @@ -2464,11 +2436,13 @@ | ||
| 2464 | 2436 | ** --allow-conflict Allow unresolved merge conflicts |
| 2465 | 2437 | ** --allow-empty Allow a commit with no changes |
| 2466 | 2438 | ** --allow-fork Allow the commit to fork |
| 2467 | 2439 | ** --allow-older Allow a commit older than its ancestor |
| 2468 | 2440 | ** --baseline Use a baseline manifest in the commit process |
| 2441 | +** --bgcolor COLOR Apply COLOR to this one check-in only | |
| 2469 | 2442 | ** --branch NEW-BRANCH-NAME Check in to this new branch |
| 2443 | +** --branchcolor COLOR Apply given COLOR to the branch | |
| 2470 | 2444 | ** --close Close the branch being committed |
| 2471 | 2445 | ** --date-override DATETIME Make DATETIME the time of the check-in. |
| 2472 | 2446 | ** Useful when importing historical check-ins |
| 2473 | 2447 | ** from another version control system. |
| 2474 | 2448 | ** --delta Use a delta manifest in the commit process |
| @@ -2556,11 +2530,11 @@ | ||
| 2556 | 2530 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2557 | 2531 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2558 | 2532 | int mxSize; |
| 2559 | 2533 | char *zCurBranch = 0; /* The current branch name of checkout */ |
| 2560 | 2534 | char *zNewBranch = 0; /* The branch name after update */ |
| 2561 | - int ckComFlgs; /* Flags passed to suspicious_comment() */ | |
| 2535 | + int ckComFlgs; /* Flags passed to verify_comment() */ | |
| 2562 | 2536 | |
| 2563 | 2537 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2564 | 2538 | url_proxy_options(); |
| 2565 | 2539 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2566 | 2540 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2928,15 +2902,13 @@ | ||
| 2928 | 2902 | }else{ |
| 2929 | 2903 | const char *zVerComs = db_get("verify-comments","on"); |
| 2930 | 2904 | if( is_false(zVerComs) ){ |
| 2931 | 2905 | ckComFlgs = 0; |
| 2932 | 2906 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 2933 | - ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP; | |
| 2934 | - }else if( strcmp(zVerComs,"links")==0 ){ | |
| 2935 | - ckComFlgs = COMCK_LINKS; | |
| 2907 | + ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP; | |
| 2936 | 2908 | }else{ |
| 2937 | - ckComFlgs = COMCK_LINKS | COMCK_MARKUP; | |
| 2909 | + ckComFlgs = COMCK_MARKUP; | |
| 2938 | 2910 | } |
| 2939 | 2911 | } |
| 2940 | 2912 | |
| 2941 | 2913 | /* Get the check-in comment. This might involve prompting the |
| 2942 | 2914 | ** user for the check-in comment, in which case we should resync |
| @@ -2943,23 +2915,21 @@ | ||
| 2943 | 2915 | ** to renew the check-in lock and repeat the checks for conflicts. |
| 2944 | 2916 | */ |
| 2945 | 2917 | if( zComment ){ |
| 2946 | 2918 | blob_zero(&comment); |
| 2947 | 2919 | blob_append(&comment, zComment, -1); |
| 2948 | - ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP); | |
| 2949 | - ckComFlgs |= COMCK_NOPREVIEW; | |
| 2950 | - if( suspicious_comment(&comment, ckComFlgs) ){ | |
| 2920 | + ckComFlgs &= ~COMCK_PREVIEW; | |
| 2921 | + if( verify_comment(&comment, ckComFlgs) ){ | |
| 2951 | 2922 | fossil_fatal("Commit aborted; " |
| 2952 | 2923 | "use --no-verify-comment to override"); |
| 2953 | 2924 | } |
| 2954 | 2925 | }else if( zComFile ){ |
| 2955 | 2926 | blob_zero(&comment); |
| 2956 | 2927 | blob_read_from_file(&comment, zComFile, ExtFILE); |
| 2957 | 2928 | blob_to_utf8_no_bom(&comment, 1); |
| 2958 | - ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP); | |
| 2959 | - ckComFlgs |= COMCK_NOPREVIEW; | |
| 2960 | - if( suspicious_comment(&comment, ckComFlgs) ){ | |
| 2929 | + ckComFlgs &= ~COMCK_PREVIEW; | |
| 2930 | + if( verify_comment(&comment, ckComFlgs) ){ | |
| 2961 | 2931 | fossil_fatal("Commit aborted; " |
| 2962 | 2932 | "use --no-verify-comment to override"); |
| 2963 | 2933 | } |
| 2964 | 2934 | }else if( !noPrompt ){ |
| 2965 | 2935 | while( 1/*exit-by-break*/ ){ |
| @@ -2966,23 +2936,23 @@ | ||
| 2966 | 2936 | int rc; |
| 2967 | 2937 | char *zInit; |
| 2968 | 2938 | zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'"); |
| 2969 | 2939 | prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag); |
| 2970 | 2940 | db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); |
| 2971 | - if( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){ | |
| 2941 | + if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){ | |
| 2972 | 2942 | if( rc==COMCK_PREVIEW ){ |
| 2973 | - prompt_user("\nContinue (Y/n/e=edit)? ", &ans); | |
| 2943 | + prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans); | |
| 2974 | 2944 | }else{ |
| 2975 | - prompt_user("\nContinue (y/n/E=edit)? ", &ans); | |
| 2945 | + prompt_user("Edit, abort, or continue (E/a/c)? ", &ans); | |
| 2976 | 2946 | } |
| 2977 | 2947 | cReply = blob_str(&ans)[0]; |
| 2978 | 2948 | cReply = fossil_tolower(cReply); |
| 2979 | 2949 | blob_reset(&ans); |
| 2980 | - if( cReply=='n' ){ | |
| 2950 | + if( cReply=='a' ){ | |
| 2981 | 2951 | fossil_fatal("Commit aborted."); |
| 2982 | 2952 | } |
| 2983 | - if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){ | |
| 2953 | + if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){ | |
| 2984 | 2954 | fossil_free(zInit); |
| 2985 | 2955 | continue; |
| 2986 | 2956 | } |
| 2987 | 2957 | } |
| 2988 | 2958 | if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ |
| 2989 | 2959 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -1467,10 +1467,11 @@ | |
| 1467 | CheckinInfo *p, |
| 1468 | int parent_rid, |
| 1469 | int dryRunFlag |
| 1470 | ){ |
| 1471 | Blob prompt; |
| 1472 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1473 | int bomSize; |
| 1474 | const unsigned char *bom = get_utf8_bom(&bomSize); |
| 1475 | blob_init(&prompt, (const char *) bom, bomSize); |
| 1476 | if( zInit && zInit[0]){ |
| @@ -1479,14 +1480,37 @@ | |
| 1479 | #else |
| 1480 | blob_init(&prompt, zInit, -1); |
| 1481 | #endif |
| 1482 | blob_append(&prompt, |
| 1483 | "\n" |
| 1484 | "# Enter a commit message for this check-in." |
| 1485 | " Lines beginning with # are ignored.\n" |
| 1486 | "#\n", -1 |
| 1487 | ); |
| 1488 | if( dryRunFlag ){ |
| 1489 | blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes " |
| 1490 | "will be made to the repository\n#\n"); |
| 1491 | } |
| 1492 | blob_appendf(&prompt, "# user: %s\n", |
| @@ -2289,134 +2313,82 @@ | |
| 2289 | ** |
| 2290 | ** This setting determines how much sanity checking, if any, the |
| 2291 | ** "fossil commit" and "fossil amend" commands do against check-in |
| 2292 | ** comments. Recognized values: |
| 2293 | ** |
| 2294 | ** on (Default) Check for bad syntax in check-in comments |
| 2295 | ** and offer the user a chance to continue editing for |
| 2296 | ** interactive sessions, or simply abort the commit if |
| 2297 | ** commit was entered using -m or -M |
| 2298 | ** |
| 2299 | ** off Do not do syntax checking of any kind |
| 2300 | ** |
| 2301 | ** links Similar to "on", except only check for bad hyperlinks |
| 2302 | ** |
| 2303 | ** preview Do all the same checks as "on" but also preview the |
| 2304 | ** check-in comment to the user during interactive sessions |
| 2305 | ** and provide an opportunity to accept or re-edit |
| 2306 | */ |
| 2307 | |
| 2308 | #if INTERFACE |
| 2309 | #define COMCK_LINKS 0x01 /* Check for back hyperlinks */ |
| 2310 | #define COMCK_MARKUP 0x02 /* Check markup */ |
| 2311 | #define COMCK_PREVIEW 0x04 /* Always preview, even if no issues found */ |
| 2312 | #define COMCK_NOPREVIEW 0x08 /* Never preview, even for other errors */ |
| 2313 | #endif /* INTERFACE */ |
| 2314 | |
| 2315 | /* |
| 2316 | ** Check for possible formatting errors in the comment string pComment. |
| 2317 | ** |
| 2318 | ** If concerns are found, write a description of the problem(s) to |
| 2319 | ** stdout and return non-zero. The return value is some combination |
| 2320 | ** of the COMCK_* flags, depending on what went wrong. |
| 2321 | ** |
| 2322 | ** If no issues are seen, do not output anything and return zero. |
| 2323 | */ |
| 2324 | int suspicious_comment(Blob *pComment, int mFlags){ |
| 2325 | char *zStart = blob_str(pComment); |
| 2326 | char *z; |
| 2327 | char *zEnd, *zEnd2; |
| 2328 | char *zSep; |
| 2329 | char cSave1; |
| 2330 | int nIssue = 0; |
| 2331 | int rc = mFlags & COMCK_PREVIEW; |
| 2332 | Blob out; |
| 2333 | static const char zSpecial[] = "\\&<*_`["; |
| 2334 | |
| 2335 | if( mFlags==0 ) return 0; |
| 2336 | z = zStart; |
| 2337 | blob_init(&out, 0, 0); |
| 2338 | if( mFlags & COMCK_LINKS ){ |
| 2339 | while( (z = strchr(z,'['))!=0 ){ |
| 2340 | zEnd = strchr(z,']'); |
| 2341 | if( zEnd==0 ){ |
| 2342 | blob_appendf(&out,"\n (%d) ", ++nIssue); |
| 2343 | blob_appendf(&out, "Unterminated hyperlink \"%.12s...\"", z); |
| 2344 | break; |
| 2345 | } |
| 2346 | if( zEnd[1]=='(' && (zEnd2 = strchr(zEnd,')'))!=0 ){ |
| 2347 | blob_appendf(&out,"\n (%d) ", ++nIssue); |
| 2348 | blob_appendf(&out, "Markdown hyperlink syntax: %.*s", |
| 2349 | (int)(zEnd2+1-z), z); |
| 2350 | z = zEnd2; |
| 2351 | continue; |
| 2352 | } |
| 2353 | zSep = strchr(z+1,'|'); |
| 2354 | if( zSep==0 || zSep>zEnd ) zSep = zEnd; |
| 2355 | while( zSep>z && fossil_isspace(zSep[-1]) ) zSep--; |
| 2356 | cSave1 = zSep[0]; |
| 2357 | zSep[0] = 0; |
| 2358 | if( !wiki_valid_link_target(z+1) ){ |
| 2359 | blob_appendf(&out,"\n (%d) ", ++nIssue); |
| 2360 | blob_appendf(&out, "Broken hyperlink: [%s]", z+1); |
| 2361 | } |
| 2362 | zSep[0] = cSave1; |
| 2363 | z = zEnd; |
| 2364 | } |
| 2365 | } |
| 2366 | |
| 2367 | if( nIssue>0 |
| 2368 | || (mFlags & COMCK_PREVIEW)!=0 |
| 2369 | || ((mFlags & COMCK_MARKUP)!=0 && strcspn(zStart,zSpecial)<strlen(zStart)) |
| 2370 | ){ |
| 2371 | char zGot[16]; |
| 2372 | int nGot = 0; |
| 2373 | int i; |
| 2374 | if( (mFlags & COMCK_MARKUP)!=0 ){ |
| 2375 | for(i=0; zSpecial[i]; i++){ |
| 2376 | if( strchr(zStart,zSpecial[i]) ) zGot[nGot++] = zSpecial[i]; |
| 2377 | } |
| 2378 | } |
| 2379 | zGot[nGot] = 0; |
| 2380 | if( nGot>0 ) rc |= COMCK_MARKUP; |
| 2381 | if( nGot>0 && nIssue>0 ){ |
| 2382 | blob_appendf(&out,"\n (%d) Comment uses special character%s \"%s\"", |
| 2383 | ++nIssue, (nGot>1 ? "s" : ""), zGot); |
| 2384 | nGot = 0; |
| 2385 | } |
| 2386 | if( nIssue ){ |
| 2387 | rc |= COMCK_LINKS; |
| 2388 | fossil_print( |
| 2389 | "Possible comment formatting error%s:%b\n", |
| 2390 | nIssue>1 ? "s" : "", &out |
| 2391 | ); |
| 2392 | } |
| 2393 | if( (mFlags & COMCK_NOPREVIEW)==0 ){ |
| 2394 | Blob in, html, txt; |
| 2395 | blob_init(&in, blob_str(pComment), -1); |
| 2396 | blob_init(&html, 0, 0); |
| 2397 | blob_init(&txt, 0, 0); |
| 2398 | wiki_convert(&in, &html, WIKI_INLINE); |
| 2399 | html_to_plaintext(blob_str(&html), &txt); |
| 2400 | if( nGot>0 ){ |
| 2401 | fossil_print( |
| 2402 | "The comment uses special character%s \"%s\". " |
| 2403 | "Does it render as you expect?\n\n ", |
| 2404 | (nGot>1 ? "s" : ""), zGot |
| 2405 | ); |
| 2406 | }else{ |
| 2407 | fossil_print("Preview of the check-in comment:\n\n "); |
| 2408 | } |
| 2409 | comment_print(blob_str(&txt), 0, 3, -1, get_comment_format()); |
| 2410 | blob_reset(&in); |
| 2411 | blob_reset(&html); |
| 2412 | blob_reset(&txt); |
| 2413 | } |
| 2414 | } |
| 2415 | blob_reset(&out); |
| 2416 | return rc; |
| 2417 | } |
| 2418 | |
| 2419 | /* |
| 2420 | ** COMMAND: ci# |
| 2421 | ** COMMAND: commit |
| 2422 | ** |
| @@ -2464,11 +2436,13 @@ | |
| 2464 | ** --allow-conflict Allow unresolved merge conflicts |
| 2465 | ** --allow-empty Allow a commit with no changes |
| 2466 | ** --allow-fork Allow the commit to fork |
| 2467 | ** --allow-older Allow a commit older than its ancestor |
| 2468 | ** --baseline Use a baseline manifest in the commit process |
| 2469 | ** --branch NEW-BRANCH-NAME Check in to this new branch |
| 2470 | ** --close Close the branch being committed |
| 2471 | ** --date-override DATETIME Make DATETIME the time of the check-in. |
| 2472 | ** Useful when importing historical check-ins |
| 2473 | ** from another version control system. |
| 2474 | ** --delta Use a delta manifest in the commit process |
| @@ -2556,11 +2530,11 @@ | |
| 2556 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2557 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2558 | int mxSize; |
| 2559 | char *zCurBranch = 0; /* The current branch name of checkout */ |
| 2560 | char *zNewBranch = 0; /* The branch name after update */ |
| 2561 | int ckComFlgs; /* Flags passed to suspicious_comment() */ |
| 2562 | |
| 2563 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2564 | url_proxy_options(); |
| 2565 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2566 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2928,15 +2902,13 @@ | |
| 2928 | }else{ |
| 2929 | const char *zVerComs = db_get("verify-comments","on"); |
| 2930 | if( is_false(zVerComs) ){ |
| 2931 | ckComFlgs = 0; |
| 2932 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 2933 | ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP; |
| 2934 | }else if( strcmp(zVerComs,"links")==0 ){ |
| 2935 | ckComFlgs = COMCK_LINKS; |
| 2936 | }else{ |
| 2937 | ckComFlgs = COMCK_LINKS | COMCK_MARKUP; |
| 2938 | } |
| 2939 | } |
| 2940 | |
| 2941 | /* Get the check-in comment. This might involve prompting the |
| 2942 | ** user for the check-in comment, in which case we should resync |
| @@ -2943,23 +2915,21 @@ | |
| 2943 | ** to renew the check-in lock and repeat the checks for conflicts. |
| 2944 | */ |
| 2945 | if( zComment ){ |
| 2946 | blob_zero(&comment); |
| 2947 | blob_append(&comment, zComment, -1); |
| 2948 | ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP); |
| 2949 | ckComFlgs |= COMCK_NOPREVIEW; |
| 2950 | if( suspicious_comment(&comment, ckComFlgs) ){ |
| 2951 | fossil_fatal("Commit aborted; " |
| 2952 | "use --no-verify-comment to override"); |
| 2953 | } |
| 2954 | }else if( zComFile ){ |
| 2955 | blob_zero(&comment); |
| 2956 | blob_read_from_file(&comment, zComFile, ExtFILE); |
| 2957 | blob_to_utf8_no_bom(&comment, 1); |
| 2958 | ckComFlgs &= ~(COMCK_PREVIEW|COMCK_MARKUP); |
| 2959 | ckComFlgs |= COMCK_NOPREVIEW; |
| 2960 | if( suspicious_comment(&comment, ckComFlgs) ){ |
| 2961 | fossil_fatal("Commit aborted; " |
| 2962 | "use --no-verify-comment to override"); |
| 2963 | } |
| 2964 | }else if( !noPrompt ){ |
| 2965 | while( 1/*exit-by-break*/ ){ |
| @@ -2966,23 +2936,23 @@ | |
| 2966 | int rc; |
| 2967 | char *zInit; |
| 2968 | zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'"); |
| 2969 | prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag); |
| 2970 | db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); |
| 2971 | if( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){ |
| 2972 | if( rc==COMCK_PREVIEW ){ |
| 2973 | prompt_user("\nContinue (Y/n/e=edit)? ", &ans); |
| 2974 | }else{ |
| 2975 | prompt_user("\nContinue (y/n/E=edit)? ", &ans); |
| 2976 | } |
| 2977 | cReply = blob_str(&ans)[0]; |
| 2978 | cReply = fossil_tolower(cReply); |
| 2979 | blob_reset(&ans); |
| 2980 | if( cReply=='n' ){ |
| 2981 | fossil_fatal("Commit aborted."); |
| 2982 | } |
| 2983 | if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){ |
| 2984 | fossil_free(zInit); |
| 2985 | continue; |
| 2986 | } |
| 2987 | } |
| 2988 | if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ |
| 2989 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -1467,10 +1467,11 @@ | |
| 1467 | CheckinInfo *p, |
| 1468 | int parent_rid, |
| 1469 | int dryRunFlag |
| 1470 | ){ |
| 1471 | Blob prompt; |
| 1472 | int wikiFlags; |
| 1473 | #if defined(_WIN32) || defined(__CYGWIN__) |
| 1474 | int bomSize; |
| 1475 | const unsigned char *bom = get_utf8_bom(&bomSize); |
| 1476 | blob_init(&prompt, (const char *) bom, bomSize); |
| 1477 | if( zInit && zInit[0]){ |
| @@ -1479,14 +1480,37 @@ | |
| 1480 | #else |
| 1481 | blob_init(&prompt, zInit, -1); |
| 1482 | #endif |
| 1483 | blob_append(&prompt, |
| 1484 | "\n" |
| 1485 | "# Enter the commit message. Formatting rules:\n" |
| 1486 | "# * Lines beginning with # are ignored.\n", |
| 1487 | -1 |
| 1488 | ); |
| 1489 | wikiFlags = wiki_convert_flags(1); |
| 1490 | if( wikiFlags & WIKI_LINKSONLY ){ |
| 1491 | blob_append(&prompt,"# * Hyperlinks inside of [...]\n", -1); |
| 1492 | if( wikiFlags & WIKI_NEWLINE ){ |
| 1493 | blob_append(&prompt, |
| 1494 | "# * Newlines are significant and are displayed as written\n", -1); |
| 1495 | }else{ |
| 1496 | blob_append(&prompt, |
| 1497 | "# * Newlines are interpreted as ordinary spaces\n", |
| 1498 | -1 |
| 1499 | ); |
| 1500 | } |
| 1501 | blob_append(&prompt, |
| 1502 | "# * All other text will be displayed as written\n", -1); |
| 1503 | }else{ |
| 1504 | blob_append(&prompt, |
| 1505 | "# * Hyperlinks: [target] or [target|display-text]\n" |
| 1506 | "# * Blank lines cause a paragraph break\n" |
| 1507 | "# * Other text rendered as if it where HTML\n", -1 |
| 1508 | ); |
| 1509 | } |
| 1510 | blob_append(&prompt, "#\n", 2); |
| 1511 | |
| 1512 | if( dryRunFlag ){ |
| 1513 | blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes " |
| 1514 | "will be made to the repository\n#\n"); |
| 1515 | } |
| 1516 | blob_appendf(&prompt, "# user: %s\n", |
| @@ -2289,134 +2313,82 @@ | |
| 2313 | ** |
| 2314 | ** This setting determines how much sanity checking, if any, the |
| 2315 | ** "fossil commit" and "fossil amend" commands do against check-in |
| 2316 | ** comments. Recognized values: |
| 2317 | ** |
| 2318 | ** on (Default) Check for bad syntax and/or broken hyperlinks |
| 2319 | ** in check-in comments and offer the user a chance to |
| 2320 | ** continue editing for interactive sessions, or simply |
| 2321 | ** abort the commit if the comment was entered using -m or -M |
| 2322 | ** |
| 2323 | ** off Do not do syntax checking of any kind |
| 2324 | ** |
| 2325 | ** preview Do all the same checks as "on" but also always preview the |
| 2326 | ** check-in comment to the user during interactive sessions |
| 2327 | ** even if no obvious errors are found, and provide an |
| 2328 | ** opportunity to accept or re-edit |
| 2329 | */ |
| 2330 | |
| 2331 | #if INTERFACE |
| 2332 | #define COMCK_MARKUP 0x01 /* Check for mistakes */ |
| 2333 | #define COMCK_PREVIEW 0x02 /* Always preview, even if no issues found */ |
| 2334 | #endif /* INTERFACE */ |
| 2335 | |
| 2336 | /* |
| 2337 | ** Check for possible formatting errors in the comment string pComment. |
| 2338 | ** |
| 2339 | ** If issues are found, write an appropriate error notice, probably also |
| 2340 | ** including the complete text of the comment formatted to highlight the |
| 2341 | ** problem, to stdout and return non-zero. The return value is some |
| 2342 | ** combination of the COMCK_* flags, depending on what went wrong. |
| 2343 | ** |
| 2344 | ** If no issues are seen, do not output anything and return zero. |
| 2345 | */ |
| 2346 | int verify_comment(Blob *pComment, int mFlags){ |
| 2347 | Blob in, html; |
| 2348 | int mResult; |
| 2349 | int rc = mFlags & COMCK_PREVIEW; |
| 2350 | int wFlags; |
| 2351 | |
| 2352 | if( mFlags==0 ) return 0; |
| 2353 | blob_init(&in, blob_str(pComment), -1); |
| 2354 | blob_init(&html, 0, 0); |
| 2355 | wFlags = wiki_convert_flags(0); |
| 2356 | wFlags &= ~WIKI_NOBADLINKS; |
| 2357 | wFlags |= WIKI_MARK; |
| 2358 | mResult = wiki_convert(&in, &html, wFlags); |
| 2359 | if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP; |
| 2360 | if( rc ){ |
| 2361 | int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM; |
| 2362 | Blob txt; |
| 2363 | if( terminal_is_vt100() ) htot |= HTOT_VT100; |
| 2364 | blob_init(&txt, 0, 0); |
| 2365 | html_to_plaintext(blob_str(&html), &txt, htot); |
| 2366 | if( rc & COMCK_MARKUP ){ |
| 2367 | fossil_print("Possible format errors in the check-in comment:\n\n "); |
| 2368 | }else{ |
| 2369 | fossil_print("Preview of the check-in comment:\n\n "); |
| 2370 | } |
| 2371 | if( wFlags & WIKI_NEWLINE ){ |
| 2372 | Blob line; |
| 2373 | char *zIndent = ""; |
| 2374 | while( blob_line(&txt, &line) ){ |
| 2375 | fossil_print("%s%b", zIndent, &line); |
| 2376 | zIndent = " "; |
| 2377 | } |
| 2378 | fossil_print("\n"); |
| 2379 | }else{ |
| 2380 | comment_print(blob_str(&txt), 0, 3, -1, get_comment_format()); |
| 2381 | } |
| 2382 | fossil_print("\n"); |
| 2383 | fflush(stdout); |
| 2384 | blob_reset(&txt); |
| 2385 | } |
| 2386 | blob_reset(&html); |
| 2387 | blob_reset(&in); |
| 2388 | return rc; |
| 2389 | } |
| 2390 | |
| 2391 | /* |
| 2392 | ** COMMAND: ci# |
| 2393 | ** COMMAND: commit |
| 2394 | ** |
| @@ -2464,11 +2436,13 @@ | |
| 2436 | ** --allow-conflict Allow unresolved merge conflicts |
| 2437 | ** --allow-empty Allow a commit with no changes |
| 2438 | ** --allow-fork Allow the commit to fork |
| 2439 | ** --allow-older Allow a commit older than its ancestor |
| 2440 | ** --baseline Use a baseline manifest in the commit process |
| 2441 | ** --bgcolor COLOR Apply COLOR to this one check-in only |
| 2442 | ** --branch NEW-BRANCH-NAME Check in to this new branch |
| 2443 | ** --branchcolor COLOR Apply given COLOR to the branch |
| 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 |
| @@ -2556,11 +2530,11 @@ | |
| 2530 | int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
| 2531 | int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
| 2532 | int mxSize; |
| 2533 | char *zCurBranch = 0; /* The current branch name of checkout */ |
| 2534 | char *zNewBranch = 0; /* The branch name after update */ |
| 2535 | int ckComFlgs; /* Flags passed to verify_comment() */ |
| 2536 | |
| 2537 | memset(&sCiInfo, 0, sizeof(sCiInfo)); |
| 2538 | url_proxy_options(); |
| 2539 | /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ |
| 2540 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| @@ -2928,15 +2902,13 @@ | |
| 2902 | }else{ |
| 2903 | const char *zVerComs = db_get("verify-comments","on"); |
| 2904 | if( is_false(zVerComs) ){ |
| 2905 | ckComFlgs = 0; |
| 2906 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 2907 | ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP; |
| 2908 | }else{ |
| 2909 | ckComFlgs = COMCK_MARKUP; |
| 2910 | } |
| 2911 | } |
| 2912 | |
| 2913 | /* Get the check-in comment. This might involve prompting the |
| 2914 | ** user for the check-in comment, in which case we should resync |
| @@ -2943,23 +2915,21 @@ | |
| 2915 | ** to renew the check-in lock and repeat the checks for conflicts. |
| 2916 | */ |
| 2917 | if( zComment ){ |
| 2918 | blob_zero(&comment); |
| 2919 | blob_append(&comment, zComment, -1); |
| 2920 | ckComFlgs &= ~COMCK_PREVIEW; |
| 2921 | if( verify_comment(&comment, ckComFlgs) ){ |
| 2922 | fossil_fatal("Commit aborted; " |
| 2923 | "use --no-verify-comment to override"); |
| 2924 | } |
| 2925 | }else if( zComFile ){ |
| 2926 | blob_zero(&comment); |
| 2927 | blob_read_from_file(&comment, zComFile, ExtFILE); |
| 2928 | blob_to_utf8_no_bom(&comment, 1); |
| 2929 | ckComFlgs &= ~COMCK_PREVIEW; |
| 2930 | if( verify_comment(&comment, ckComFlgs) ){ |
| 2931 | fossil_fatal("Commit aborted; " |
| 2932 | "use --no-verify-comment to override"); |
| 2933 | } |
| 2934 | }else if( !noPrompt ){ |
| 2935 | while( 1/*exit-by-break*/ ){ |
| @@ -2966,23 +2936,23 @@ | |
| 2936 | int rc; |
| 2937 | char *zInit; |
| 2938 | zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'"); |
| 2939 | prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag); |
| 2940 | db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); |
| 2941 | if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){ |
| 2942 | if( rc==COMCK_PREVIEW ){ |
| 2943 | prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans); |
| 2944 | }else{ |
| 2945 | prompt_user("Edit, abort, or continue (E/a/c)? ", &ans); |
| 2946 | } |
| 2947 | cReply = blob_str(&ans)[0]; |
| 2948 | cReply = fossil_tolower(cReply); |
| 2949 | blob_reset(&ans); |
| 2950 | if( cReply=='a' ){ |
| 2951 | fossil_fatal("Commit aborted."); |
| 2952 | } |
| 2953 | if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){ |
| 2954 | fossil_free(zInit); |
| 2955 | continue; |
| 2956 | } |
| 2957 | } |
| 2958 | if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ |
| 2959 |
+10
-4
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -276,13 +276,14 @@ | ||
| 276 | 276 | ** |
| 277 | 277 | ** The --latest flag can be used in place of VERSION to check-out the |
| 278 | 278 | ** latest version in the repository. |
| 279 | 279 | ** |
| 280 | 280 | ** Options: |
| 281 | -** --force Ignore edited files in the current check-out | |
| 282 | -** --keep Only update the manifest file(s) | |
| 281 | +** -f|--force Ignore edited files in the current check-out | |
| 282 | +** -k|--keep Only update the manifest file(s) | |
| 283 | 283 | ** --force-missing Force check-out even if content is missing |
| 284 | +** --prompt Prompt before overwriting when --force is used | |
| 284 | 285 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 285 | 286 | ** times (the timestamp of the last check-in which modified |
| 286 | 287 | ** them) |
| 287 | 288 | ** |
| 288 | 289 | ** See also: [[update]] |
| @@ -298,16 +299,21 @@ | ||
| 298 | 299 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 299 | 300 | Blob cksum1, cksum1b, cksum2; |
| 300 | 301 | |
| 301 | 302 | db_must_be_within_tree(); |
| 302 | 303 | db_begin_transaction(); |
| 303 | - forceFlag = find_option("force","f",0)!=0; | |
| 304 | 304 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 305 | - keepFlag = find_option("keep",0,0)!=0; | |
| 305 | + keepFlag = find_option("keep","k",0)!=0; | |
| 306 | + forceFlag = find_option("force","f",0)!=0; | |
| 306 | 307 | latestFlag = find_option("latest",0,0)!=0; |
| 307 | 308 | promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; |
| 308 | 309 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 310 | + | |
| 311 | + if( keepFlag != 0 ){ | |
| 312 | + /* After flag collection, in order not to affect promptFlag */ | |
| 313 | + forceFlag=1; | |
| 314 | + } | |
| 309 | 315 | |
| 310 | 316 | /* We should be done with options.. */ |
| 311 | 317 | verify_all_options(); |
| 312 | 318 | |
| 313 | 319 | if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ |
| 314 | 320 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -276,13 +276,14 @@ | |
| 276 | ** |
| 277 | ** The --latest flag can be used in place of VERSION to check-out the |
| 278 | ** latest version in the repository. |
| 279 | ** |
| 280 | ** Options: |
| 281 | ** --force Ignore edited files in the current check-out |
| 282 | ** --keep Only update the manifest file(s) |
| 283 | ** --force-missing Force check-out even if content is missing |
| 284 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 285 | ** times (the timestamp of the last check-in which modified |
| 286 | ** them) |
| 287 | ** |
| 288 | ** See also: [[update]] |
| @@ -298,16 +299,21 @@ | |
| 298 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 299 | Blob cksum1, cksum1b, cksum2; |
| 300 | |
| 301 | db_must_be_within_tree(); |
| 302 | db_begin_transaction(); |
| 303 | forceFlag = find_option("force","f",0)!=0; |
| 304 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 305 | keepFlag = find_option("keep",0,0)!=0; |
| 306 | latestFlag = find_option("latest",0,0)!=0; |
| 307 | promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; |
| 308 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 309 | |
| 310 | /* We should be done with options.. */ |
| 311 | verify_all_options(); |
| 312 | |
| 313 | if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ |
| 314 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -276,13 +276,14 @@ | |
| 276 | ** |
| 277 | ** The --latest flag can be used in place of VERSION to check-out the |
| 278 | ** latest version in the repository. |
| 279 | ** |
| 280 | ** Options: |
| 281 | ** -f|--force Ignore edited files in the current check-out |
| 282 | ** -k|--keep Only update the manifest file(s) |
| 283 | ** --force-missing Force check-out even if content is missing |
| 284 | ** --prompt Prompt before overwriting when --force is used |
| 285 | ** --setmtime Set timestamps of all files to match their SCM-side |
| 286 | ** times (the timestamp of the last check-in which modified |
| 287 | ** them) |
| 288 | ** |
| 289 | ** See also: [[update]] |
| @@ -298,16 +299,21 @@ | |
| 299 | int setmtimeFlag; /* --setmtime. Set mtimes on files */ |
| 300 | Blob cksum1, cksum1b, cksum2; |
| 301 | |
| 302 | db_must_be_within_tree(); |
| 303 | db_begin_transaction(); |
| 304 | forceMissingFlag = find_option("force-missing",0,0)!=0; |
| 305 | keepFlag = find_option("keep","k",0)!=0; |
| 306 | forceFlag = find_option("force","f",0)!=0; |
| 307 | latestFlag = find_option("latest",0,0)!=0; |
| 308 | promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; |
| 309 | setmtimeFlag = find_option("setmtime",0,0)!=0; |
| 310 | |
| 311 | if( keepFlag != 0 ){ |
| 312 | /* After flag collection, in order not to affect promptFlag */ |
| 313 | forceFlag=1; |
| 314 | } |
| 315 | |
| 316 | /* We should be done with options.. */ |
| 317 | verify_all_options(); |
| 318 | |
| 319 | if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ |
| 320 |
+333
| --- src/color.c | ||
| +++ src/color.c | ||
| @@ -20,10 +20,262 @@ | ||
| 20 | 20 | ** |
| 21 | 21 | */ |
| 22 | 22 | #include "config.h" |
| 23 | 23 | #include <string.h> |
| 24 | 24 | #include "color.h" |
| 25 | + | |
| 26 | +/* | |
| 27 | +** 140 standard CSS color names and their corresponding RGB values, | |
| 28 | +** in alphabetical order by name so that we can do a binary search | |
| 29 | +** for lookup. | |
| 30 | +*/ | |
| 31 | +static const struct CssColors { | |
| 32 | + const char *zName; /* CSS Color name, lower case */ | |
| 33 | + unsigned int iRGB; /* Corresponding RGB value */ | |
| 34 | +} aCssColors[] = { | |
| 35 | + { "aliceblue", 0xf0f8ff }, | |
| 36 | + { "antiquewhite", 0xfaebd7 }, | |
| 37 | + { "aqua", 0x00ffff }, | |
| 38 | + { "aquamarine", 0x7fffd4 }, | |
| 39 | + { "azure", 0xf0ffff }, | |
| 40 | + { "beige", 0xf5f5dc }, | |
| 41 | + { "bisque", 0xffe4c4 }, | |
| 42 | + { "black", 0x000000 }, | |
| 43 | + { "blanchedalmond", 0xffebcd }, | |
| 44 | + { "blue", 0x0000ff }, | |
| 45 | + { "blueviolet", 0x8a2be2 }, | |
| 46 | + { "brown", 0xa52a2a }, | |
| 47 | + { "burlywood", 0xdeb887 }, | |
| 48 | + { "cadetblue", 0x5f9ea0 }, | |
| 49 | + { "chartreuse", 0x7fff00 }, | |
| 50 | + { "chocolate", 0xd2691e }, | |
| 51 | + { "coral", 0xff7f50 }, | |
| 52 | + { "cornflowerblue", 0x6495ed }, | |
| 53 | + { "cornsilk", 0xfff8dc }, | |
| 54 | + { "crimson", 0xdc143c }, | |
| 55 | + { "cyan", 0x00ffff }, | |
| 56 | + { "darkblue", 0x00008b }, | |
| 57 | + { "darkcyan", 0x008b8b }, | |
| 58 | + { "darkgoldenrod", 0xb8860b }, | |
| 59 | + { "darkgray", 0xa9a9a9 }, | |
| 60 | + { "darkgreen", 0x006400 }, | |
| 61 | + { "darkkhaki", 0xbdb76b }, | |
| 62 | + { "darkmagenta", 0x8b008b }, | |
| 63 | + { "darkolivegreen", 0x556b2f }, | |
| 64 | + { "darkorange", 0xff8c00 }, | |
| 65 | + { "darkorchid", 0x9932cc }, | |
| 66 | + { "darkred", 0x8b0000 }, | |
| 67 | + { "darksalmon", 0xe9967a }, | |
| 68 | + { "darkseagreen", 0x8fbc8f }, | |
| 69 | + { "darkslateblue", 0x483d8b }, | |
| 70 | + { "darkslategray", 0x2f4f4f }, | |
| 71 | + { "darkturquoise", 0x00ced1 }, | |
| 72 | + { "darkviolet", 0x9400d3 }, | |
| 73 | + { "deeppink", 0xff1493 }, | |
| 74 | + { "deepskyblue", 0x00bfff }, | |
| 75 | + { "dimgray", 0x696969 }, | |
| 76 | + { "dodgerblue", 0x1e90ff }, | |
| 77 | + { "firebrick", 0xb22222 }, | |
| 78 | + { "floralwhite", 0xfffaf0 }, | |
| 79 | + { "forestgreen", 0x228b22 }, | |
| 80 | + { "fuchsia", 0xff00ff }, | |
| 81 | + { "gainsboro", 0xdcdcdc }, | |
| 82 | + { "ghostwhite", 0xf8f8ff }, | |
| 83 | + { "gold", 0xffd700 }, | |
| 84 | + { "goldenrod", 0xdaa520 }, | |
| 85 | + { "gray", 0x808080 }, | |
| 86 | + { "green", 0x008000 }, | |
| 87 | + { "greenyellow", 0xadff2f }, | |
| 88 | + { "honeydew", 0xf0fff0 }, | |
| 89 | + { "hotpink", 0xff69b4 }, | |
| 90 | + { "indianred", 0xcd5c5c }, | |
| 91 | + { "indigo", 0x4b0082 }, | |
| 92 | + { "ivory", 0xfffff0 }, | |
| 93 | + { "khaki", 0xf0e68c }, | |
| 94 | + { "lavender", 0xe6e6fa }, | |
| 95 | + { "lavenderblush", 0xfff0f5 }, | |
| 96 | + { "lawngreen", 0x7cfc00 }, | |
| 97 | + { "lemonchiffon", 0xfffacd }, | |
| 98 | + { "lightblue", 0xadd8e6 }, | |
| 99 | + { "lightcoral", 0xf08080 }, | |
| 100 | + { "lightcyan", 0xe0ffff }, | |
| 101 | + { "lightgoldenrodyellow", 0xfafad2 }, | |
| 102 | + { "lightgrey", 0xd3d3d3 }, | |
| 103 | + { "lightgreen", 0x90ee90 }, | |
| 104 | + { "lightpink", 0xffb6c1 }, | |
| 105 | + { "lightsalmon", 0xffa07a }, | |
| 106 | + { "lightseagreen", 0x20b2aa }, | |
| 107 | + { "lightskyblue", 0x87cefa }, | |
| 108 | + { "lightslategray", 0x778899 }, | |
| 109 | + { "lightsteelblue", 0xb0c4de }, | |
| 110 | + { "lightyellow", 0xffffe0 }, | |
| 111 | + { "lime", 0x00ff00 }, | |
| 112 | + { "limegreen", 0x32cd32 }, | |
| 113 | + { "linen", 0xfaf0e6 }, | |
| 114 | + { "magenta", 0xff00ff }, | |
| 115 | + { "maroon", 0x800000 }, | |
| 116 | + { "mediumaquamarine", 0x66cdaa }, | |
| 117 | + { "mediumblue", 0x0000cd }, | |
| 118 | + { "mediumorchid", 0xba55d3 }, | |
| 119 | + { "mediumpurple", 0x9370d8 }, | |
| 120 | + { "mediumseagreen", 0x3cb371 }, | |
| 121 | + { "mediumslateblue", 0x7b68ee }, | |
| 122 | + { "mediumspringgreen", 0x00fa9a }, | |
| 123 | + { "mediumturquoise", 0x48d1cc }, | |
| 124 | + { "mediumvioletred", 0xc71585 }, | |
| 125 | + { "midnightblue", 0x191970 }, | |
| 126 | + { "mintcream", 0xf5fffa }, | |
| 127 | + { "mistyrose", 0xffe4e1 }, | |
| 128 | + { "moccasin", 0xffe4b5 }, | |
| 129 | + { "navajowhite", 0xffdead }, | |
| 130 | + { "navy", 0x000080 }, | |
| 131 | + { "oldlace", 0xfdf5e6 }, | |
| 132 | + { "olive", 0x808000 }, | |
| 133 | + { "olivedrab", 0x6b8e23 }, | |
| 134 | + { "orange", 0xffa500 }, | |
| 135 | + { "orangered", 0xff4500 }, | |
| 136 | + { "orchid", 0xda70d6 }, | |
| 137 | + { "palegoldenrod", 0xeee8aa }, | |
| 138 | + { "palegreen", 0x98fb98 }, | |
| 139 | + { "paleturquoise", 0xafeeee }, | |
| 140 | + { "palevioletred", 0xd87093 }, | |
| 141 | + { "papayawhip", 0xffefd5 }, | |
| 142 | + { "peachpuff", 0xffdab9 }, | |
| 143 | + { "peru", 0xcd853f }, | |
| 144 | + { "pink", 0xffc0cb }, | |
| 145 | + { "plum", 0xdda0dd }, | |
| 146 | + { "powderblue", 0xb0e0e6 }, | |
| 147 | + { "purple", 0x800080 }, | |
| 148 | + { "red", 0xff0000 }, | |
| 149 | + { "rosybrown", 0xbc8f8f }, | |
| 150 | + { "royalblue", 0x4169e1 }, | |
| 151 | + { "saddlebrown", 0x8b4513 }, | |
| 152 | + { "salmon", 0xfa8072 }, | |
| 153 | + { "sandybrown", 0xf4a460 }, | |
| 154 | + { "seagreen", 0x2e8b57 }, | |
| 155 | + { "seashell", 0xfff5ee }, | |
| 156 | + { "sienna", 0xa0522d }, | |
| 157 | + { "silver", 0xc0c0c0 }, | |
| 158 | + { "skyblue", 0x87ceeb }, | |
| 159 | + { "slateblue", 0x6a5acd }, | |
| 160 | + { "slategray", 0x708090 }, | |
| 161 | + { "snow", 0xfffafa }, | |
| 162 | + { "springgreen", 0x00ff7f }, | |
| 163 | + { "steelblue", 0x4682b4 }, | |
| 164 | + { "tan", 0xd2b48c }, | |
| 165 | + { "teal", 0x008080 }, | |
| 166 | + { "thistle", 0xd8bfd8 }, | |
| 167 | + { "tomato", 0xff6347 }, | |
| 168 | + { "turquoise", 0x40e0d0 }, | |
| 169 | + { "violet", 0xee82ee }, | |
| 170 | + { "wheat", 0xf5deb3 }, | |
| 171 | + { "white", 0xffffff }, | |
| 172 | + { "whitesmoke", 0xf5f5f5 }, | |
| 173 | + { "yellow", 0xffff00 }, | |
| 174 | + { "yellowgreen", 0x9acd32 }, | |
| 175 | +}; | |
| 176 | + | |
| 177 | +/* | |
| 178 | +** Attempt to translate a CSS color name into an integer that | |
| 179 | +** represents the equivalent RGB value. Ignore alpha if provided. | |
| 180 | +** If the name cannot be translated, return -1. | |
| 181 | +*/ | |
| 182 | +int color_name_to_rgb(const char *zName){ | |
| 183 | + if( zName==0 || zName[0]==0 ) return -1; | |
| 184 | + if( zName[0]=='#' ){ | |
| 185 | + int i, v = 0; | |
| 186 | + for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){ | |
| 187 | + v = v*16 + fossil_hexvalue(zName[i]); | |
| 188 | + } | |
| 189 | + if( i==4 ){ | |
| 190 | + v = fossil_hexvalue(zName[1])*0x110000 + | |
| 191 | + fossil_hexvalue(zName[2])*0x1100 + | |
| 192 | + fossil_hexvalue(zName[3])*0x11; | |
| 193 | + return v; | |
| 194 | + } | |
| 195 | + if( i==7 ){ | |
| 196 | + return v; | |
| 197 | + } | |
| 198 | + return -1; | |
| 199 | + }else{ | |
| 200 | + int iMin = 0; | |
| 201 | + int iMax = count(aCssColors)-1; | |
| 202 | + while( iMin<=iMax ){ | |
| 203 | + int iMid = (iMin+iMax)/2; | |
| 204 | + int c = sqlite3_stricmp(aCssColors[iMid].zName, zName); | |
| 205 | + if( c==0 ) return aCssColors[iMid].iRGB; | |
| 206 | + if( c<0 ){ | |
| 207 | + iMin = iMid+1; | |
| 208 | + }else{ | |
| 209 | + iMax = iMid-1; | |
| 210 | + } | |
| 211 | + } | |
| 212 | + return -1; | |
| 213 | + } | |
| 214 | +} | |
| 215 | + | |
| 216 | +/* | |
| 217 | +** SETTING: raw-bgcolor boolean default=off | |
| 218 | +** | |
| 219 | +** Fossil usually tries to adjust user-specified background colors | |
| 220 | +** for checkins so that the text is readable and so that the color | |
| 221 | +** is not too garish. This setting disables that filter. When | |
| 222 | +** this setting is on, the user-selected background colors are shown | |
| 223 | +** exactly as requested. | |
| 224 | +*/ | |
| 225 | + | |
| 226 | +/* | |
| 227 | +** Shift a color provided by the user so that it is suitable | |
| 228 | +** for use as a background color in the current skin. | |
| 229 | +** | |
| 230 | +** The return value is a #HHHHHH color name contained in | |
| 231 | +** static space that is overwritten on the next call. | |
| 232 | +** | |
| 233 | +** If we cannot make sense of the background color recommendation | |
| 234 | +** that is the input, then return NULL. | |
| 235 | +** | |
| 236 | +** The iFgClr parameter is normally 0. But for testing purposes, set | |
| 237 | +** it to 1 for a black foregrounds and 2 for a white foreground. | |
| 238 | +*/ | |
| 239 | +const char *reasonable_bg_color(const char *zRequested, int iFgClr){ | |
| 240 | + int iRGB = color_name_to_rgb(zRequested); | |
| 241 | + int r, g, b; /* RGB components of requested color */ | |
| 242 | + static int systemFg = 0; /* 1==black-foreground 2==white-foreground */ | |
| 243 | + int fg; /* Foreground color to actually use */ | |
| 244 | + static char zColor[10]; /* Return value */ | |
| 245 | + | |
| 246 | + if( iFgClr ){ | |
| 247 | + fg = iFgClr; | |
| 248 | + }else if( systemFg==0 ){ | |
| 249 | + if( db_get_boolean("raw-bgcolor",0) ){ | |
| 250 | + fg = systemFg = 3; | |
| 251 | + }else{ | |
| 252 | + fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1; | |
| 253 | + } | |
| 254 | + }else{ | |
| 255 | + fg = systemFg; | |
| 256 | + } | |
| 257 | + if( fg>=3 ) return zRequested; | |
| 258 | + | |
| 259 | + if( iRGB<0 ) return 0; | |
| 260 | + r = (iRGB>>16) & 0xff; | |
| 261 | + g = (iRGB>>8) & 0xff; | |
| 262 | + b = iRGB & 0xff; | |
| 263 | + if( fg==1 ){ | |
| 264 | + const int K = 70; | |
| 265 | + r = (K*r)/255 + (255-K); | |
| 266 | + g = (K*g)/255 + (255-K); | |
| 267 | + b = (K*b)/255 + (255-K); | |
| 268 | + }else{ | |
| 269 | + const int K = 90; | |
| 270 | + r = (K*r)/255; | |
| 271 | + g = (K*g)/255; | |
| 272 | + b = (K*b)/255; | |
| 273 | + } | |
| 274 | + sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b); | |
| 275 | + return zColor; | |
| 276 | +} | |
| 25 | 277 | |
| 26 | 278 | /* |
| 27 | 279 | ** Compute a hash on a branch or user name |
| 28 | 280 | */ |
| 29 | 281 | static unsigned int hash_of_name(const char *z){ |
| @@ -185,5 +437,86 @@ | ||
| 185 | 437 | @ <input type="submit" value="Submit"> |
| 186 | 438 | @ <input type="submit" name="rand" value="Random"> |
| 187 | 439 | @ </form> |
| 188 | 440 | style_finish_page(); |
| 189 | 441 | } |
| 442 | + | |
| 443 | +/* | |
| 444 | +** WEBPAGE: test-bgcolor | |
| 445 | +** | |
| 446 | +** Show how user-specified background colors will be rendered | |
| 447 | +** using the reasonable_bg_color() algorithm. | |
| 448 | +*/ | |
| 449 | +void test_bgcolor_page(void){ | |
| 450 | + const char *zReq; /* Requested color name */ | |
| 451 | + const char *zBG; /* Actual color provided */ | |
| 452 | + const char *zBg1; | |
| 453 | + char zNm[10]; | |
| 454 | + static const char *azDflt[] = { | |
| 455 | + "red", "orange", "yellow", "green", "blue", "indigo", "violet", | |
| 456 | + "tan", "brown", "gray" | |
| 457 | + }; | |
| 458 | + int i, cnt, iClr, r, g, b; | |
| 459 | + char *zFg; | |
| 460 | + login_check_credentials(); | |
| 461 | + style_set_current_feature("test"); | |
| 462 | + style_header("Background Color Test"); | |
| 463 | + for(i=cnt=0; i<10; i++){ | |
| 464 | + sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); | |
| 465 | + zReq = PD(zNm,azDflt[i]); | |
| 466 | + if( zReq==0 || zReq[0]==0 ) continue; | |
| 467 | + if( cnt==0 ){ | |
| 468 | + @ <table border="1" cellspacing="0" cellpadding="10"> | |
| 469 | + @ <tr> | |
| 470 | + @ <th>Requested Background | |
| 471 | + @ <th>Light mode | |
| 472 | + @ <th>Dark mode | |
| 473 | + @ </tr> | |
| 474 | + } | |
| 475 | + cnt++; | |
| 476 | + zBG = reasonable_bg_color(zReq, 0); | |
| 477 | + if( zBG==0 ){ | |
| 478 | + @ <tr><td colspan="3" align="center">\ | |
| 479 | + @ "%h(zReq)" is not a recognized color name</td></tr> | |
| 480 | + continue; | |
| 481 | + } | |
| 482 | + iClr = color_name_to_rgb(zReq); | |
| 483 | + r = (iClr>>16) & 0xff; | |
| 484 | + g = (iClr>>8) & 0xff; | |
| 485 | + b = iClr & 0xff; | |
| 486 | + if( 3*r + 7*g + b > 6*255 ){ | |
| 487 | + zFg = "black"; | |
| 488 | + }else{ | |
| 489 | + zFg = "white"; | |
| 490 | + } | |
| 491 | + if( zReq[0]!='#' ){ | |
| 492 | + char zReqRGB[12]; | |
| 493 | + sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq)); | |
| 494 | + @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\ | |
| 495 | + @ Requested color "%h(zReq)" (%h(zReqRGB))</td> | |
| 496 | + }else{ | |
| 497 | + @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\ | |
| 498 | + @ Requested color "%h(zReq)"</td> | |
| 499 | + } | |
| 500 | + zBg1 = reasonable_bg_color(zReq,1); | |
| 501 | + @ <td style='color:black;background-color:%h(zBg1);'>\ | |
| 502 | + @ Background color for dark text: %h(zBg1)</td> | |
| 503 | + zBg1 = reasonable_bg_color(zReq,2); | |
| 504 | + @ <td style='color:white;background-color:%h(zBg1);'>\ | |
| 505 | + @ Background color for light text: %h(zBg1)</td></tr> | |
| 506 | + } | |
| 507 | + if( cnt ){ | |
| 508 | + @ </table> | |
| 509 | + @ <hr> | |
| 510 | + } | |
| 511 | + @ <form method="POST"> | |
| 512 | + @ <p>Enter CSS color names below and see them shifted into corresponding | |
| 513 | + @ background colors above.</p> | |
| 514 | + for(i=0; i<10; i++){ | |
| 515 | + sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); | |
| 516 | + @ <input type="text" size="30" name='%s(zNm)' \ | |
| 517 | + @ value='%h(PD(zNm,azDflt[i]))'><br> | |
| 518 | + } | |
| 519 | + @ <input type="submit" value="Submit"> | |
| 520 | + @ </form> | |
| 521 | + style_finish_page(); | |
| 522 | +} | |
| 190 | 523 |
| --- src/color.c | |
| +++ src/color.c | |
| @@ -20,10 +20,262 @@ | |
| 20 | ** |
| 21 | */ |
| 22 | #include "config.h" |
| 23 | #include <string.h> |
| 24 | #include "color.h" |
| 25 | |
| 26 | /* |
| 27 | ** Compute a hash on a branch or user name |
| 28 | */ |
| 29 | static unsigned int hash_of_name(const char *z){ |
| @@ -185,5 +437,86 @@ | |
| 185 | @ <input type="submit" value="Submit"> |
| 186 | @ <input type="submit" name="rand" value="Random"> |
| 187 | @ </form> |
| 188 | style_finish_page(); |
| 189 | } |
| 190 |
| --- src/color.c | |
| +++ src/color.c | |
| @@ -20,10 +20,262 @@ | |
| 20 | ** |
| 21 | */ |
| 22 | #include "config.h" |
| 23 | #include <string.h> |
| 24 | #include "color.h" |
| 25 | |
| 26 | /* |
| 27 | ** 140 standard CSS color names and their corresponding RGB values, |
| 28 | ** in alphabetical order by name so that we can do a binary search |
| 29 | ** for lookup. |
| 30 | */ |
| 31 | static const struct CssColors { |
| 32 | const char *zName; /* CSS Color name, lower case */ |
| 33 | unsigned int iRGB; /* Corresponding RGB value */ |
| 34 | } aCssColors[] = { |
| 35 | { "aliceblue", 0xf0f8ff }, |
| 36 | { "antiquewhite", 0xfaebd7 }, |
| 37 | { "aqua", 0x00ffff }, |
| 38 | { "aquamarine", 0x7fffd4 }, |
| 39 | { "azure", 0xf0ffff }, |
| 40 | { "beige", 0xf5f5dc }, |
| 41 | { "bisque", 0xffe4c4 }, |
| 42 | { "black", 0x000000 }, |
| 43 | { "blanchedalmond", 0xffebcd }, |
| 44 | { "blue", 0x0000ff }, |
| 45 | { "blueviolet", 0x8a2be2 }, |
| 46 | { "brown", 0xa52a2a }, |
| 47 | { "burlywood", 0xdeb887 }, |
| 48 | { "cadetblue", 0x5f9ea0 }, |
| 49 | { "chartreuse", 0x7fff00 }, |
| 50 | { "chocolate", 0xd2691e }, |
| 51 | { "coral", 0xff7f50 }, |
| 52 | { "cornflowerblue", 0x6495ed }, |
| 53 | { "cornsilk", 0xfff8dc }, |
| 54 | { "crimson", 0xdc143c }, |
| 55 | { "cyan", 0x00ffff }, |
| 56 | { "darkblue", 0x00008b }, |
| 57 | { "darkcyan", 0x008b8b }, |
| 58 | { "darkgoldenrod", 0xb8860b }, |
| 59 | { "darkgray", 0xa9a9a9 }, |
| 60 | { "darkgreen", 0x006400 }, |
| 61 | { "darkkhaki", 0xbdb76b }, |
| 62 | { "darkmagenta", 0x8b008b }, |
| 63 | { "darkolivegreen", 0x556b2f }, |
| 64 | { "darkorange", 0xff8c00 }, |
| 65 | { "darkorchid", 0x9932cc }, |
| 66 | { "darkred", 0x8b0000 }, |
| 67 | { "darksalmon", 0xe9967a }, |
| 68 | { "darkseagreen", 0x8fbc8f }, |
| 69 | { "darkslateblue", 0x483d8b }, |
| 70 | { "darkslategray", 0x2f4f4f }, |
| 71 | { "darkturquoise", 0x00ced1 }, |
| 72 | { "darkviolet", 0x9400d3 }, |
| 73 | { "deeppink", 0xff1493 }, |
| 74 | { "deepskyblue", 0x00bfff }, |
| 75 | { "dimgray", 0x696969 }, |
| 76 | { "dodgerblue", 0x1e90ff }, |
| 77 | { "firebrick", 0xb22222 }, |
| 78 | { "floralwhite", 0xfffaf0 }, |
| 79 | { "forestgreen", 0x228b22 }, |
| 80 | { "fuchsia", 0xff00ff }, |
| 81 | { "gainsboro", 0xdcdcdc }, |
| 82 | { "ghostwhite", 0xf8f8ff }, |
| 83 | { "gold", 0xffd700 }, |
| 84 | { "goldenrod", 0xdaa520 }, |
| 85 | { "gray", 0x808080 }, |
| 86 | { "green", 0x008000 }, |
| 87 | { "greenyellow", 0xadff2f }, |
| 88 | { "honeydew", 0xf0fff0 }, |
| 89 | { "hotpink", 0xff69b4 }, |
| 90 | { "indianred", 0xcd5c5c }, |
| 91 | { "indigo", 0x4b0082 }, |
| 92 | { "ivory", 0xfffff0 }, |
| 93 | { "khaki", 0xf0e68c }, |
| 94 | { "lavender", 0xe6e6fa }, |
| 95 | { "lavenderblush", 0xfff0f5 }, |
| 96 | { "lawngreen", 0x7cfc00 }, |
| 97 | { "lemonchiffon", 0xfffacd }, |
| 98 | { "lightblue", 0xadd8e6 }, |
| 99 | { "lightcoral", 0xf08080 }, |
| 100 | { "lightcyan", 0xe0ffff }, |
| 101 | { "lightgoldenrodyellow", 0xfafad2 }, |
| 102 | { "lightgrey", 0xd3d3d3 }, |
| 103 | { "lightgreen", 0x90ee90 }, |
| 104 | { "lightpink", 0xffb6c1 }, |
| 105 | { "lightsalmon", 0xffa07a }, |
| 106 | { "lightseagreen", 0x20b2aa }, |
| 107 | { "lightskyblue", 0x87cefa }, |
| 108 | { "lightslategray", 0x778899 }, |
| 109 | { "lightsteelblue", 0xb0c4de }, |
| 110 | { "lightyellow", 0xffffe0 }, |
| 111 | { "lime", 0x00ff00 }, |
| 112 | { "limegreen", 0x32cd32 }, |
| 113 | { "linen", 0xfaf0e6 }, |
| 114 | { "magenta", 0xff00ff }, |
| 115 | { "maroon", 0x800000 }, |
| 116 | { "mediumaquamarine", 0x66cdaa }, |
| 117 | { "mediumblue", 0x0000cd }, |
| 118 | { "mediumorchid", 0xba55d3 }, |
| 119 | { "mediumpurple", 0x9370d8 }, |
| 120 | { "mediumseagreen", 0x3cb371 }, |
| 121 | { "mediumslateblue", 0x7b68ee }, |
| 122 | { "mediumspringgreen", 0x00fa9a }, |
| 123 | { "mediumturquoise", 0x48d1cc }, |
| 124 | { "mediumvioletred", 0xc71585 }, |
| 125 | { "midnightblue", 0x191970 }, |
| 126 | { "mintcream", 0xf5fffa }, |
| 127 | { "mistyrose", 0xffe4e1 }, |
| 128 | { "moccasin", 0xffe4b5 }, |
| 129 | { "navajowhite", 0xffdead }, |
| 130 | { "navy", 0x000080 }, |
| 131 | { "oldlace", 0xfdf5e6 }, |
| 132 | { "olive", 0x808000 }, |
| 133 | { "olivedrab", 0x6b8e23 }, |
| 134 | { "orange", 0xffa500 }, |
| 135 | { "orangered", 0xff4500 }, |
| 136 | { "orchid", 0xda70d6 }, |
| 137 | { "palegoldenrod", 0xeee8aa }, |
| 138 | { "palegreen", 0x98fb98 }, |
| 139 | { "paleturquoise", 0xafeeee }, |
| 140 | { "palevioletred", 0xd87093 }, |
| 141 | { "papayawhip", 0xffefd5 }, |
| 142 | { "peachpuff", 0xffdab9 }, |
| 143 | { "peru", 0xcd853f }, |
| 144 | { "pink", 0xffc0cb }, |
| 145 | { "plum", 0xdda0dd }, |
| 146 | { "powderblue", 0xb0e0e6 }, |
| 147 | { "purple", 0x800080 }, |
| 148 | { "red", 0xff0000 }, |
| 149 | { "rosybrown", 0xbc8f8f }, |
| 150 | { "royalblue", 0x4169e1 }, |
| 151 | { "saddlebrown", 0x8b4513 }, |
| 152 | { "salmon", 0xfa8072 }, |
| 153 | { "sandybrown", 0xf4a460 }, |
| 154 | { "seagreen", 0x2e8b57 }, |
| 155 | { "seashell", 0xfff5ee }, |
| 156 | { "sienna", 0xa0522d }, |
| 157 | { "silver", 0xc0c0c0 }, |
| 158 | { "skyblue", 0x87ceeb }, |
| 159 | { "slateblue", 0x6a5acd }, |
| 160 | { "slategray", 0x708090 }, |
| 161 | { "snow", 0xfffafa }, |
| 162 | { "springgreen", 0x00ff7f }, |
| 163 | { "steelblue", 0x4682b4 }, |
| 164 | { "tan", 0xd2b48c }, |
| 165 | { "teal", 0x008080 }, |
| 166 | { "thistle", 0xd8bfd8 }, |
| 167 | { "tomato", 0xff6347 }, |
| 168 | { "turquoise", 0x40e0d0 }, |
| 169 | { "violet", 0xee82ee }, |
| 170 | { "wheat", 0xf5deb3 }, |
| 171 | { "white", 0xffffff }, |
| 172 | { "whitesmoke", 0xf5f5f5 }, |
| 173 | { "yellow", 0xffff00 }, |
| 174 | { "yellowgreen", 0x9acd32 }, |
| 175 | }; |
| 176 | |
| 177 | /* |
| 178 | ** Attempt to translate a CSS color name into an integer that |
| 179 | ** represents the equivalent RGB value. Ignore alpha if provided. |
| 180 | ** If the name cannot be translated, return -1. |
| 181 | */ |
| 182 | int color_name_to_rgb(const char *zName){ |
| 183 | if( zName==0 || zName[0]==0 ) return -1; |
| 184 | if( zName[0]=='#' ){ |
| 185 | int i, v = 0; |
| 186 | for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){ |
| 187 | v = v*16 + fossil_hexvalue(zName[i]); |
| 188 | } |
| 189 | if( i==4 ){ |
| 190 | v = fossil_hexvalue(zName[1])*0x110000 + |
| 191 | fossil_hexvalue(zName[2])*0x1100 + |
| 192 | fossil_hexvalue(zName[3])*0x11; |
| 193 | return v; |
| 194 | } |
| 195 | if( i==7 ){ |
| 196 | return v; |
| 197 | } |
| 198 | return -1; |
| 199 | }else{ |
| 200 | int iMin = 0; |
| 201 | int iMax = count(aCssColors)-1; |
| 202 | while( iMin<=iMax ){ |
| 203 | int iMid = (iMin+iMax)/2; |
| 204 | int c = sqlite3_stricmp(aCssColors[iMid].zName, zName); |
| 205 | if( c==0 ) return aCssColors[iMid].iRGB; |
| 206 | if( c<0 ){ |
| 207 | iMin = iMid+1; |
| 208 | }else{ |
| 209 | iMax = iMid-1; |
| 210 | } |
| 211 | } |
| 212 | return -1; |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | /* |
| 217 | ** SETTING: raw-bgcolor boolean default=off |
| 218 | ** |
| 219 | ** Fossil usually tries to adjust user-specified background colors |
| 220 | ** for checkins so that the text is readable and so that the color |
| 221 | ** is not too garish. This setting disables that filter. When |
| 222 | ** this setting is on, the user-selected background colors are shown |
| 223 | ** exactly as requested. |
| 224 | */ |
| 225 | |
| 226 | /* |
| 227 | ** Shift a color provided by the user so that it is suitable |
| 228 | ** for use as a background color in the current skin. |
| 229 | ** |
| 230 | ** The return value is a #HHHHHH color name contained in |
| 231 | ** static space that is overwritten on the next call. |
| 232 | ** |
| 233 | ** If we cannot make sense of the background color recommendation |
| 234 | ** that is the input, then return NULL. |
| 235 | ** |
| 236 | ** The iFgClr parameter is normally 0. But for testing purposes, set |
| 237 | ** it to 1 for a black foregrounds and 2 for a white foreground. |
| 238 | */ |
| 239 | const char *reasonable_bg_color(const char *zRequested, int iFgClr){ |
| 240 | int iRGB = color_name_to_rgb(zRequested); |
| 241 | int r, g, b; /* RGB components of requested color */ |
| 242 | static int systemFg = 0; /* 1==black-foreground 2==white-foreground */ |
| 243 | int fg; /* Foreground color to actually use */ |
| 244 | static char zColor[10]; /* Return value */ |
| 245 | |
| 246 | if( iFgClr ){ |
| 247 | fg = iFgClr; |
| 248 | }else if( systemFg==0 ){ |
| 249 | if( db_get_boolean("raw-bgcolor",0) ){ |
| 250 | fg = systemFg = 3; |
| 251 | }else{ |
| 252 | fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1; |
| 253 | } |
| 254 | }else{ |
| 255 | fg = systemFg; |
| 256 | } |
| 257 | if( fg>=3 ) return zRequested; |
| 258 | |
| 259 | if( iRGB<0 ) return 0; |
| 260 | r = (iRGB>>16) & 0xff; |
| 261 | g = (iRGB>>8) & 0xff; |
| 262 | b = iRGB & 0xff; |
| 263 | if( fg==1 ){ |
| 264 | const int K = 70; |
| 265 | r = (K*r)/255 + (255-K); |
| 266 | g = (K*g)/255 + (255-K); |
| 267 | b = (K*b)/255 + (255-K); |
| 268 | }else{ |
| 269 | const int K = 90; |
| 270 | r = (K*r)/255; |
| 271 | g = (K*g)/255; |
| 272 | b = (K*b)/255; |
| 273 | } |
| 274 | sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b); |
| 275 | return zColor; |
| 276 | } |
| 277 | |
| 278 | /* |
| 279 | ** Compute a hash on a branch or user name |
| 280 | */ |
| 281 | static unsigned int hash_of_name(const char *z){ |
| @@ -185,5 +437,86 @@ | |
| 437 | @ <input type="submit" value="Submit"> |
| 438 | @ <input type="submit" name="rand" value="Random"> |
| 439 | @ </form> |
| 440 | style_finish_page(); |
| 441 | } |
| 442 | |
| 443 | /* |
| 444 | ** WEBPAGE: test-bgcolor |
| 445 | ** |
| 446 | ** Show how user-specified background colors will be rendered |
| 447 | ** using the reasonable_bg_color() algorithm. |
| 448 | */ |
| 449 | void test_bgcolor_page(void){ |
| 450 | const char *zReq; /* Requested color name */ |
| 451 | const char *zBG; /* Actual color provided */ |
| 452 | const char *zBg1; |
| 453 | char zNm[10]; |
| 454 | static const char *azDflt[] = { |
| 455 | "red", "orange", "yellow", "green", "blue", "indigo", "violet", |
| 456 | "tan", "brown", "gray" |
| 457 | }; |
| 458 | int i, cnt, iClr, r, g, b; |
| 459 | char *zFg; |
| 460 | login_check_credentials(); |
| 461 | style_set_current_feature("test"); |
| 462 | style_header("Background Color Test"); |
| 463 | for(i=cnt=0; i<10; i++){ |
| 464 | sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); |
| 465 | zReq = PD(zNm,azDflt[i]); |
| 466 | if( zReq==0 || zReq[0]==0 ) continue; |
| 467 | if( cnt==0 ){ |
| 468 | @ <table border="1" cellspacing="0" cellpadding="10"> |
| 469 | @ <tr> |
| 470 | @ <th>Requested Background |
| 471 | @ <th>Light mode |
| 472 | @ <th>Dark mode |
| 473 | @ </tr> |
| 474 | } |
| 475 | cnt++; |
| 476 | zBG = reasonable_bg_color(zReq, 0); |
| 477 | if( zBG==0 ){ |
| 478 | @ <tr><td colspan="3" align="center">\ |
| 479 | @ "%h(zReq)" is not a recognized color name</td></tr> |
| 480 | continue; |
| 481 | } |
| 482 | iClr = color_name_to_rgb(zReq); |
| 483 | r = (iClr>>16) & 0xff; |
| 484 | g = (iClr>>8) & 0xff; |
| 485 | b = iClr & 0xff; |
| 486 | if( 3*r + 7*g + b > 6*255 ){ |
| 487 | zFg = "black"; |
| 488 | }else{ |
| 489 | zFg = "white"; |
| 490 | } |
| 491 | if( zReq[0]!='#' ){ |
| 492 | char zReqRGB[12]; |
| 493 | sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq)); |
| 494 | @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\ |
| 495 | @ Requested color "%h(zReq)" (%h(zReqRGB))</td> |
| 496 | }else{ |
| 497 | @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\ |
| 498 | @ Requested color "%h(zReq)"</td> |
| 499 | } |
| 500 | zBg1 = reasonable_bg_color(zReq,1); |
| 501 | @ <td style='color:black;background-color:%h(zBg1);'>\ |
| 502 | @ Background color for dark text: %h(zBg1)</td> |
| 503 | zBg1 = reasonable_bg_color(zReq,2); |
| 504 | @ <td style='color:white;background-color:%h(zBg1);'>\ |
| 505 | @ Background color for light text: %h(zBg1)</td></tr> |
| 506 | } |
| 507 | if( cnt ){ |
| 508 | @ </table> |
| 509 | @ <hr> |
| 510 | } |
| 511 | @ <form method="POST"> |
| 512 | @ <p>Enter CSS color names below and see them shifted into corresponding |
| 513 | @ background colors above.</p> |
| 514 | for(i=0; i<10; i++){ |
| 515 | sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); |
| 516 | @ <input type="text" size="30" name='%s(zNm)' \ |
| 517 | @ value='%h(PD(zNm,azDflt[i]))'><br> |
| 518 | } |
| 519 | @ <input type="submit" value="Submit"> |
| 520 | @ </form> |
| 521 | style_finish_page(); |
| 522 | } |
| 523 |
+4
-4
| --- src/comformat.c | ||
| +++ src/comformat.c | ||
| @@ -266,14 +266,14 @@ | ||
| 266 | 266 | int i = 0; /* Counted bytes. */ |
| 267 | 267 | int cchUTF8 = 1; /* Code units consumed. */ |
| 268 | 268 | int maxUTF8 = 1; /* Expected sequence length. */ |
| 269 | 269 | char c = z[i++]; |
| 270 | 270 | if( c==0x1b && z[i]=='[' ){ |
| 271 | - do{ | |
| 272 | - i++; | |
| 273 | - }while( i<fossil_isdigit(z[i]) || z[i]==';' ); | |
| 274 | - if( fossil_isalpha(z[i]) ){ | |
| 271 | + i++; | |
| 272 | + while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } | |
| 273 | + while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } | |
| 274 | + if( z[i]>=0x40 && z[i]<=0x7e ){ | |
| 275 | 275 | *pCchUTF8 = i+1; |
| 276 | 276 | *pUtf32 = 0x301; /* A zero-width character */ |
| 277 | 277 | return; |
| 278 | 278 | } |
| 279 | 279 | } |
| 280 | 280 |
| --- src/comformat.c | |
| +++ src/comformat.c | |
| @@ -266,14 +266,14 @@ | |
| 266 | int i = 0; /* Counted bytes. */ |
| 267 | int cchUTF8 = 1; /* Code units consumed. */ |
| 268 | int maxUTF8 = 1; /* Expected sequence length. */ |
| 269 | char c = z[i++]; |
| 270 | if( c==0x1b && z[i]=='[' ){ |
| 271 | do{ |
| 272 | i++; |
| 273 | }while( i<fossil_isdigit(z[i]) || z[i]==';' ); |
| 274 | if( fossil_isalpha(z[i]) ){ |
| 275 | *pCchUTF8 = i+1; |
| 276 | *pUtf32 = 0x301; /* A zero-width character */ |
| 277 | return; |
| 278 | } |
| 279 | } |
| 280 |
| --- src/comformat.c | |
| +++ src/comformat.c | |
| @@ -266,14 +266,14 @@ | |
| 266 | int i = 0; /* Counted bytes. */ |
| 267 | int cchUTF8 = 1; /* Code units consumed. */ |
| 268 | int maxUTF8 = 1; /* Expected sequence length. */ |
| 269 | char c = z[i++]; |
| 270 | if( c==0x1b && z[i]=='[' ){ |
| 271 | i++; |
| 272 | while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } |
| 273 | while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } |
| 274 | if( z[i]>=0x40 && z[i]<=0x7e ){ |
| 275 | *pCchUTF8 = i+1; |
| 276 | *pUtf32 = 0x301; /* A zero-width character */ |
| 277 | return; |
| 278 | } |
| 279 | } |
| 280 |
-1
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -100,11 +100,10 @@ | ||
| 100 | 100 | { "logo-image", CONFIGSET_SKIN }, |
| 101 | 101 | { "background-mimetype", CONFIGSET_SKIN }, |
| 102 | 102 | { "background-image", CONFIGSET_SKIN }, |
| 103 | 103 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 104 | 104 | { "icon-image", CONFIGSET_SKIN }, |
| 105 | - { "timeline-block-markup", CONFIGSET_SKIN }, | |
| 106 | 105 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 107 | 106 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 108 | 107 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 109 | 108 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 110 | 109 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 111 | 110 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -100,11 +100,10 @@ | |
| 100 | { "logo-image", CONFIGSET_SKIN }, |
| 101 | { "background-mimetype", CONFIGSET_SKIN }, |
| 102 | { "background-image", CONFIGSET_SKIN }, |
| 103 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 104 | { "icon-image", CONFIGSET_SKIN }, |
| 105 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 106 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 107 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 108 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 109 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 110 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 111 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -100,11 +100,10 @@ | |
| 100 | { "logo-image", CONFIGSET_SKIN }, |
| 101 | { "background-mimetype", CONFIGSET_SKIN }, |
| 102 | { "background-image", CONFIGSET_SKIN }, |
| 103 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 104 | { "icon-image", CONFIGSET_SKIN }, |
| 105 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 106 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 107 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 108 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 109 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 110 |
M
src/db.c
+4
-1
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1629,10 +1629,12 @@ | ||
| 1629 | 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | 1631 | chat_msg_from_event, 0, 0); |
| 1632 | 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | 1633 | file_inode_sql_func,0,0); |
| 1634 | + sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0, | |
| 1635 | + artifact_to_json_sql_func,0,0); | |
| 1634 | 1636 | |
| 1635 | 1637 | } |
| 1636 | 1638 | |
| 1637 | 1639 | #if USE_SEE |
| 1638 | 1640 | /* |
| @@ -5067,11 +5069,12 @@ | ||
| 5067 | 5069 | ** |
| 5068 | 5070 | ** All repositories are searched (in lexicographical order) and the first |
| 5069 | 5071 | ** repository with a non-zero "repolist-skin" value is used as the skin |
| 5070 | 5072 | ** for the repository list page. If none of the repositories on the list |
| 5071 | 5073 | ** have a non-zero "repolist-skin" setting then the repository list is |
| 5072 | -** displayed using unadorned HTML ("skinless"). | |
| 5074 | +** displayed using unadorned HTML ("skinless"), with the page title taken | |
| 5075 | +** from the FOSSIL_REPOLIST_TITLE environment variable. | |
| 5073 | 5076 | ** |
| 5074 | 5077 | ** If repolist-skin has a value of 2, then the repository is omitted from |
| 5075 | 5078 | ** the list in use cases 1 through 4, but not for 5 and 6. |
| 5076 | 5079 | */ |
| 5077 | 5080 | /* |
| 5078 | 5081 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1629,10 +1629,12 @@ | |
| 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | chat_msg_from_event, 0, 0); |
| 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | file_inode_sql_func,0,0); |
| 1634 | |
| 1635 | } |
| 1636 | |
| 1637 | #if USE_SEE |
| 1638 | /* |
| @@ -5067,11 +5069,12 @@ | |
| 5067 | ** |
| 5068 | ** All repositories are searched (in lexicographical order) and the first |
| 5069 | ** repository with a non-zero "repolist-skin" value is used as the skin |
| 5070 | ** for the repository list page. If none of the repositories on the list |
| 5071 | ** have a non-zero "repolist-skin" setting then the repository list is |
| 5072 | ** displayed using unadorned HTML ("skinless"). |
| 5073 | ** |
| 5074 | ** If repolist-skin has a value of 2, then the repository is omitted from |
| 5075 | ** the list in use cases 1 through 4, but not for 5 and 6. |
| 5076 | */ |
| 5077 | /* |
| 5078 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1629,10 +1629,12 @@ | |
| 1629 | sqlite3_create_function(db, "chat_msg_from_event", 4, |
| 1630 | SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, |
| 1631 | chat_msg_from_event, 0, 0); |
| 1632 | sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0, |
| 1633 | file_inode_sql_func,0,0); |
| 1634 | sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0, |
| 1635 | artifact_to_json_sql_func,0,0); |
| 1636 | |
| 1637 | } |
| 1638 | |
| 1639 | #if USE_SEE |
| 1640 | /* |
| @@ -5067,11 +5069,12 @@ | |
| 5069 | ** |
| 5070 | ** All repositories are searched (in lexicographical order) and the first |
| 5071 | ** repository with a non-zero "repolist-skin" value is used as the skin |
| 5072 | ** for the repository list page. If none of the repositories on the list |
| 5073 | ** have a non-zero "repolist-skin" setting then the repository list is |
| 5074 | ** displayed using unadorned HTML ("skinless"), with the page title taken |
| 5075 | ** from the FOSSIL_REPOLIST_TITLE environment variable. |
| 5076 | ** |
| 5077 | ** If repolist-skin has a value of 2, then the repository is omitted from |
| 5078 | ** the list in use cases 1 through 4, but not for 5 and 6. |
| 5079 | */ |
| 5080 | /* |
| 5081 |
+1
-1
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -639,11 +639,11 @@ | ||
| 639 | 639 | ** are special to HTML encoded. We need to decode these before turning |
| 640 | 640 | ** the text into a title, as the title text will be reencoded later */ |
| 641 | 641 | char *zTitle = mprintf("%.*s", nValue, zValue); |
| 642 | 642 | int i; |
| 643 | 643 | for(i=0; fossil_isspace(zTitle[i]); i++){} |
| 644 | - html_to_plaintext(zTitle+i, pTitle); | |
| 644 | + html_to_plaintext(zTitle+i, pTitle, 0); | |
| 645 | 645 | fossil_free(zTitle); |
| 646 | 646 | seenTitle = 1; |
| 647 | 647 | if( seenClass ) return 1; |
| 648 | 648 | } |
| 649 | 649 | } |
| 650 | 650 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -639,11 +639,11 @@ | |
| 639 | ** are special to HTML encoded. We need to decode these before turning |
| 640 | ** the text into a title, as the title text will be reencoded later */ |
| 641 | char *zTitle = mprintf("%.*s", nValue, zValue); |
| 642 | int i; |
| 643 | for(i=0; fossil_isspace(zTitle[i]); i++){} |
| 644 | html_to_plaintext(zTitle+i, pTitle); |
| 645 | fossil_free(zTitle); |
| 646 | seenTitle = 1; |
| 647 | if( seenClass ) return 1; |
| 648 | } |
| 649 | } |
| 650 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -639,11 +639,11 @@ | |
| 639 | ** are special to HTML encoded. We need to decode these before turning |
| 640 | ** the text into a title, as the title text will be reencoded later */ |
| 641 | char *zTitle = mprintf("%.*s", nValue, zValue); |
| 642 | int i; |
| 643 | for(i=0; fossil_isspace(zTitle[i]); i++){} |
| 644 | html_to_plaintext(zTitle+i, pTitle, 0); |
| 645 | fossil_free(zTitle); |
| 646 | seenTitle = 1; |
| 647 | if( seenClass ) return 1; |
| 648 | } |
| 649 | } |
| 650 |
+3
-3
| --- src/encode.c | ||
| +++ src/encode.c | ||
| @@ -244,11 +244,11 @@ | ||
| 244 | 244 | } |
| 245 | 245 | |
| 246 | 246 | /* |
| 247 | 247 | ** Convert a single HEX digit to an integer |
| 248 | 248 | */ |
| 249 | -static int AsciiToHex(int c){ | |
| 249 | +int fossil_hexvalue(int c){ | |
| 250 | 250 | if( c>='a' && c<='f' ){ |
| 251 | 251 | c += 10 - 'a'; |
| 252 | 252 | }else if( c>='A' && c<='F' ){ |
| 253 | 253 | c += 10 - 'A'; |
| 254 | 254 | }else if( c>='0' && c<='9' ){ |
| @@ -272,12 +272,12 @@ | ||
| 272 | 272 | i = j = 0; |
| 273 | 273 | while( z[i] ){ |
| 274 | 274 | switch( z[i] ){ |
| 275 | 275 | case '%': |
| 276 | 276 | if( z[i+1] && z[i+2] ){ |
| 277 | - z[j] = AsciiToHex(z[i+1]) << 4; | |
| 278 | - z[j] |= AsciiToHex(z[i+2]); | |
| 277 | + z[j] = fossil_hexvalue(z[i+1]) << 4; | |
| 278 | + z[j] |= fossil_hexvalue(z[i+2]); | |
| 279 | 279 | i += 2; |
| 280 | 280 | } |
| 281 | 281 | break; |
| 282 | 282 | case '+': |
| 283 | 283 | z[j] = ' '; |
| 284 | 284 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -244,11 +244,11 @@ | |
| 244 | } |
| 245 | |
| 246 | /* |
| 247 | ** Convert a single HEX digit to an integer |
| 248 | */ |
| 249 | static int AsciiToHex(int c){ |
| 250 | if( c>='a' && c<='f' ){ |
| 251 | c += 10 - 'a'; |
| 252 | }else if( c>='A' && c<='F' ){ |
| 253 | c += 10 - 'A'; |
| 254 | }else if( c>='0' && c<='9' ){ |
| @@ -272,12 +272,12 @@ | |
| 272 | i = j = 0; |
| 273 | while( z[i] ){ |
| 274 | switch( z[i] ){ |
| 275 | case '%': |
| 276 | if( z[i+1] && z[i+2] ){ |
| 277 | z[j] = AsciiToHex(z[i+1]) << 4; |
| 278 | z[j] |= AsciiToHex(z[i+2]); |
| 279 | i += 2; |
| 280 | } |
| 281 | break; |
| 282 | case '+': |
| 283 | z[j] = ' '; |
| 284 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -244,11 +244,11 @@ | |
| 244 | } |
| 245 | |
| 246 | /* |
| 247 | ** Convert a single HEX digit to an integer |
| 248 | */ |
| 249 | int fossil_hexvalue(int c){ |
| 250 | if( c>='a' && c<='f' ){ |
| 251 | c += 10 - 'a'; |
| 252 | }else if( c>='A' && c<='F' ){ |
| 253 | c += 10 - 'A'; |
| 254 | }else if( c>='0' && c<='9' ){ |
| @@ -272,12 +272,12 @@ | |
| 272 | i = j = 0; |
| 273 | while( z[i] ){ |
| 274 | switch( z[i] ){ |
| 275 | case '%': |
| 276 | if( z[i+1] && z[i+2] ){ |
| 277 | z[j] = fossil_hexvalue(z[i+1]) << 4; |
| 278 | z[j] |= fossil_hexvalue(z[i+2]); |
| 279 | i += 2; |
| 280 | } |
| 281 | break; |
| 282 | case '+': |
| 283 | z[j] = ' '; |
| 284 |
+2
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -636,10 +636,12 @@ | ||
| 636 | 636 | if( zBr==0 ) zBr = "trunk"; |
| 637 | 637 | if( uBg ){ |
| 638 | 638 | zBgClr = user_color(zUser); |
| 639 | 639 | }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ |
| 640 | 640 | zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); |
| 641 | + }else if( zBgClr ){ | |
| 642 | + zBgClr = reasonable_bg_color(zBgClr,0); | |
| 641 | 643 | } |
| 642 | 644 | gidx = graph_add_row(pGraph, |
| 643 | 645 | frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000, |
| 644 | 646 | nParent, 0, aParent, zBr, zBgClr, |
| 645 | 647 | zUuid, 0); |
| 646 | 648 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -636,10 +636,12 @@ | |
| 636 | if( zBr==0 ) zBr = "trunk"; |
| 637 | if( uBg ){ |
| 638 | zBgClr = user_color(zUser); |
| 639 | }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ |
| 640 | zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); |
| 641 | } |
| 642 | gidx = graph_add_row(pGraph, |
| 643 | frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000, |
| 644 | nParent, 0, aParent, zBr, zBgClr, |
| 645 | zUuid, 0); |
| 646 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -636,10 +636,12 @@ | |
| 636 | if( zBr==0 ) zBr = "trunk"; |
| 637 | if( uBg ){ |
| 638 | zBgClr = user_color(zUser); |
| 639 | }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ |
| 640 | zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); |
| 641 | }else if( zBgClr ){ |
| 642 | zBgClr = reasonable_bg_color(zBgClr,0); |
| 643 | } |
| 644 | gidx = graph_add_row(pGraph, |
| 645 | frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000, |
| 646 | nParent, 0, aParent, zBr, zBgClr, |
| 647 | zUuid, 0); |
| 648 |
+29
-11
| --- src/graph.c | ||
| +++ src/graph.c | ||
| @@ -59,11 +59,11 @@ | ||
| 59 | 59 | ** check-ins and many files. For this reason, we make the identifier |
| 60 | 60 | ** a 64-bit integer, to dramatically reduce the risk of an overflow. |
| 61 | 61 | */ |
| 62 | 62 | typedef sqlite3_int64 GraphRowId; |
| 63 | 63 | |
| 64 | -#define GR_MAX_RAIL 40 /* Max number of "rails" to display */ | |
| 64 | +#define GR_MAX_RAIL 64 /* Max number of "rails" to display */ | |
| 65 | 65 | |
| 66 | 66 | /* The graph appears vertically beside a timeline. Each row in the |
| 67 | 67 | ** timeline corresponds to a row in the graph. GraphRow.idx is 0 for |
| 68 | 68 | ** the top-most row and increases moving down. Hence (in the absence of |
| 69 | 69 | ** time skew) parents have a larger index than their children. |
| @@ -118,10 +118,11 @@ | ||
| 118 | 118 | char **azBranch; /* Names of the branches */ |
| 119 | 119 | int nRow; /* Number of rows */ |
| 120 | 120 | int nHash; /* Number of slots in apHash[] */ |
| 121 | 121 | u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different |
| 122 | 122 | ** rail that the node */ |
| 123 | + u8 bOverfull; /* Unable to allocate sufficient rails */ | |
| 123 | 124 | u64 mergeRail; /* Rails used for merge lines */ |
| 124 | 125 | GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ |
| 125 | 126 | u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */ |
| 126 | 127 | }; |
| 127 | 128 | |
| @@ -336,11 +337,18 @@ | ||
| 336 | 337 | iBestDist = dist; |
| 337 | 338 | iBest = i; |
| 338 | 339 | } |
| 339 | 340 | } |
| 340 | 341 | } |
| 341 | - if( iBestDist>1000 ) p->nErr++; | |
| 342 | + if( iBestDist>1000 ){ | |
| 343 | + p->bOverfull = 1; | |
| 344 | + iBest = GR_MAX_RAIL; | |
| 345 | + } | |
| 346 | + if( iBest>GR_MAX_RAIL ){ | |
| 347 | + p->bOverfull = 1; | |
| 348 | + iBest = GR_MAX_RAIL; | |
| 349 | + } | |
| 342 | 350 | if( iBest>p->mxRail ) p->mxRail = iBest; |
| 343 | 351 | if( bMergeRail ) p->mergeRail |= BIT(iBest); |
| 344 | 352 | return iBest; |
| 345 | 353 | } |
| 346 | 354 | |
| @@ -709,11 +717,11 @@ | ||
| 709 | 717 | if( pRow->iRail>=0 ) continue; |
| 710 | 718 | if( pRow->isDup ) continue; |
| 711 | 719 | if( pRow->nParent<0 ) continue; |
| 712 | 720 | if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ |
| 713 | 721 | pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0); |
| 714 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 722 | + /* if( p->mxRail>=GR_MAX_RAIL ) return; */ | |
| 715 | 723 | mask = BIT(pRow->iRail); |
| 716 | 724 | if( !omitDescenders ){ |
| 717 | 725 | int n = RISER_MARGIN; |
| 718 | 726 | pRow->bDescender = pRow->nParent>0; |
| 719 | 727 | for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){ |
| @@ -744,28 +752,38 @@ | ||
| 744 | 752 | assert( pRow->nParent>0 ); |
| 745 | 753 | parentRid = pRow->aParent[0]; |
| 746 | 754 | pParent = hashFind(p, parentRid); |
| 747 | 755 | if( pParent==0 ){ |
| 748 | 756 | pRow->iRail = ++p->mxRail; |
| 749 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 757 | + if( p->mxRail>=GR_MAX_RAIL ){ | |
| 758 | + pRow->iRail = p->mxRail = GR_MAX_RAIL; | |
| 759 | + p->bOverfull = 1; | |
| 760 | + } | |
| 750 | 761 | pRow->railInUse = BIT(pRow->iRail); |
| 751 | 762 | continue; |
| 752 | 763 | } |
| 753 | 764 | if( pParent->idx>pRow->idx ){ |
| 754 | 765 | /* Common case: Child occurs after parent and is above the |
| 755 | 766 | ** parent in the timeline */ |
| 756 | 767 | pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx, |
| 757 | 768 | pParent->iRail, 0); |
| 758 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 769 | + /* if( p->mxRail>=GR_MAX_RAIL ) return; */ | |
| 759 | 770 | pParent->aiRiser[pRow->iRail] = pRow->idx; |
| 760 | 771 | }else{ |
| 761 | 772 | /* Timewarp case: Child occurs earlier in time than parent and |
| 762 | 773 | ** appears below the parent in the timeline. */ |
| 763 | 774 | int iDownRail = ++p->mxRail; |
| 764 | 775 | if( iDownRail<1 ) iDownRail = ++p->mxRail; |
| 776 | + if( p->mxRail>GR_MAX_RAIL ){ | |
| 777 | + iDownRail = p->mxRail = GR_MAX_RAIL; | |
| 778 | + p->bOverfull = 1; | |
| 779 | + } | |
| 765 | 780 | pRow->iRail = ++p->mxRail; |
| 766 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 781 | + if( p->mxRail>=GR_MAX_RAIL ){ | |
| 782 | + pRow->iRail = p->mxRail = GR_MAX_RAIL; | |
| 783 | + p->bOverfull = 1; | |
| 784 | + } | |
| 767 | 785 | pRow->railInUse = BIT(pRow->iRail); |
| 768 | 786 | pParent->aiRiser[iDownRail] = pRow->idx; |
| 769 | 787 | mask = BIT(iDownRail); |
| 770 | 788 | for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){ |
| 771 | 789 | pLoop->railInUse |= mask; |
| @@ -824,11 +842,11 @@ | ||
| 824 | 842 | break; |
| 825 | 843 | } |
| 826 | 844 | } |
| 827 | 845 | if( iMrail==-1 ){ |
| 828 | 846 | iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1); |
| 829 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 847 | + /*if( p->mxRail>=GR_MAX_RAIL ) return;*/ | |
| 830 | 848 | mergeRiserFrom[iMrail] = parentRid; |
| 831 | 849 | } |
| 832 | 850 | iReuseIdx = p->nRow+1; |
| 833 | 851 | iReuseRail = iMrail; |
| 834 | 852 | mask = BIT(iMrail); |
| @@ -856,11 +874,11 @@ | ||
| 856 | 874 | pDesc->mergeUpto = pDesc->idx; |
| 857 | 875 | } |
| 858 | 876 | }else{ |
| 859 | 877 | /* Create a new merge for an on-screen node */ |
| 860 | 878 | createMergeRiser(p, pDesc, pRow, isCherrypick); |
| 861 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 879 | + /* if( p->mxRail>=GR_MAX_RAIL ) return; */ | |
| 862 | 880 | if( iReuseIdx<0 |
| 863 | 881 | && pDesc->nMergeChild==1 |
| 864 | 882 | && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf) |
| 865 | 883 | ){ |
| 866 | 884 | iReuseIdx = pDesc->idx; |
| @@ -872,17 +890,17 @@ | ||
| 872 | 890 | } |
| 873 | 891 | |
| 874 | 892 | /* |
| 875 | 893 | ** Insert merge rails from primaries to duplicates. |
| 876 | 894 | */ |
| 877 | - if( hasDup ){ | |
| 895 | + if( hasDup && p->mxRail<GR_MAX_RAIL ){ | |
| 878 | 896 | int dupRail; |
| 879 | 897 | int mxRail; |
| 880 | 898 | find_max_rail(p); |
| 881 | 899 | mxRail = p->mxRail; |
| 882 | 900 | dupRail = mxRail+1; |
| 883 | - if( p->mxRail>=GR_MAX_RAIL ) return; | |
| 901 | + /* if( p->mxRail>=GR_MAX_RAIL ) return; */ | |
| 884 | 902 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 885 | 903 | if( !pRow->isDup ) continue; |
| 886 | 904 | pRow->iRail = dupRail; |
| 887 | 905 | pDesc = hashFind(p, pRow->rid); |
| 888 | 906 | assert( pDesc!=0 && pDesc!=pRow ); |
| @@ -893,11 +911,11 @@ | ||
| 893 | 911 | dupRail = mxRail+1; |
| 894 | 912 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 895 | 913 | if( pRow->isDup ) pRow->iRail = dupRail; |
| 896 | 914 | } |
| 897 | 915 | } |
| 898 | - if( mxRail>=GR_MAX_RAIL ) return; | |
| 916 | + /* if( mxRail>=GR_MAX_RAIL ) return; */ | |
| 899 | 917 | } |
| 900 | 918 | |
| 901 | 919 | /* |
| 902 | 920 | ** Find the maximum rail number. |
| 903 | 921 | */ |
| 904 | 922 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -59,11 +59,11 @@ | |
| 59 | ** check-ins and many files. For this reason, we make the identifier |
| 60 | ** a 64-bit integer, to dramatically reduce the risk of an overflow. |
| 61 | */ |
| 62 | typedef sqlite3_int64 GraphRowId; |
| 63 | |
| 64 | #define GR_MAX_RAIL 40 /* Max number of "rails" to display */ |
| 65 | |
| 66 | /* The graph appears vertically beside a timeline. Each row in the |
| 67 | ** timeline corresponds to a row in the graph. GraphRow.idx is 0 for |
| 68 | ** the top-most row and increases moving down. Hence (in the absence of |
| 69 | ** time skew) parents have a larger index than their children. |
| @@ -118,10 +118,11 @@ | |
| 118 | char **azBranch; /* Names of the branches */ |
| 119 | int nRow; /* Number of rows */ |
| 120 | int nHash; /* Number of slots in apHash[] */ |
| 121 | u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different |
| 122 | ** rail that the node */ |
| 123 | u64 mergeRail; /* Rails used for merge lines */ |
| 124 | GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ |
| 125 | u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */ |
| 126 | }; |
| 127 | |
| @@ -336,11 +337,18 @@ | |
| 336 | iBestDist = dist; |
| 337 | iBest = i; |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | if( iBestDist>1000 ) p->nErr++; |
| 342 | if( iBest>p->mxRail ) p->mxRail = iBest; |
| 343 | if( bMergeRail ) p->mergeRail |= BIT(iBest); |
| 344 | return iBest; |
| 345 | } |
| 346 | |
| @@ -709,11 +717,11 @@ | |
| 709 | if( pRow->iRail>=0 ) continue; |
| 710 | if( pRow->isDup ) continue; |
| 711 | if( pRow->nParent<0 ) continue; |
| 712 | if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ |
| 713 | pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0); |
| 714 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 715 | mask = BIT(pRow->iRail); |
| 716 | if( !omitDescenders ){ |
| 717 | int n = RISER_MARGIN; |
| 718 | pRow->bDescender = pRow->nParent>0; |
| 719 | for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){ |
| @@ -744,28 +752,38 @@ | |
| 744 | assert( pRow->nParent>0 ); |
| 745 | parentRid = pRow->aParent[0]; |
| 746 | pParent = hashFind(p, parentRid); |
| 747 | if( pParent==0 ){ |
| 748 | pRow->iRail = ++p->mxRail; |
| 749 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 750 | pRow->railInUse = BIT(pRow->iRail); |
| 751 | continue; |
| 752 | } |
| 753 | if( pParent->idx>pRow->idx ){ |
| 754 | /* Common case: Child occurs after parent and is above the |
| 755 | ** parent in the timeline */ |
| 756 | pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx, |
| 757 | pParent->iRail, 0); |
| 758 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 759 | pParent->aiRiser[pRow->iRail] = pRow->idx; |
| 760 | }else{ |
| 761 | /* Timewarp case: Child occurs earlier in time than parent and |
| 762 | ** appears below the parent in the timeline. */ |
| 763 | int iDownRail = ++p->mxRail; |
| 764 | if( iDownRail<1 ) iDownRail = ++p->mxRail; |
| 765 | pRow->iRail = ++p->mxRail; |
| 766 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 767 | pRow->railInUse = BIT(pRow->iRail); |
| 768 | pParent->aiRiser[iDownRail] = pRow->idx; |
| 769 | mask = BIT(iDownRail); |
| 770 | for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){ |
| 771 | pLoop->railInUse |= mask; |
| @@ -824,11 +842,11 @@ | |
| 824 | break; |
| 825 | } |
| 826 | } |
| 827 | if( iMrail==-1 ){ |
| 828 | iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1); |
| 829 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 830 | mergeRiserFrom[iMrail] = parentRid; |
| 831 | } |
| 832 | iReuseIdx = p->nRow+1; |
| 833 | iReuseRail = iMrail; |
| 834 | mask = BIT(iMrail); |
| @@ -856,11 +874,11 @@ | |
| 856 | pDesc->mergeUpto = pDesc->idx; |
| 857 | } |
| 858 | }else{ |
| 859 | /* Create a new merge for an on-screen node */ |
| 860 | createMergeRiser(p, pDesc, pRow, isCherrypick); |
| 861 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 862 | if( iReuseIdx<0 |
| 863 | && pDesc->nMergeChild==1 |
| 864 | && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf) |
| 865 | ){ |
| 866 | iReuseIdx = pDesc->idx; |
| @@ -872,17 +890,17 @@ | |
| 872 | } |
| 873 | |
| 874 | /* |
| 875 | ** Insert merge rails from primaries to duplicates. |
| 876 | */ |
| 877 | if( hasDup ){ |
| 878 | int dupRail; |
| 879 | int mxRail; |
| 880 | find_max_rail(p); |
| 881 | mxRail = p->mxRail; |
| 882 | dupRail = mxRail+1; |
| 883 | if( p->mxRail>=GR_MAX_RAIL ) return; |
| 884 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 885 | if( !pRow->isDup ) continue; |
| 886 | pRow->iRail = dupRail; |
| 887 | pDesc = hashFind(p, pRow->rid); |
| 888 | assert( pDesc!=0 && pDesc!=pRow ); |
| @@ -893,11 +911,11 @@ | |
| 893 | dupRail = mxRail+1; |
| 894 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 895 | if( pRow->isDup ) pRow->iRail = dupRail; |
| 896 | } |
| 897 | } |
| 898 | if( mxRail>=GR_MAX_RAIL ) return; |
| 899 | } |
| 900 | |
| 901 | /* |
| 902 | ** Find the maximum rail number. |
| 903 | */ |
| 904 |
| --- src/graph.c | |
| +++ src/graph.c | |
| @@ -59,11 +59,11 @@ | |
| 59 | ** check-ins and many files. For this reason, we make the identifier |
| 60 | ** a 64-bit integer, to dramatically reduce the risk of an overflow. |
| 61 | */ |
| 62 | typedef sqlite3_int64 GraphRowId; |
| 63 | |
| 64 | #define GR_MAX_RAIL 64 /* Max number of "rails" to display */ |
| 65 | |
| 66 | /* The graph appears vertically beside a timeline. Each row in the |
| 67 | ** timeline corresponds to a row in the graph. GraphRow.idx is 0 for |
| 68 | ** the top-most row and increases moving down. Hence (in the absence of |
| 69 | ** time skew) parents have a larger index than their children. |
| @@ -118,10 +118,11 @@ | |
| 118 | char **azBranch; /* Names of the branches */ |
| 119 | int nRow; /* Number of rows */ |
| 120 | int nHash; /* Number of slots in apHash[] */ |
| 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 | |
| @@ -336,11 +337,18 @@ | |
| 337 | iBestDist = dist; |
| 338 | iBest = i; |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | if( iBestDist>1000 ){ |
| 343 | p->bOverfull = 1; |
| 344 | iBest = GR_MAX_RAIL; |
| 345 | } |
| 346 | if( iBest>GR_MAX_RAIL ){ |
| 347 | p->bOverfull = 1; |
| 348 | iBest = GR_MAX_RAIL; |
| 349 | } |
| 350 | if( iBest>p->mxRail ) p->mxRail = iBest; |
| 351 | if( bMergeRail ) p->mergeRail |= BIT(iBest); |
| 352 | return iBest; |
| 353 | } |
| 354 | |
| @@ -709,11 +717,11 @@ | |
| 717 | if( pRow->iRail>=0 ) continue; |
| 718 | if( pRow->isDup ) continue; |
| 719 | if( pRow->nParent<0 ) continue; |
| 720 | if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ |
| 721 | pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0); |
| 722 | /* if( p->mxRail>=GR_MAX_RAIL ) return; */ |
| 723 | mask = BIT(pRow->iRail); |
| 724 | if( !omitDescenders ){ |
| 725 | int n = RISER_MARGIN; |
| 726 | pRow->bDescender = pRow->nParent>0; |
| 727 | for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){ |
| @@ -744,28 +752,38 @@ | |
| 752 | assert( pRow->nParent>0 ); |
| 753 | parentRid = pRow->aParent[0]; |
| 754 | pParent = hashFind(p, parentRid); |
| 755 | if( pParent==0 ){ |
| 756 | pRow->iRail = ++p->mxRail; |
| 757 | if( p->mxRail>=GR_MAX_RAIL ){ |
| 758 | pRow->iRail = p->mxRail = GR_MAX_RAIL; |
| 759 | p->bOverfull = 1; |
| 760 | } |
| 761 | pRow->railInUse = BIT(pRow->iRail); |
| 762 | continue; |
| 763 | } |
| 764 | if( pParent->idx>pRow->idx ){ |
| 765 | /* Common case: Child occurs after parent and is above the |
| 766 | ** parent in the timeline */ |
| 767 | pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx, |
| 768 | pParent->iRail, 0); |
| 769 | /* if( p->mxRail>=GR_MAX_RAIL ) return; */ |
| 770 | pParent->aiRiser[pRow->iRail] = pRow->idx; |
| 771 | }else{ |
| 772 | /* Timewarp case: Child occurs earlier in time than parent and |
| 773 | ** appears below the parent in the timeline. */ |
| 774 | int iDownRail = ++p->mxRail; |
| 775 | if( iDownRail<1 ) iDownRail = ++p->mxRail; |
| 776 | if( p->mxRail>GR_MAX_RAIL ){ |
| 777 | iDownRail = p->mxRail = GR_MAX_RAIL; |
| 778 | p->bOverfull = 1; |
| 779 | } |
| 780 | pRow->iRail = ++p->mxRail; |
| 781 | if( p->mxRail>=GR_MAX_RAIL ){ |
| 782 | pRow->iRail = p->mxRail = GR_MAX_RAIL; |
| 783 | p->bOverfull = 1; |
| 784 | } |
| 785 | pRow->railInUse = BIT(pRow->iRail); |
| 786 | pParent->aiRiser[iDownRail] = pRow->idx; |
| 787 | mask = BIT(iDownRail); |
| 788 | for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){ |
| 789 | pLoop->railInUse |= mask; |
| @@ -824,11 +842,11 @@ | |
| 842 | break; |
| 843 | } |
| 844 | } |
| 845 | if( iMrail==-1 ){ |
| 846 | iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1); |
| 847 | /*if( p->mxRail>=GR_MAX_RAIL ) return;*/ |
| 848 | mergeRiserFrom[iMrail] = parentRid; |
| 849 | } |
| 850 | iReuseIdx = p->nRow+1; |
| 851 | iReuseRail = iMrail; |
| 852 | mask = BIT(iMrail); |
| @@ -856,11 +874,11 @@ | |
| 874 | pDesc->mergeUpto = pDesc->idx; |
| 875 | } |
| 876 | }else{ |
| 877 | /* Create a new merge for an on-screen node */ |
| 878 | createMergeRiser(p, pDesc, pRow, isCherrypick); |
| 879 | /* if( p->mxRail>=GR_MAX_RAIL ) return; */ |
| 880 | if( iReuseIdx<0 |
| 881 | && pDesc->nMergeChild==1 |
| 882 | && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf) |
| 883 | ){ |
| 884 | iReuseIdx = pDesc->idx; |
| @@ -872,17 +890,17 @@ | |
| 890 | } |
| 891 | |
| 892 | /* |
| 893 | ** Insert merge rails from primaries to duplicates. |
| 894 | */ |
| 895 | if( hasDup && p->mxRail<GR_MAX_RAIL ){ |
| 896 | int dupRail; |
| 897 | int mxRail; |
| 898 | find_max_rail(p); |
| 899 | mxRail = p->mxRail; |
| 900 | dupRail = mxRail+1; |
| 901 | /* if( p->mxRail>=GR_MAX_RAIL ) return; */ |
| 902 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 903 | if( !pRow->isDup ) continue; |
| 904 | pRow->iRail = dupRail; |
| 905 | pDesc = hashFind(p, pRow->rid); |
| 906 | assert( pDesc!=0 && pDesc!=pRow ); |
| @@ -893,11 +911,11 @@ | |
| 911 | dupRail = mxRail+1; |
| 912 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 913 | if( pRow->isDup ) pRow->iRail = dupRail; |
| 914 | } |
| 915 | } |
| 916 | /* if( mxRail>=GR_MAX_RAIL ) return; */ |
| 917 | } |
| 918 | |
| 919 | /* |
| 920 | ** Find the maximum rail number. |
| 921 | */ |
| 922 |
+10
-1
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -512,10 +512,13 @@ | ||
| 512 | 512 | transport_send(&g.url, &hdr); |
| 513 | 513 | transport_send(&g.url, &payload); |
| 514 | 514 | blob_reset(&hdr); |
| 515 | 515 | blob_reset(&payload); |
| 516 | 516 | transport_flip(&g.url); |
| 517 | + if( mHttpFlags & HTTP_VERBOSE ){ | |
| 518 | + fossil_print("IP-Address: %s\n", g.zIpAddr); | |
| 519 | + } | |
| 517 | 520 | |
| 518 | 521 | /* |
| 519 | 522 | ** Read and interpret the server reply |
| 520 | 523 | */ |
| 521 | 524 | closeConnection = 1; |
| @@ -572,10 +575,11 @@ | ||
| 572 | 575 | } |
| 573 | 576 | }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) && |
| 574 | 577 | fossil_strnicmp(zLine, "location:", 9)==0 ){ |
| 575 | 578 | int i, j; |
| 576 | 579 | int wasHttps; |
| 580 | + int priorUrlFlags; | |
| 577 | 581 | |
| 578 | 582 | if ( --maxRedirect == 0){ |
| 579 | 583 | fossil_warning("redirect limit exceeded"); |
| 580 | 584 | goto write_err; |
| 581 | 585 | } |
| @@ -596,10 +600,11 @@ | ||
| 596 | 600 | fossil_warning("cannot redirect from %s to %s", g.url.canonical, |
| 597 | 601 | &zLine[i]); |
| 598 | 602 | goto write_err; |
| 599 | 603 | } |
| 600 | 604 | wasHttps = g.url.isHttps; |
| 605 | + priorUrlFlags = g.url.flags; | |
| 601 | 606 | url_parse(&zLine[i], 0); |
| 602 | 607 | if( wasHttps && !g.url.isHttps ){ |
| 603 | 608 | fossil_warning("cannot redirect from HTTPS to HTTP"); |
| 604 | 609 | goto write_err; |
| 605 | 610 | } |
| @@ -610,11 +615,14 @@ | ||
| 610 | 615 | transport_close(&g.url); |
| 611 | 616 | transport_global_shutdown(&g.url); |
| 612 | 617 | fSeenHttpAuth = 0; |
| 613 | 618 | if( g.zHttpAuth ) free(g.zHttpAuth); |
| 614 | 619 | g.zHttpAuth = get_httpauth(); |
| 615 | - if( rc==301 || rc==308 ) url_remember(); | |
| 620 | + if( (rc==301 || rc==308) && (priorUrlFlags & URL_REMEMBER)!=0 ){ | |
| 621 | + g.url.flags |= URL_REMEMBER; | |
| 622 | + url_remember(); | |
| 623 | + } | |
| 616 | 624 | return http_exchange(pSend, pReply, mHttpFlags, |
| 617 | 625 | maxRedirect, zAltMimetype); |
| 618 | 626 | }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ |
| 619 | 627 | if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ |
| 620 | 628 | isCompressed = 0; |
| @@ -793,10 +801,11 @@ | ||
| 793 | 801 | } |
| 794 | 802 | if( find_option("xfer",0,0)!=0 ){ |
| 795 | 803 | mHttpFlags |= HTTP_USE_LOGIN; |
| 796 | 804 | mHttpFlags &= ~HTTP_GENERIC; |
| 797 | 805 | } |
| 806 | + if( find_option("ipv4",0,0) ) g.fIPv4 = 1; | |
| 798 | 807 | verify_all_options(); |
| 799 | 808 | if( g.argc<3 || g.argc>5 ){ |
| 800 | 809 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 801 | 810 | } |
| 802 | 811 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| 803 | 812 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -512,10 +512,13 @@ | |
| 512 | transport_send(&g.url, &hdr); |
| 513 | transport_send(&g.url, &payload); |
| 514 | blob_reset(&hdr); |
| 515 | blob_reset(&payload); |
| 516 | transport_flip(&g.url); |
| 517 | |
| 518 | /* |
| 519 | ** Read and interpret the server reply |
| 520 | */ |
| 521 | closeConnection = 1; |
| @@ -572,10 +575,11 @@ | |
| 572 | } |
| 573 | }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) && |
| 574 | fossil_strnicmp(zLine, "location:", 9)==0 ){ |
| 575 | int i, j; |
| 576 | int wasHttps; |
| 577 | |
| 578 | if ( --maxRedirect == 0){ |
| 579 | fossil_warning("redirect limit exceeded"); |
| 580 | goto write_err; |
| 581 | } |
| @@ -596,10 +600,11 @@ | |
| 596 | fossil_warning("cannot redirect from %s to %s", g.url.canonical, |
| 597 | &zLine[i]); |
| 598 | goto write_err; |
| 599 | } |
| 600 | wasHttps = g.url.isHttps; |
| 601 | url_parse(&zLine[i], 0); |
| 602 | if( wasHttps && !g.url.isHttps ){ |
| 603 | fossil_warning("cannot redirect from HTTPS to HTTP"); |
| 604 | goto write_err; |
| 605 | } |
| @@ -610,11 +615,14 @@ | |
| 610 | transport_close(&g.url); |
| 611 | transport_global_shutdown(&g.url); |
| 612 | fSeenHttpAuth = 0; |
| 613 | if( g.zHttpAuth ) free(g.zHttpAuth); |
| 614 | g.zHttpAuth = get_httpauth(); |
| 615 | if( rc==301 || rc==308 ) url_remember(); |
| 616 | return http_exchange(pSend, pReply, mHttpFlags, |
| 617 | maxRedirect, zAltMimetype); |
| 618 | }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ |
| 619 | if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ |
| 620 | isCompressed = 0; |
| @@ -793,10 +801,11 @@ | |
| 793 | } |
| 794 | if( find_option("xfer",0,0)!=0 ){ |
| 795 | mHttpFlags |= HTTP_USE_LOGIN; |
| 796 | mHttpFlags &= ~HTTP_GENERIC; |
| 797 | } |
| 798 | verify_all_options(); |
| 799 | if( g.argc<3 || g.argc>5 ){ |
| 800 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 801 | } |
| 802 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| 803 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -512,10 +512,13 @@ | |
| 512 | transport_send(&g.url, &hdr); |
| 513 | transport_send(&g.url, &payload); |
| 514 | blob_reset(&hdr); |
| 515 | blob_reset(&payload); |
| 516 | transport_flip(&g.url); |
| 517 | if( mHttpFlags & HTTP_VERBOSE ){ |
| 518 | fossil_print("IP-Address: %s\n", g.zIpAddr); |
| 519 | } |
| 520 | |
| 521 | /* |
| 522 | ** Read and interpret the server reply |
| 523 | */ |
| 524 | closeConnection = 1; |
| @@ -572,10 +575,11 @@ | |
| 575 | } |
| 576 | }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) && |
| 577 | fossil_strnicmp(zLine, "location:", 9)==0 ){ |
| 578 | int i, j; |
| 579 | int wasHttps; |
| 580 | int priorUrlFlags; |
| 581 | |
| 582 | if ( --maxRedirect == 0){ |
| 583 | fossil_warning("redirect limit exceeded"); |
| 584 | goto write_err; |
| 585 | } |
| @@ -596,10 +600,11 @@ | |
| 600 | fossil_warning("cannot redirect from %s to %s", g.url.canonical, |
| 601 | &zLine[i]); |
| 602 | goto write_err; |
| 603 | } |
| 604 | wasHttps = g.url.isHttps; |
| 605 | priorUrlFlags = g.url.flags; |
| 606 | url_parse(&zLine[i], 0); |
| 607 | if( wasHttps && !g.url.isHttps ){ |
| 608 | fossil_warning("cannot redirect from HTTPS to HTTP"); |
| 609 | goto write_err; |
| 610 | } |
| @@ -610,11 +615,14 @@ | |
| 615 | transport_close(&g.url); |
| 616 | transport_global_shutdown(&g.url); |
| 617 | fSeenHttpAuth = 0; |
| 618 | if( g.zHttpAuth ) free(g.zHttpAuth); |
| 619 | g.zHttpAuth = get_httpauth(); |
| 620 | if( (rc==301 || rc==308) && (priorUrlFlags & URL_REMEMBER)!=0 ){ |
| 621 | g.url.flags |= URL_REMEMBER; |
| 622 | url_remember(); |
| 623 | } |
| 624 | return http_exchange(pSend, pReply, mHttpFlags, |
| 625 | maxRedirect, zAltMimetype); |
| 626 | }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ |
| 627 | if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ |
| 628 | isCompressed = 0; |
| @@ -793,10 +801,11 @@ | |
| 801 | } |
| 802 | if( find_option("xfer",0,0)!=0 ){ |
| 803 | mHttpFlags |= HTTP_USE_LOGIN; |
| 804 | mHttpFlags &= ~HTTP_GENERIC; |
| 805 | } |
| 806 | if( find_option("ipv4",0,0) ) g.fIPv4 = 1; |
| 807 | verify_all_options(); |
| 808 | if( g.argc<3 || g.argc>5 ){ |
| 809 | usage("URL ?PAYLOAD? ?OUTPUT?"); |
| 810 | } |
| 811 | zInFile = g.argc>=4 ? g.argv[3] : 0; |
| 812 |
+16
-2
| --- src/http_ssl.c | ||
| +++ src/http_ssl.c | ||
| @@ -451,11 +451,18 @@ | ||
| 451 | 451 | ssl_global_init_client(); |
| 452 | 452 | if( pUrlData->useProxy ){ |
| 453 | 453 | int rc; |
| 454 | 454 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 455 | 455 | BIO *sBio = BIO_new_connect(connStr); |
| 456 | - free(connStr); | |
| 456 | + if( g.fIPv4 ){ | |
| 457 | +#ifdef BIO_FAMILY_IPV4 | |
| 458 | + BIO_set_conn_ip_family(sBio, BIO_FAMILY_IPV4); | |
| 459 | +#else | |
| 460 | + fossil_warning("The --ipv4 option is not supported in this build\n"); | |
| 461 | +#endif | |
| 462 | + } | |
| 463 | + fossil_free(connStr); | |
| 457 | 464 | if( BIO_do_connect(sBio)<=0 ){ |
| 458 | 465 | ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)", |
| 459 | 466 | pUrlData->name, pUrlData->port, |
| 460 | 467 | ERR_reason_error_string(ERR_get_error())); |
| 461 | 468 | ssl_close_client(); |
| @@ -503,11 +510,18 @@ | ||
| 503 | 510 | #endif |
| 504 | 511 | |
| 505 | 512 | if( !pUrlData->useProxy ){ |
| 506 | 513 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); |
| 507 | 514 | BIO_set_conn_hostname(iBio, connStr); |
| 508 | - free(connStr); | |
| 515 | + fossil_free(connStr); | |
| 516 | + if( g.fIPv4 ){ | |
| 517 | +#ifdef BIO_FAMILY_IPV4 | |
| 518 | + BIO_set_conn_ip_family(iBio, BIO_FAMILY_IPV4); | |
| 519 | +#else | |
| 520 | + fossil_warning("The --ipv4 option is not supported in this build\n"); | |
| 521 | +#endif | |
| 522 | + } | |
| 509 | 523 | if( BIO_do_connect(iBio)<=0 ){ |
| 510 | 524 | ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", |
| 511 | 525 | pUrlData->name, pUrlData->port, |
| 512 | 526 | ERR_reason_error_string(ERR_get_error())); |
| 513 | 527 | ssl_close_client(); |
| 514 | 528 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -451,11 +451,18 @@ | |
| 451 | ssl_global_init_client(); |
| 452 | if( pUrlData->useProxy ){ |
| 453 | int rc; |
| 454 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 455 | BIO *sBio = BIO_new_connect(connStr); |
| 456 | free(connStr); |
| 457 | if( BIO_do_connect(sBio)<=0 ){ |
| 458 | ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)", |
| 459 | pUrlData->name, pUrlData->port, |
| 460 | ERR_reason_error_string(ERR_get_error())); |
| 461 | ssl_close_client(); |
| @@ -503,11 +510,18 @@ | |
| 503 | #endif |
| 504 | |
| 505 | if( !pUrlData->useProxy ){ |
| 506 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); |
| 507 | BIO_set_conn_hostname(iBio, connStr); |
| 508 | free(connStr); |
| 509 | if( BIO_do_connect(iBio)<=0 ){ |
| 510 | ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", |
| 511 | pUrlData->name, pUrlData->port, |
| 512 | ERR_reason_error_string(ERR_get_error())); |
| 513 | ssl_close_client(); |
| 514 |
| --- src/http_ssl.c | |
| +++ src/http_ssl.c | |
| @@ -451,11 +451,18 @@ | |
| 451 | ssl_global_init_client(); |
| 452 | if( pUrlData->useProxy ){ |
| 453 | int rc; |
| 454 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 455 | BIO *sBio = BIO_new_connect(connStr); |
| 456 | if( g.fIPv4 ){ |
| 457 | #ifdef BIO_FAMILY_IPV4 |
| 458 | BIO_set_conn_ip_family(sBio, BIO_FAMILY_IPV4); |
| 459 | #else |
| 460 | fossil_warning("The --ipv4 option is not supported in this build\n"); |
| 461 | #endif |
| 462 | } |
| 463 | fossil_free(connStr); |
| 464 | if( BIO_do_connect(sBio)<=0 ){ |
| 465 | ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)", |
| 466 | pUrlData->name, pUrlData->port, |
| 467 | ERR_reason_error_string(ERR_get_error())); |
| 468 | ssl_close_client(); |
| @@ -503,11 +510,18 @@ | |
| 510 | #endif |
| 511 | |
| 512 | if( !pUrlData->useProxy ){ |
| 513 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); |
| 514 | BIO_set_conn_hostname(iBio, connStr); |
| 515 | fossil_free(connStr); |
| 516 | if( g.fIPv4 ){ |
| 517 | #ifdef BIO_FAMILY_IPV4 |
| 518 | BIO_set_conn_ip_family(iBio, BIO_FAMILY_IPV4); |
| 519 | #else |
| 520 | fossil_warning("The --ipv4 option is not supported in this build\n"); |
| 521 | #endif |
| 522 | } |
| 523 | if( BIO_do_connect(iBio)<=0 ){ |
| 524 | ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", |
| 525 | pUrlData->name, pUrlData->port, |
| 526 | ERR_reason_error_string(ERR_get_error())); |
| 527 | ssl_close_client(); |
| 528 |
+68
-34
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -2622,31 +2622,36 @@ | ||
| 2622 | 2622 | |
| 2623 | 2623 | /* |
| 2624 | 2624 | ** WEBPAGE: artifact |
| 2625 | 2625 | ** WEBPAGE: file |
| 2626 | 2626 | ** WEBPAGE: whatis |
| 2627 | +** WEBPAGE: docfile | |
| 2627 | 2628 | ** |
| 2628 | 2629 | ** Typical usage: |
| 2629 | 2630 | ** |
| 2630 | 2631 | ** /artifact/HASH |
| 2631 | 2632 | ** /whatis/HASH |
| 2632 | 2633 | ** /file/NAME |
| 2634 | +** /docfile/NAME | |
| 2633 | 2635 | ** |
| 2634 | 2636 | ** Additional query parameters: |
| 2635 | 2637 | ** |
| 2636 | 2638 | ** ln - show line numbers |
| 2637 | 2639 | ** ln=N - highlight line number N |
| 2638 | 2640 | ** ln=M-N - highlight lines M through N inclusive |
| 2639 | 2641 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2640 | 2642 | ** verbose - show more detail in the description |
| 2643 | +** brief - show just the document, not the metadata. The | |
| 2644 | +** /docfile page is an alias for /file?brief | |
| 2641 | 2645 | ** download - redirect to the download (artifact page only) |
| 2642 | 2646 | ** name=NAME - filename or hash as a query parameter |
| 2643 | 2647 | ** filename=NAME - alternative spelling for "name=" |
| 2644 | 2648 | ** fn=NAME - alternative spelling for "name=" |
| 2645 | 2649 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2646 | 2650 | ** identify the file. |
| 2647 | 2651 | ** txt - Force display of unformatted source text |
| 2652 | +** hash - Output only the hash of the artifact | |
| 2648 | 2653 | ** |
| 2649 | 2654 | ** The /artifact page show the complete content of a file |
| 2650 | 2655 | ** identified by HASH. The /whatis page shows only a description |
| 2651 | 2656 | ** of how the artifact is used. The /file page shows the most recent |
| 2652 | 2657 | ** version of the file or directory called NAME, or a list of the |
| @@ -2665,18 +2670,21 @@ | ||
| 2665 | 2670 | void artifact_page(void){ |
| 2666 | 2671 | int rid = 0; |
| 2667 | 2672 | Blob content; |
| 2668 | 2673 | const char *zMime; |
| 2669 | 2674 | Blob downloadName; |
| 2675 | + Blob uuid; | |
| 2670 | 2676 | int renderAsWiki = 0; |
| 2671 | 2677 | int renderAsHtml = 0; |
| 2672 | 2678 | int renderAsSvg = 0; |
| 2673 | 2679 | int objType; |
| 2674 | 2680 | int asText; |
| 2675 | 2681 | const char *zUuid = 0; |
| 2676 | 2682 | u32 objdescFlags = OBJDESC_BASE; |
| 2677 | 2683 | int descOnly = fossil_strcmp(g.zPath,"whatis")==0; |
| 2684 | + int hashOnly = P("hash")!=0; | |
| 2685 | + int docOnly = P("brief")!=0; | |
| 2678 | 2686 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2679 | 2687 | const char *zLn = P("ln"); |
| 2680 | 2688 | const char *zName = P("name"); |
| 2681 | 2689 | const char *zCI = P("ci"); |
| 2682 | 2690 | HQuery url; |
| @@ -2687,10 +2695,14 @@ | ||
| 2687 | 2695 | |
| 2688 | 2696 | login_check_credentials(); |
| 2689 | 2697 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2690 | 2698 | cgi_check_for_malice(); |
| 2691 | 2699 | style_set_current_feature("artifact"); |
| 2700 | + if( fossil_strcmp(g.zPath, "docfile")==0 ){ | |
| 2701 | + isFile = 1; | |
| 2702 | + docOnly = 1; | |
| 2703 | + } | |
| 2692 | 2704 | |
| 2693 | 2705 | /* Capture and normalize the name= and ci= query parameters */ |
| 2694 | 2706 | if( zName==0 ){ |
| 2695 | 2707 | zName = P("filename"); |
| 2696 | 2708 | if( zName==0 ){ |
| @@ -2789,14 +2801,23 @@ | ||
| 2789 | 2801 | url_add_parameter(&url, "verbose", "1"); |
| 2790 | 2802 | objdescFlags |= OBJDESC_DETAIL; |
| 2791 | 2803 | } |
| 2792 | 2804 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2793 | 2805 | etag_check(ETAG_HASH, zUuid); |
| 2806 | + | |
| 2807 | + if( descOnly && hashOnly ){ | |
| 2808 | + blob_set(&uuid, zUuid); | |
| 2809 | + cgi_set_content_type("text/plain"); | |
| 2810 | + cgi_set_content(&uuid); | |
| 2811 | + return; | |
| 2812 | + } | |
| 2794 | 2813 | |
| 2795 | 2814 | asText = P("txt")!=0; |
| 2796 | 2815 | if( isFile ){ |
| 2797 | - if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ | |
| 2816 | + if( docOnly ){ | |
| 2817 | + /* No header */ | |
| 2818 | + }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ | |
| 2798 | 2819 | zCI = "tip"; |
| 2799 | 2820 | @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a> |
| 2800 | 2821 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2801 | 2822 | }else{ |
| 2802 | 2823 | const char *zPath; |
| @@ -2814,17 +2835,19 @@ | ||
| 2814 | 2835 | }else{ |
| 2815 | 2836 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2> |
| 2816 | 2837 | } |
| 2817 | 2838 | blob_reset(&path); |
| 2818 | 2839 | } |
| 2819 | - style_submenu_element("Artifact", "%R/artifact/%S", zUuid); | |
| 2820 | 2840 | zMime = mimetype_from_name(zName); |
| 2821 | - style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", | |
| 2822 | - zName, zCI); | |
| 2823 | - style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", | |
| 2824 | - zName, zCI); | |
| 2825 | - style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName); | |
| 2841 | + if( !docOnly ){ | |
| 2842 | + style_submenu_element("Artifact", "%R/artifact/%S", zUuid); | |
| 2843 | + style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", | |
| 2844 | + zName, zCI); | |
| 2845 | + style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", | |
| 2846 | + zName, zCI); | |
| 2847 | + style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName); | |
| 2848 | + } | |
| 2826 | 2849 | blob_init(&downloadName, zName, -1); |
| 2827 | 2850 | objType = OBJTYPE_CONTENT; |
| 2828 | 2851 | }else{ |
| 2829 | 2852 | @ <h2>Artifact |
| 2830 | 2853 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| @@ -2843,11 +2866,11 @@ | ||
| 2843 | 2866 | cgi_redirectf("%R/raw/%s?at=%T", |
| 2844 | 2867 | db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid), |
| 2845 | 2868 | file_tail(blob_str(&downloadName))); |
| 2846 | 2869 | /*NOTREACHED*/ |
| 2847 | 2870 | } |
| 2848 | - if( g.perm.Admin ){ | |
| 2871 | + if( g.perm.Admin && !docOnly ){ | |
| 2849 | 2872 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2850 | 2873 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| 2851 | 2874 | style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid); |
| 2852 | 2875 | }else{ |
| 2853 | 2876 | style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid); |
| @@ -2888,41 +2911,49 @@ | ||
| 2888 | 2911 | const char *zIp = db_column_text(&q,2); |
| 2889 | 2912 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2890 | 2913 | } |
| 2891 | 2914 | db_finalize(&q); |
| 2892 | 2915 | } |
| 2893 | - style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName)); | |
| 2894 | - if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ | |
| 2895 | - style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); | |
| 2916 | + if( !docOnly ){ | |
| 2917 | + style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName)); | |
| 2918 | + if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ | |
| 2919 | + style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); | |
| 2920 | + } | |
| 2896 | 2921 | } |
| 2897 | 2922 | if( zMime ){ |
| 2898 | 2923 | if( fossil_strcmp(zMime, "text/html")==0 ){ |
| 2899 | 2924 | if( asText ){ |
| 2900 | 2925 | style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2901 | 2926 | }else{ |
| 2902 | 2927 | renderAsHtml = 1; |
| 2903 | - style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); | |
| 2928 | + if( !docOnly ){ | |
| 2929 | + style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); | |
| 2930 | + } | |
| 2904 | 2931 | } |
| 2905 | 2932 | }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 |
| 2906 | 2933 | || fossil_strcmp(zMime, "text/x-markdown")==0 |
| 2907 | 2934 | || fossil_strcmp(zMime, "text/x-pikchr")==0 ){ |
| 2908 | 2935 | if( asText ){ |
| 2909 | 2936 | style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki", |
| 2910 | 2937 | "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2911 | 2938 | }else{ |
| 2912 | 2939 | renderAsWiki = 1; |
| 2913 | - style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); | |
| 2940 | + if( !docOnly ){ | |
| 2941 | + style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); | |
| 2942 | + } | |
| 2914 | 2943 | } |
| 2915 | 2944 | }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){ |
| 2916 | 2945 | if( asText ){ |
| 2917 | 2946 | style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2918 | 2947 | }else{ |
| 2919 | 2948 | renderAsSvg = 1; |
| 2920 | - style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); | |
| 2949 | + if( !docOnly ){ | |
| 2950 | + style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); | |
| 2951 | + } | |
| 2921 | 2952 | } |
| 2922 | 2953 | } |
| 2923 | - if( fileedit_is_editable(zName) ){ | |
| 2954 | + if( !docOnly && fileedit_is_editable(zName) ){ | |
| 2924 | 2955 | style_submenu_element("Edit", |
| 2925 | 2956 | "%R/fileedit?filename=%T&checkin=%!S", |
| 2926 | 2957 | zName, zCI); |
| 2927 | 2958 | } |
| 2928 | 2959 | } |
| @@ -2930,11 +2961,13 @@ | ||
| 2930 | 2961 | style_submenu_element("Parsed", "%R/info/%s", zUuid); |
| 2931 | 2962 | } |
| 2932 | 2963 | if( descOnly ){ |
| 2933 | 2964 | style_submenu_element("Content", "%R/artifact/%s", zUuid); |
| 2934 | 2965 | }else{ |
| 2935 | - @ <hr> | |
| 2966 | + if( !docOnly || !isFile ){ | |
| 2967 | + @ <hr> | |
| 2968 | + } | |
| 2936 | 2969 | content_get(rid, &content); |
| 2937 | 2970 | if( renderAsWiki ){ |
| 2938 | 2971 | safe_html_context(DOCSRC_FILE); |
| 2939 | 2972 | wiki_render_by_mimetype(&content, zMime); |
| 2940 | 2973 | document_emit_js(); |
| @@ -3600,10 +3633,11 @@ | ||
| 3600 | 3633 | zNewColorFlag = P("newclr") ? " checked" : ""; |
| 3601 | 3634 | zNewTagFlag = P("newtag") ? " checked" : ""; |
| 3602 | 3635 | zNewTag = PDT("tagname",""); |
| 3603 | 3636 | zNewBrFlag = P("newbr") ? " checked" : ""; |
| 3604 | 3637 | zNewBranch = PDT("brname",""); |
| 3638 | + zBranchName = branch_of_rid(rid); | |
| 3605 | 3639 | zCloseFlag = P("close") ? " checked" : ""; |
| 3606 | 3640 | zHideFlag = P("hide") ? " checked" : ""; |
| 3607 | 3641 | if( P("apply") && cgi_csrf_safe(2) ){ |
| 3608 | 3642 | Blob ctrl; |
| 3609 | 3643 | char *zNow; |
| @@ -3647,17 +3681,25 @@ | ||
| 3647 | 3681 | zUuid[10] = 0; |
| 3648 | 3682 | style_header("Edit Check-in [%s]", zUuid); |
| 3649 | 3683 | if( P("preview") ){ |
| 3650 | 3684 | Blob suffix; |
| 3651 | 3685 | int nTag = 0; |
| 3686 | + const char *zDplyBr; /* Branch name used to determine BG color */ | |
| 3687 | + if( zNewBrFlag[0] && zNewBranch[0] ){ | |
| 3688 | + zDplyBr = zNewBranch; | |
| 3689 | + }else{ | |
| 3690 | + zDplyBr = zBranchName; | |
| 3691 | + } | |
| 3652 | 3692 | @ <b>Preview:</b> |
| 3653 | 3693 | @ <blockquote> |
| 3654 | 3694 | @ <table border=0> |
| 3655 | 3695 | if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){ |
| 3656 | - @ <tr><td style="background-color: %h(zNewColor);"> | |
| 3696 | + @ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));"> | |
| 3657 | 3697 | }else if( zColor[0] ){ |
| 3658 | - @ <tr><td style="background-color: %h(zColor);"> | |
| 3698 | + @ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));"> | |
| 3699 | + }else if( zDplyBr && fossil_strcmp(zDplyBr,"trunk")!=0 ){ | |
| 3700 | + @ <tr><td style="background-color:%h(hash_color(zDplyBr));"> | |
| 3659 | 3701 | }else{ |
| 3660 | 3702 | @ <tr><td> |
| 3661 | 3703 | } |
| 3662 | 3704 | @ %!W(blob_str(&comment)) |
| 3663 | 3705 | blob_zero(&suffix); |
| @@ -3738,13 +3780,10 @@ | ||
| 3738 | 3780 | @ <tr><th align="right" valign="top">Tags:</th> |
| 3739 | 3781 | @ <td valign="top"> |
| 3740 | 3782 | @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)> |
| 3741 | 3783 | @ Add the following new tag name to this check-in:</label> |
| 3742 | 3784 | @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)"> |
| 3743 | - zBranchName = db_text(0, "SELECT value FROM tagxref, tag" | |
| 3744 | - " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" | |
| 3745 | - " AND tagxref.tagid=%d", rid, TAG_BRANCH); | |
| 3746 | 3785 | db_prepare(&q, |
| 3747 | 3786 | "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag" |
| 3748 | 3787 | " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" |
| 3749 | 3788 | " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" |
| 3750 | 3789 | " ELSE tagname END /*sort*/", |
| @@ -3937,11 +3976,11 @@ | ||
| 3937 | 3976 | Blob comment; |
| 3938 | 3977 | char *zNow; |
| 3939 | 3978 | int nTags, nCancels; |
| 3940 | 3979 | int i; |
| 3941 | 3980 | Stmt q; |
| 3942 | - int ckComFlgs; /* Flags passed to suspicious_comment() */ | |
| 3981 | + int ckComFlgs; /* Flags passed to verify_comment() */ | |
| 3943 | 3982 | |
| 3944 | 3983 | |
| 3945 | 3984 | fEditComment = find_option("edit-comment","e",0)!=0; |
| 3946 | 3985 | zNewComment = find_option("comment","m",1); |
| 3947 | 3986 | zComFile = find_option("message-file","M",1); |
| @@ -4030,18 +4069,13 @@ | ||
| 4030 | 4069 | }else{ |
| 4031 | 4070 | const char *zVerComs = db_get("verify-comments","on"); |
| 4032 | 4071 | if( is_false(zVerComs) ){ |
| 4033 | 4072 | ckComFlgs = 0; |
| 4034 | 4073 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 4035 | - ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP; | |
| 4036 | - }else if( strcmp(zVerComs,"links")==0 ){ | |
| 4037 | - ckComFlgs = COMCK_LINKS; | |
| 4074 | + ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP; | |
| 4038 | 4075 | }else{ |
| 4039 | - ckComFlgs = COMCK_LINKS | COMCK_MARKUP; | |
| 4040 | - } | |
| 4041 | - if( zNewComment || zComFile ){ | |
| 4042 | - ckComFlgs = (ckComFlgs & COMCK_LINKS) | COMCK_NOPREVIEW; | |
| 4076 | + ckComFlgs = COMCK_MARKUP; | |
| 4043 | 4077 | } |
| 4044 | 4078 | } |
| 4045 | 4079 | if( fEditComment ){ |
| 4046 | 4080 | prepare_amend_comment(&comment, zComment, zUuid); |
| 4047 | 4081 | }else if( zComFile ){ |
| @@ -4052,29 +4086,29 @@ | ||
| 4052 | 4086 | } |
| 4053 | 4087 | if( blob_size(&comment)>0 |
| 4054 | 4088 | && comment_compare(zComment, blob_str(&comment))==0 |
| 4055 | 4089 | ){ |
| 4056 | 4090 | int rc; |
| 4057 | - while( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){ | |
| 4091 | + while( (rc = verify_comment(&comment, ckComFlgs))!=0 ){ | |
| 4058 | 4092 | char cReply; |
| 4059 | 4093 | Blob ans; |
| 4060 | 4094 | if( !fEditComment ){ |
| 4061 | 4095 | fossil_fatal("Amend aborted; " |
| 4062 | 4096 | "use --no-verify-comment to override"); |
| 4063 | 4097 | } |
| 4064 | 4098 | if( rc==COMCK_PREVIEW ){ |
| 4065 | - prompt_user("\nContinue (Y/n/e=edit)? ", &ans); | |
| 4099 | + prompt_user("Continue, abort, or edit (C/a/e)? ", &ans); | |
| 4066 | 4100 | }else{ |
| 4067 | - prompt_user("\nContinue (y/n/E=edit)? ", &ans); | |
| 4101 | + prompt_user("Edit, abort, or continue (E/a/c)? ", &ans); | |
| 4068 | 4102 | } |
| 4069 | 4103 | cReply = blob_str(&ans)[0]; |
| 4070 | 4104 | cReply = fossil_tolower(cReply); |
| 4071 | 4105 | blob_reset(&ans); |
| 4072 | - if( cReply=='n' ){ | |
| 4106 | + if( cReply=='a' ){ | |
| 4073 | 4107 | fossil_fatal("Amend aborted."); |
| 4074 | 4108 | } |
| 4075 | - if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){ | |
| 4109 | + if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){ | |
| 4076 | 4110 | char *zPrior = blob_materialize(&comment); |
| 4077 | 4111 | blob_init(&comment, 0, 0); |
| 4078 | 4112 | prepare_amend_comment(&comment, zPrior, zUuid); |
| 4079 | 4113 | fossil_free(zPrior); |
| 4080 | 4114 | continue; |
| 4081 | 4115 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -2622,31 +2622,36 @@ | |
| 2622 | |
| 2623 | /* |
| 2624 | ** WEBPAGE: artifact |
| 2625 | ** WEBPAGE: file |
| 2626 | ** WEBPAGE: whatis |
| 2627 | ** |
| 2628 | ** Typical usage: |
| 2629 | ** |
| 2630 | ** /artifact/HASH |
| 2631 | ** /whatis/HASH |
| 2632 | ** /file/NAME |
| 2633 | ** |
| 2634 | ** Additional query parameters: |
| 2635 | ** |
| 2636 | ** ln - show line numbers |
| 2637 | ** ln=N - highlight line number N |
| 2638 | ** ln=M-N - highlight lines M through N inclusive |
| 2639 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2640 | ** verbose - show more detail in the description |
| 2641 | ** download - redirect to the download (artifact page only) |
| 2642 | ** name=NAME - filename or hash as a query parameter |
| 2643 | ** filename=NAME - alternative spelling for "name=" |
| 2644 | ** fn=NAME - alternative spelling for "name=" |
| 2645 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2646 | ** identify the file. |
| 2647 | ** txt - Force display of unformatted source text |
| 2648 | ** |
| 2649 | ** The /artifact page show the complete content of a file |
| 2650 | ** identified by HASH. The /whatis page shows only a description |
| 2651 | ** of how the artifact is used. The /file page shows the most recent |
| 2652 | ** version of the file or directory called NAME, or a list of the |
| @@ -2665,18 +2670,21 @@ | |
| 2665 | void artifact_page(void){ |
| 2666 | int rid = 0; |
| 2667 | Blob content; |
| 2668 | const char *zMime; |
| 2669 | Blob downloadName; |
| 2670 | int renderAsWiki = 0; |
| 2671 | int renderAsHtml = 0; |
| 2672 | int renderAsSvg = 0; |
| 2673 | int objType; |
| 2674 | int asText; |
| 2675 | const char *zUuid = 0; |
| 2676 | u32 objdescFlags = OBJDESC_BASE; |
| 2677 | int descOnly = fossil_strcmp(g.zPath,"whatis")==0; |
| 2678 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2679 | const char *zLn = P("ln"); |
| 2680 | const char *zName = P("name"); |
| 2681 | const char *zCI = P("ci"); |
| 2682 | HQuery url; |
| @@ -2687,10 +2695,14 @@ | |
| 2687 | |
| 2688 | login_check_credentials(); |
| 2689 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2690 | cgi_check_for_malice(); |
| 2691 | style_set_current_feature("artifact"); |
| 2692 | |
| 2693 | /* Capture and normalize the name= and ci= query parameters */ |
| 2694 | if( zName==0 ){ |
| 2695 | zName = P("filename"); |
| 2696 | if( zName==0 ){ |
| @@ -2789,14 +2801,23 @@ | |
| 2789 | url_add_parameter(&url, "verbose", "1"); |
| 2790 | objdescFlags |= OBJDESC_DETAIL; |
| 2791 | } |
| 2792 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2793 | etag_check(ETAG_HASH, zUuid); |
| 2794 | |
| 2795 | asText = P("txt")!=0; |
| 2796 | if( isFile ){ |
| 2797 | if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2798 | zCI = "tip"; |
| 2799 | @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a> |
| 2800 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2801 | }else{ |
| 2802 | const char *zPath; |
| @@ -2814,17 +2835,19 @@ | |
| 2814 | }else{ |
| 2815 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2> |
| 2816 | } |
| 2817 | blob_reset(&path); |
| 2818 | } |
| 2819 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2820 | zMime = mimetype_from_name(zName); |
| 2821 | style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", |
| 2822 | zName, zCI); |
| 2823 | style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", |
| 2824 | zName, zCI); |
| 2825 | style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName); |
| 2826 | blob_init(&downloadName, zName, -1); |
| 2827 | objType = OBJTYPE_CONTENT; |
| 2828 | }else{ |
| 2829 | @ <h2>Artifact |
| 2830 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| @@ -2843,11 +2866,11 @@ | |
| 2843 | cgi_redirectf("%R/raw/%s?at=%T", |
| 2844 | db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid), |
| 2845 | file_tail(blob_str(&downloadName))); |
| 2846 | /*NOTREACHED*/ |
| 2847 | } |
| 2848 | if( g.perm.Admin ){ |
| 2849 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2850 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| 2851 | style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid); |
| 2852 | }else{ |
| 2853 | style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid); |
| @@ -2888,41 +2911,49 @@ | |
| 2888 | const char *zIp = db_column_text(&q,2); |
| 2889 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2890 | } |
| 2891 | db_finalize(&q); |
| 2892 | } |
| 2893 | style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName)); |
| 2894 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2895 | style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); |
| 2896 | } |
| 2897 | if( zMime ){ |
| 2898 | if( fossil_strcmp(zMime, "text/html")==0 ){ |
| 2899 | if( asText ){ |
| 2900 | style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2901 | }else{ |
| 2902 | renderAsHtml = 1; |
| 2903 | style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); |
| 2904 | } |
| 2905 | }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 |
| 2906 | || fossil_strcmp(zMime, "text/x-markdown")==0 |
| 2907 | || fossil_strcmp(zMime, "text/x-pikchr")==0 ){ |
| 2908 | if( asText ){ |
| 2909 | style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki", |
| 2910 | "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2911 | }else{ |
| 2912 | renderAsWiki = 1; |
| 2913 | style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); |
| 2914 | } |
| 2915 | }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){ |
| 2916 | if( asText ){ |
| 2917 | style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2918 | }else{ |
| 2919 | renderAsSvg = 1; |
| 2920 | style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0)); |
| 2921 | } |
| 2922 | } |
| 2923 | if( fileedit_is_editable(zName) ){ |
| 2924 | style_submenu_element("Edit", |
| 2925 | "%R/fileedit?filename=%T&checkin=%!S", |
| 2926 | zName, zCI); |
| 2927 | } |
| 2928 | } |
| @@ -2930,11 +2961,13 @@ | |
| 2930 | style_submenu_element("Parsed", "%R/info/%s", zUuid); |
| 2931 | } |
| 2932 | if( descOnly ){ |
| 2933 | style_submenu_element("Content", "%R/artifact/%s", zUuid); |
| 2934 | }else{ |
| 2935 | @ <hr> |
| 2936 | content_get(rid, &content); |
| 2937 | if( renderAsWiki ){ |
| 2938 | safe_html_context(DOCSRC_FILE); |
| 2939 | wiki_render_by_mimetype(&content, zMime); |
| 2940 | document_emit_js(); |
| @@ -3600,10 +3633,11 @@ | |
| 3600 | zNewColorFlag = P("newclr") ? " checked" : ""; |
| 3601 | zNewTagFlag = P("newtag") ? " checked" : ""; |
| 3602 | zNewTag = PDT("tagname",""); |
| 3603 | zNewBrFlag = P("newbr") ? " checked" : ""; |
| 3604 | zNewBranch = PDT("brname",""); |
| 3605 | zCloseFlag = P("close") ? " checked" : ""; |
| 3606 | zHideFlag = P("hide") ? " checked" : ""; |
| 3607 | if( P("apply") && cgi_csrf_safe(2) ){ |
| 3608 | Blob ctrl; |
| 3609 | char *zNow; |
| @@ -3647,17 +3681,25 @@ | |
| 3647 | zUuid[10] = 0; |
| 3648 | style_header("Edit Check-in [%s]", zUuid); |
| 3649 | if( P("preview") ){ |
| 3650 | Blob suffix; |
| 3651 | int nTag = 0; |
| 3652 | @ <b>Preview:</b> |
| 3653 | @ <blockquote> |
| 3654 | @ <table border=0> |
| 3655 | if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){ |
| 3656 | @ <tr><td style="background-color: %h(zNewColor);"> |
| 3657 | }else if( zColor[0] ){ |
| 3658 | @ <tr><td style="background-color: %h(zColor);"> |
| 3659 | }else{ |
| 3660 | @ <tr><td> |
| 3661 | } |
| 3662 | @ %!W(blob_str(&comment)) |
| 3663 | blob_zero(&suffix); |
| @@ -3738,13 +3780,10 @@ | |
| 3738 | @ <tr><th align="right" valign="top">Tags:</th> |
| 3739 | @ <td valign="top"> |
| 3740 | @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)> |
| 3741 | @ Add the following new tag name to this check-in:</label> |
| 3742 | @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)"> |
| 3743 | zBranchName = db_text(0, "SELECT value FROM tagxref, tag" |
| 3744 | " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" |
| 3745 | " AND tagxref.tagid=%d", rid, TAG_BRANCH); |
| 3746 | db_prepare(&q, |
| 3747 | "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag" |
| 3748 | " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" |
| 3749 | " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" |
| 3750 | " ELSE tagname END /*sort*/", |
| @@ -3937,11 +3976,11 @@ | |
| 3937 | Blob comment; |
| 3938 | char *zNow; |
| 3939 | int nTags, nCancels; |
| 3940 | int i; |
| 3941 | Stmt q; |
| 3942 | int ckComFlgs; /* Flags passed to suspicious_comment() */ |
| 3943 | |
| 3944 | |
| 3945 | fEditComment = find_option("edit-comment","e",0)!=0; |
| 3946 | zNewComment = find_option("comment","m",1); |
| 3947 | zComFile = find_option("message-file","M",1); |
| @@ -4030,18 +4069,13 @@ | |
| 4030 | }else{ |
| 4031 | const char *zVerComs = db_get("verify-comments","on"); |
| 4032 | if( is_false(zVerComs) ){ |
| 4033 | ckComFlgs = 0; |
| 4034 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 4035 | ckComFlgs = COMCK_PREVIEW | COMCK_LINKS | COMCK_MARKUP; |
| 4036 | }else if( strcmp(zVerComs,"links")==0 ){ |
| 4037 | ckComFlgs = COMCK_LINKS; |
| 4038 | }else{ |
| 4039 | ckComFlgs = COMCK_LINKS | COMCK_MARKUP; |
| 4040 | } |
| 4041 | if( zNewComment || zComFile ){ |
| 4042 | ckComFlgs = (ckComFlgs & COMCK_LINKS) | COMCK_NOPREVIEW; |
| 4043 | } |
| 4044 | } |
| 4045 | if( fEditComment ){ |
| 4046 | prepare_amend_comment(&comment, zComment, zUuid); |
| 4047 | }else if( zComFile ){ |
| @@ -4052,29 +4086,29 @@ | |
| 4052 | } |
| 4053 | if( blob_size(&comment)>0 |
| 4054 | && comment_compare(zComment, blob_str(&comment))==0 |
| 4055 | ){ |
| 4056 | int rc; |
| 4057 | while( (rc = suspicious_comment(&comment, ckComFlgs))!=0 ){ |
| 4058 | char cReply; |
| 4059 | Blob ans; |
| 4060 | if( !fEditComment ){ |
| 4061 | fossil_fatal("Amend aborted; " |
| 4062 | "use --no-verify-comment to override"); |
| 4063 | } |
| 4064 | if( rc==COMCK_PREVIEW ){ |
| 4065 | prompt_user("\nContinue (Y/n/e=edit)? ", &ans); |
| 4066 | }else{ |
| 4067 | prompt_user("\nContinue (y/n/E=edit)? ", &ans); |
| 4068 | } |
| 4069 | cReply = blob_str(&ans)[0]; |
| 4070 | cReply = fossil_tolower(cReply); |
| 4071 | blob_reset(&ans); |
| 4072 | if( cReply=='n' ){ |
| 4073 | fossil_fatal("Amend aborted."); |
| 4074 | } |
| 4075 | if( cReply=='e' || (cReply!='y' && rc!=COMCK_PREVIEW) ){ |
| 4076 | char *zPrior = blob_materialize(&comment); |
| 4077 | blob_init(&comment, 0, 0); |
| 4078 | prepare_amend_comment(&comment, zPrior, zUuid); |
| 4079 | fossil_free(zPrior); |
| 4080 | continue; |
| 4081 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -2622,31 +2622,36 @@ | |
| 2622 | |
| 2623 | /* |
| 2624 | ** WEBPAGE: artifact |
| 2625 | ** WEBPAGE: file |
| 2626 | ** WEBPAGE: whatis |
| 2627 | ** WEBPAGE: docfile |
| 2628 | ** |
| 2629 | ** Typical usage: |
| 2630 | ** |
| 2631 | ** /artifact/HASH |
| 2632 | ** /whatis/HASH |
| 2633 | ** /file/NAME |
| 2634 | ** /docfile/NAME |
| 2635 | ** |
| 2636 | ** Additional query parameters: |
| 2637 | ** |
| 2638 | ** ln - show line numbers |
| 2639 | ** ln=N - highlight line number N |
| 2640 | ** ln=M-N - highlight lines M through N inclusive |
| 2641 | ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) |
| 2642 | ** verbose - show more detail in the description |
| 2643 | ** brief - show just the document, not the metadata. The |
| 2644 | ** /docfile page is an alias for /file?brief |
| 2645 | ** download - redirect to the download (artifact page only) |
| 2646 | ** name=NAME - filename or hash as a query parameter |
| 2647 | ** filename=NAME - alternative spelling for "name=" |
| 2648 | ** fn=NAME - alternative spelling for "name=" |
| 2649 | ** ci=VERSION - The specific check-in to use with "name=" to |
| 2650 | ** identify the file. |
| 2651 | ** txt - Force display of unformatted source text |
| 2652 | ** hash - Output only the hash of the artifact |
| 2653 | ** |
| 2654 | ** The /artifact page show the complete content of a file |
| 2655 | ** identified by HASH. The /whatis page shows only a description |
| 2656 | ** of how the artifact is used. The /file page shows the most recent |
| 2657 | ** version of the file or directory called NAME, or a list of the |
| @@ -2665,18 +2670,21 @@ | |
| 2670 | void artifact_page(void){ |
| 2671 | int rid = 0; |
| 2672 | Blob content; |
| 2673 | const char *zMime; |
| 2674 | Blob downloadName; |
| 2675 | Blob uuid; |
| 2676 | int renderAsWiki = 0; |
| 2677 | int renderAsHtml = 0; |
| 2678 | int renderAsSvg = 0; |
| 2679 | int objType; |
| 2680 | int asText; |
| 2681 | const char *zUuid = 0; |
| 2682 | u32 objdescFlags = OBJDESC_BASE; |
| 2683 | int descOnly = fossil_strcmp(g.zPath,"whatis")==0; |
| 2684 | int hashOnly = P("hash")!=0; |
| 2685 | int docOnly = P("brief")!=0; |
| 2686 | int isFile = fossil_strcmp(g.zPath,"file")==0; |
| 2687 | const char *zLn = P("ln"); |
| 2688 | const char *zName = P("name"); |
| 2689 | const char *zCI = P("ci"); |
| 2690 | HQuery url; |
| @@ -2687,10 +2695,14 @@ | |
| 2695 | |
| 2696 | login_check_credentials(); |
| 2697 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 2698 | cgi_check_for_malice(); |
| 2699 | style_set_current_feature("artifact"); |
| 2700 | if( fossil_strcmp(g.zPath, "docfile")==0 ){ |
| 2701 | isFile = 1; |
| 2702 | docOnly = 1; |
| 2703 | } |
| 2704 | |
| 2705 | /* Capture and normalize the name= and ci= query parameters */ |
| 2706 | if( zName==0 ){ |
| 2707 | zName = P("filename"); |
| 2708 | if( zName==0 ){ |
| @@ -2789,14 +2801,23 @@ | |
| 2801 | url_add_parameter(&url, "verbose", "1"); |
| 2802 | objdescFlags |= OBJDESC_DETAIL; |
| 2803 | } |
| 2804 | zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2805 | etag_check(ETAG_HASH, zUuid); |
| 2806 | |
| 2807 | if( descOnly && hashOnly ){ |
| 2808 | blob_set(&uuid, zUuid); |
| 2809 | cgi_set_content_type("text/plain"); |
| 2810 | cgi_set_content(&uuid); |
| 2811 | return; |
| 2812 | } |
| 2813 | |
| 2814 | asText = P("txt")!=0; |
| 2815 | if( isFile ){ |
| 2816 | if( docOnly ){ |
| 2817 | /* No header */ |
| 2818 | }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){ |
| 2819 | zCI = "tip"; |
| 2820 | @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a> |
| 2821 | @ from the %z(href("%R/info/tip"))latest check-in</a></h2> |
| 2822 | }else{ |
| 2823 | const char *zPath; |
| @@ -2814,17 +2835,19 @@ | |
| 2835 | }else{ |
| 2836 | @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2> |
| 2837 | } |
| 2838 | blob_reset(&path); |
| 2839 | } |
| 2840 | zMime = mimetype_from_name(zName); |
| 2841 | if( !docOnly ){ |
| 2842 | style_submenu_element("Artifact", "%R/artifact/%S", zUuid); |
| 2843 | style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T", |
| 2844 | zName, zCI); |
| 2845 | style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T", |
| 2846 | zName, zCI); |
| 2847 | style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName); |
| 2848 | } |
| 2849 | blob_init(&downloadName, zName, -1); |
| 2850 | objType = OBJTYPE_CONTENT; |
| 2851 | }else{ |
| 2852 | @ <h2>Artifact |
| 2853 | style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid); |
| @@ -2843,11 +2866,11 @@ | |
| 2866 | cgi_redirectf("%R/raw/%s?at=%T", |
| 2867 | db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid), |
| 2868 | file_tail(blob_str(&downloadName))); |
| 2869 | /*NOTREACHED*/ |
| 2870 | } |
| 2871 | if( g.perm.Admin && !docOnly ){ |
| 2872 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2873 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| 2874 | style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid); |
| 2875 | }else{ |
| 2876 | style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid); |
| @@ -2888,41 +2911,49 @@ | |
| 2911 | const char *zIp = db_column_text(&q,2); |
| 2912 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2913 | } |
| 2914 | db_finalize(&q); |
| 2915 | } |
| 2916 | if( !docOnly ){ |
| 2917 | style_submenu_element("Download", "%R/raw/%s?at=%T",zUuid,file_tail(zName)); |
| 2918 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2919 | style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid); |
| 2920 | } |
| 2921 | } |
| 2922 | if( zMime ){ |
| 2923 | if( fossil_strcmp(zMime, "text/html")==0 ){ |
| 2924 | if( asText ){ |
| 2925 | style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2926 | }else{ |
| 2927 | renderAsHtml = 1; |
| 2928 | if( !docOnly ){ |
| 2929 | style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); |
| 2930 | } |
| 2931 | } |
| 2932 | }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 |
| 2933 | || fossil_strcmp(zMime, "text/x-markdown")==0 |
| 2934 | || fossil_strcmp(zMime, "text/x-pikchr")==0 ){ |
| 2935 | if( asText ){ |
| 2936 | style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki", |
| 2937 | "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2938 | }else{ |
| 2939 | renderAsWiki = 1; |
| 2940 | if( !docOnly ){ |
| 2941 | style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); |
| 2942 | } |
| 2943 | } |
| 2944 | }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){ |
| 2945 | if( asText ){ |
| 2946 | style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0)); |
| 2947 | }else{ |
| 2948 | renderAsSvg = 1; |
| 2949 | if( !docOnly ){ |
| 2950 | style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0)); |
| 2951 | } |
| 2952 | } |
| 2953 | } |
| 2954 | if( !docOnly && fileedit_is_editable(zName) ){ |
| 2955 | style_submenu_element("Edit", |
| 2956 | "%R/fileedit?filename=%T&checkin=%!S", |
| 2957 | zName, zCI); |
| 2958 | } |
| 2959 | } |
| @@ -2930,11 +2961,13 @@ | |
| 2961 | style_submenu_element("Parsed", "%R/info/%s", zUuid); |
| 2962 | } |
| 2963 | if( descOnly ){ |
| 2964 | style_submenu_element("Content", "%R/artifact/%s", zUuid); |
| 2965 | }else{ |
| 2966 | if( !docOnly || !isFile ){ |
| 2967 | @ <hr> |
| 2968 | } |
| 2969 | content_get(rid, &content); |
| 2970 | if( renderAsWiki ){ |
| 2971 | safe_html_context(DOCSRC_FILE); |
| 2972 | wiki_render_by_mimetype(&content, zMime); |
| 2973 | document_emit_js(); |
| @@ -3600,10 +3633,11 @@ | |
| 3633 | zNewColorFlag = P("newclr") ? " checked" : ""; |
| 3634 | zNewTagFlag = P("newtag") ? " checked" : ""; |
| 3635 | zNewTag = PDT("tagname",""); |
| 3636 | zNewBrFlag = P("newbr") ? " checked" : ""; |
| 3637 | zNewBranch = PDT("brname",""); |
| 3638 | zBranchName = branch_of_rid(rid); |
| 3639 | zCloseFlag = P("close") ? " checked" : ""; |
| 3640 | zHideFlag = P("hide") ? " checked" : ""; |
| 3641 | if( P("apply") && cgi_csrf_safe(2) ){ |
| 3642 | Blob ctrl; |
| 3643 | char *zNow; |
| @@ -3647,17 +3681,25 @@ | |
| 3681 | zUuid[10] = 0; |
| 3682 | style_header("Edit Check-in [%s]", zUuid); |
| 3683 | if( P("preview") ){ |
| 3684 | Blob suffix; |
| 3685 | int nTag = 0; |
| 3686 | const char *zDplyBr; /* Branch name used to determine BG color */ |
| 3687 | if( zNewBrFlag[0] && zNewBranch[0] ){ |
| 3688 | zDplyBr = zNewBranch; |
| 3689 | }else{ |
| 3690 | zDplyBr = zBranchName; |
| 3691 | } |
| 3692 | @ <b>Preview:</b> |
| 3693 | @ <blockquote> |
| 3694 | @ <table border=0> |
| 3695 | if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){ |
| 3696 | @ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));"> |
| 3697 | }else if( zColor[0] ){ |
| 3698 | @ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));"> |
| 3699 | }else if( zDplyBr && fossil_strcmp(zDplyBr,"trunk")!=0 ){ |
| 3700 | @ <tr><td style="background-color:%h(hash_color(zDplyBr));"> |
| 3701 | }else{ |
| 3702 | @ <tr><td> |
| 3703 | } |
| 3704 | @ %!W(blob_str(&comment)) |
| 3705 | blob_zero(&suffix); |
| @@ -3738,13 +3780,10 @@ | |
| 3780 | @ <tr><th align="right" valign="top">Tags:</th> |
| 3781 | @ <td valign="top"> |
| 3782 | @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)> |
| 3783 | @ Add the following new tag name to this check-in:</label> |
| 3784 | @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)"> |
| 3785 | db_prepare(&q, |
| 3786 | "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag" |
| 3787 | " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" |
| 3788 | " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" |
| 3789 | " ELSE tagname END /*sort*/", |
| @@ -3937,11 +3976,11 @@ | |
| 3976 | Blob comment; |
| 3977 | char *zNow; |
| 3978 | int nTags, nCancels; |
| 3979 | int i; |
| 3980 | Stmt q; |
| 3981 | int ckComFlgs; /* Flags passed to verify_comment() */ |
| 3982 | |
| 3983 | |
| 3984 | fEditComment = find_option("edit-comment","e",0)!=0; |
| 3985 | zNewComment = find_option("comment","m",1); |
| 3986 | zComFile = find_option("message-file","M",1); |
| @@ -4030,18 +4069,13 @@ | |
| 4069 | }else{ |
| 4070 | const char *zVerComs = db_get("verify-comments","on"); |
| 4071 | if( is_false(zVerComs) ){ |
| 4072 | ckComFlgs = 0; |
| 4073 | }else if( strcmp(zVerComs,"preview")==0 ){ |
| 4074 | ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP; |
| 4075 | }else{ |
| 4076 | ckComFlgs = COMCK_MARKUP; |
| 4077 | } |
| 4078 | } |
| 4079 | if( fEditComment ){ |
| 4080 | prepare_amend_comment(&comment, zComment, zUuid); |
| 4081 | }else if( zComFile ){ |
| @@ -4052,29 +4086,29 @@ | |
| 4086 | } |
| 4087 | if( blob_size(&comment)>0 |
| 4088 | && comment_compare(zComment, blob_str(&comment))==0 |
| 4089 | ){ |
| 4090 | int rc; |
| 4091 | while( (rc = verify_comment(&comment, ckComFlgs))!=0 ){ |
| 4092 | char cReply; |
| 4093 | Blob ans; |
| 4094 | if( !fEditComment ){ |
| 4095 | fossil_fatal("Amend aborted; " |
| 4096 | "use --no-verify-comment to override"); |
| 4097 | } |
| 4098 | if( rc==COMCK_PREVIEW ){ |
| 4099 | prompt_user("Continue, abort, or edit (C/a/e)? ", &ans); |
| 4100 | }else{ |
| 4101 | prompt_user("Edit, abort, or continue (E/a/c)? ", &ans); |
| 4102 | } |
| 4103 | cReply = blob_str(&ans)[0]; |
| 4104 | cReply = fossil_tolower(cReply); |
| 4105 | blob_reset(&ans); |
| 4106 | if( cReply=='a' ){ |
| 4107 | fossil_fatal("Amend aborted."); |
| 4108 | } |
| 4109 | if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){ |
| 4110 | char *zPrior = blob_materialize(&comment); |
| 4111 | blob_init(&comment, 0, 0); |
| 4112 | prepare_amend_comment(&comment, zPrior, zUuid); |
| 4113 | fossil_free(zPrior); |
| 4114 | continue; |
| 4115 |
-1
| --- src/json_config.c | ||
| +++ src/json_config.c | ||
| @@ -86,11 +86,10 @@ | ||
| 86 | 86 | { "logo-image", CONFIGSET_SKIN }, |
| 87 | 87 | { "background-mimetype", CONFIGSET_SKIN }, |
| 88 | 88 | { "background-image", CONFIGSET_SKIN }, |
| 89 | 89 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 90 | 90 | { "icon-image", CONFIGSET_SKIN }, |
| 91 | -{ "timeline-block-markup", CONFIGSET_SKIN }, | |
| 92 | 91 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 93 | 92 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 94 | 93 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 95 | 94 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 96 | 95 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 97 | 96 |
| --- src/json_config.c | |
| +++ src/json_config.c | |
| @@ -86,11 +86,10 @@ | |
| 86 | { "logo-image", CONFIGSET_SKIN }, |
| 87 | { "background-mimetype", CONFIGSET_SKIN }, |
| 88 | { "background-image", CONFIGSET_SKIN }, |
| 89 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 90 | { "icon-image", CONFIGSET_SKIN }, |
| 91 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 92 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 93 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 94 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 95 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 96 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 97 |
| --- src/json_config.c | |
| +++ src/json_config.c | |
| @@ -86,11 +86,10 @@ | |
| 86 | { "logo-image", CONFIGSET_SKIN }, |
| 87 | { "background-mimetype", CONFIGSET_SKIN }, |
| 88 | { "background-image", CONFIGSET_SKIN }, |
| 89 | { "icon-mimetype", CONFIGSET_SKIN }, |
| 90 | { "icon-image", CONFIGSET_SKIN }, |
| 91 | { "timeline-date-format", CONFIGSET_SKIN }, |
| 92 | { "timeline-default-style", CONFIGSET_SKIN }, |
| 93 | { "timeline-dwelltime", CONFIGSET_SKIN }, |
| 94 | { "timeline-closetime", CONFIGSET_SKIN }, |
| 95 | { "timeline-hard-newlines", CONFIGSET_SKIN }, |
| 96 |
+28
-5
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -2542,12 +2542,16 @@ | ||
| 2542 | 2542 | ** setenv: NAME |
| 2543 | 2543 | ** |
| 2544 | 2544 | ** Sets environment variable NAME to VALUE. If VALUE is omitted, then |
| 2545 | 2545 | ** the environment variable is unset. |
| 2546 | 2546 | */ |
| 2547 | - blob_token(&line,&value2); | |
| 2548 | - fossil_setenv(blob_str(&value), blob_str(&value2)); | |
| 2547 | + char *zValue; | |
| 2548 | + blob_tail(&line,&value2); | |
| 2549 | + blob_trim(&value2); | |
| 2550 | + zValue = blob_str(&value2); | |
| 2551 | + while( fossil_isspace(zValue[0]) ){ zValue++; } | |
| 2552 | + fossil_setenv(blob_str(&value), zValue); | |
| 2549 | 2553 | blob_reset(&value); |
| 2550 | 2554 | blob_reset(&value2); |
| 2551 | 2555 | continue; |
| 2552 | 2556 | } |
| 2553 | 2557 | if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ |
| @@ -3200,10 +3204,13 @@ | ||
| 3200 | 3204 | ** --chroot DIR Use directory for chroot instead of repository path |
| 3201 | 3205 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3202 | 3206 | ** /doc/ckout/... |
| 3203 | 3207 | ** --create Create a new REPOSITORY if it does not already exist |
| 3204 | 3208 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3209 | +** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where | |
| 3210 | +** DIR is the directory holding FILE and TAIL is the | |
| 3211 | +** filename at the end of FILE. Only works for "ui". | |
| 3205 | 3212 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3206 | 3213 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3207 | 3214 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3208 | 3215 | ** system when REPOSITORY is remote. |
| 3209 | 3216 | ** --from PATH Use PATH as the diff baseline for the /ckout page |
| @@ -3278,10 +3285,11 @@ | ||
| 3278 | 3285 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3279 | 3286 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3280 | 3287 | const char *zJsMode; /* The --jsmode parameter */ |
| 3281 | 3288 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3282 | 3289 | const char *zFrom; /* Value for --from */ |
| 3290 | + const char *zExtPage = 0; /* Argument to --extpage */ | |
| 3283 | 3291 | |
| 3284 | 3292 | |
| 3285 | 3293 | #if USE_SEE |
| 3286 | 3294 | db_setup_for_saved_encryption_key(); |
| 3287 | 3295 | #endif |
| @@ -3319,12 +3327,20 @@ | ||
| 3319 | 3327 | zFrom = find_option("from", 0, 1); |
| 3320 | 3328 | if( zFrom && zFrom==file_tail(zFrom) ){ |
| 3321 | 3329 | fossil_fatal("the argument to --from must be a pathname for" |
| 3322 | 3330 | " the \"ui\" command"); |
| 3323 | 3331 | } |
| 3324 | - zInitPage = find_option("page", "p", 1); | |
| 3325 | - if( zInitPage && zInitPage[0]=='/' ) zInitPage++; | |
| 3332 | + zExtPage = find_option("extpage",0,1); | |
| 3333 | + if( zExtPage ){ | |
| 3334 | + char *zFullPath = file_canonical_name_dup(zExtPage); | |
| 3335 | + g.zExtRoot = file_dirname(zFullPath); | |
| 3336 | + zInitPage = mprintf("ext/%s",file_tail(zFullPath)); | |
| 3337 | + fossil_free(zFullPath); | |
| 3338 | + }else{ | |
| 3339 | + zInitPage = find_option("page", "p", 1); | |
| 3340 | + if( zInitPage && zInitPage[0]=='/' ) zInitPage++; | |
| 3341 | + } | |
| 3326 | 3342 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3327 | 3343 | if( zFrom && zInitPage==0 ){ |
| 3328 | 3344 | zInitPage = mprintf("ckout?exbase=%H", zFrom); |
| 3329 | 3345 | } |
| 3330 | 3346 | } |
| @@ -3493,11 +3509,18 @@ | ||
| 3493 | 3509 | } |
| 3494 | 3510 | blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort); |
| 3495 | 3511 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3496 | 3512 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3497 | 3513 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3498 | - if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot); | |
| 3514 | + if( zExtPage ){ | |
| 3515 | + if( !file_is_absolute_path(zExtPage) ){ | |
| 3516 | + zExtPage = mprintf("%s/%s", g.argv[2], zExtPage); | |
| 3517 | + } | |
| 3518 | + blob_appendf(&ssh, " --extpage %$", zExtPage); | |
| 3519 | + }else if( g.zExtRoot ){ | |
| 3520 | + blob_appendf(&ssh, " --extroot %$", g.zExtRoot); | |
| 3521 | + } | |
| 3499 | 3522 | if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use()); |
| 3500 | 3523 | if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode); |
| 3501 | 3524 | if( fCreate ) blob_appendf(&ssh, " --create"); |
| 3502 | 3525 | blob_appendf(&ssh, " %$", g.argv[2]); |
| 3503 | 3526 | if( isRetry ){ |
| 3504 | 3527 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2542,12 +2542,16 @@ | |
| 2542 | ** setenv: NAME |
| 2543 | ** |
| 2544 | ** Sets environment variable NAME to VALUE. If VALUE is omitted, then |
| 2545 | ** the environment variable is unset. |
| 2546 | */ |
| 2547 | blob_token(&line,&value2); |
| 2548 | fossil_setenv(blob_str(&value), blob_str(&value2)); |
| 2549 | blob_reset(&value); |
| 2550 | blob_reset(&value2); |
| 2551 | continue; |
| 2552 | } |
| 2553 | if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ |
| @@ -3200,10 +3204,13 @@ | |
| 3200 | ** --chroot DIR Use directory for chroot instead of repository path |
| 3201 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3202 | ** /doc/ckout/... |
| 3203 | ** --create Create a new REPOSITORY if it does not already exist |
| 3204 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3205 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3206 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3207 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3208 | ** system when REPOSITORY is remote. |
| 3209 | ** --from PATH Use PATH as the diff baseline for the /ckout page |
| @@ -3278,10 +3285,11 @@ | |
| 3278 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3279 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3280 | const char *zJsMode; /* The --jsmode parameter */ |
| 3281 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3282 | const char *zFrom; /* Value for --from */ |
| 3283 | |
| 3284 | |
| 3285 | #if USE_SEE |
| 3286 | db_setup_for_saved_encryption_key(); |
| 3287 | #endif |
| @@ -3319,12 +3327,20 @@ | |
| 3319 | zFrom = find_option("from", 0, 1); |
| 3320 | if( zFrom && zFrom==file_tail(zFrom) ){ |
| 3321 | fossil_fatal("the argument to --from must be a pathname for" |
| 3322 | " the \"ui\" command"); |
| 3323 | } |
| 3324 | zInitPage = find_option("page", "p", 1); |
| 3325 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3326 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3327 | if( zFrom && zInitPage==0 ){ |
| 3328 | zInitPage = mprintf("ckout?exbase=%H", zFrom); |
| 3329 | } |
| 3330 | } |
| @@ -3493,11 +3509,18 @@ | |
| 3493 | } |
| 3494 | blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort); |
| 3495 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3496 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3497 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3498 | if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot); |
| 3499 | if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use()); |
| 3500 | if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode); |
| 3501 | if( fCreate ) blob_appendf(&ssh, " --create"); |
| 3502 | blob_appendf(&ssh, " %$", g.argv[2]); |
| 3503 | if( isRetry ){ |
| 3504 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -2542,12 +2542,16 @@ | |
| 2542 | ** setenv: NAME |
| 2543 | ** |
| 2544 | ** Sets environment variable NAME to VALUE. If VALUE is omitted, then |
| 2545 | ** the environment variable is unset. |
| 2546 | */ |
| 2547 | char *zValue; |
| 2548 | blob_tail(&line,&value2); |
| 2549 | blob_trim(&value2); |
| 2550 | zValue = blob_str(&value2); |
| 2551 | while( fossil_isspace(zValue[0]) ){ zValue++; } |
| 2552 | fossil_setenv(blob_str(&value), zValue); |
| 2553 | blob_reset(&value); |
| 2554 | blob_reset(&value2); |
| 2555 | continue; |
| 2556 | } |
| 2557 | if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ |
| @@ -3200,10 +3204,13 @@ | |
| 3204 | ** --chroot DIR Use directory for chroot instead of repository path |
| 3205 | ** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were |
| 3206 | ** /doc/ckout/... |
| 3207 | ** --create Create a new REPOSITORY if it does not already exist |
| 3208 | ** --errorlog FILE Append HTTP error messages to FILE |
| 3209 | ** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where |
| 3210 | ** DIR is the directory holding FILE and TAIL is the |
| 3211 | ** filename at the end of FILE. Only works for "ui". |
| 3212 | ** --extroot DIR Document root for the /ext extension mechanism |
| 3213 | ** --files GLOBLIST Comma-separated list of glob patterns for static files |
| 3214 | ** --fossilcmd PATH The pathname of the "fossil" executable on the remote |
| 3215 | ** system when REPOSITORY is remote. |
| 3216 | ** --from PATH Use PATH as the diff baseline for the /ckout page |
| @@ -3278,10 +3285,11 @@ | |
| 3285 | int findServerArg = 2; /* argv index for find_server_repository() */ |
| 3286 | char *zRemote = 0; /* Remote host on which to run "fossil ui" */ |
| 3287 | const char *zJsMode; /* The --jsmode parameter */ |
| 3288 | const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ |
| 3289 | const char *zFrom; /* Value for --from */ |
| 3290 | const char *zExtPage = 0; /* Argument to --extpage */ |
| 3291 | |
| 3292 | |
| 3293 | #if USE_SEE |
| 3294 | db_setup_for_saved_encryption_key(); |
| 3295 | #endif |
| @@ -3319,12 +3327,20 @@ | |
| 3327 | zFrom = find_option("from", 0, 1); |
| 3328 | if( zFrom && zFrom==file_tail(zFrom) ){ |
| 3329 | fossil_fatal("the argument to --from must be a pathname for" |
| 3330 | " the \"ui\" command"); |
| 3331 | } |
| 3332 | zExtPage = find_option("extpage",0,1); |
| 3333 | if( zExtPage ){ |
| 3334 | char *zFullPath = file_canonical_name_dup(zExtPage); |
| 3335 | g.zExtRoot = file_dirname(zFullPath); |
| 3336 | zInitPage = mprintf("ext/%s",file_tail(zFullPath)); |
| 3337 | fossil_free(zFullPath); |
| 3338 | }else{ |
| 3339 | zInitPage = find_option("page", "p", 1); |
| 3340 | if( zInitPage && zInitPage[0]=='/' ) zInitPage++; |
| 3341 | } |
| 3342 | zFossilCmd = find_option("fossilcmd", 0, 1); |
| 3343 | if( zFrom && zInitPage==0 ){ |
| 3344 | zInitPage = mprintf("ckout?exbase=%H", zFrom); |
| 3345 | } |
| 3346 | } |
| @@ -3493,11 +3509,18 @@ | |
| 3509 | } |
| 3510 | blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort); |
| 3511 | if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); |
| 3512 | if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); |
| 3513 | if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias); |
| 3514 | if( zExtPage ){ |
| 3515 | if( !file_is_absolute_path(zExtPage) ){ |
| 3516 | zExtPage = mprintf("%s/%s", g.argv[2], zExtPage); |
| 3517 | } |
| 3518 | blob_appendf(&ssh, " --extpage %$", zExtPage); |
| 3519 | }else if( g.zExtRoot ){ |
| 3520 | blob_appendf(&ssh, " --extroot %$", g.zExtRoot); |
| 3521 | } |
| 3522 | if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use()); |
| 3523 | if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode); |
| 3524 | if( fCreate ) blob_appendf(&ssh, " --create"); |
| 3525 | blob_appendf(&ssh, " %$", g.argv[2]); |
| 3526 | if( isRetry ){ |
| 3527 |
+295
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -2911,5 +2911,300 @@ | ||
| 2911 | 2911 | if( g.argc!=3 ) usage("RECORDID"); |
| 2912 | 2912 | rid = name_to_rid(g.argv[2]); |
| 2913 | 2913 | content_get(rid, &content); |
| 2914 | 2914 | manifest_crosslink(rid, &content, MC_NONE); |
| 2915 | 2915 | } |
| 2916 | + | |
| 2917 | +/* | |
| 2918 | +** For a given CATYPE_... value, returns a human-friendly name, or | |
| 2919 | +** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by | |
| 2920 | +** this function are geared towards use with artifact_to_json(), and | |
| 2921 | +** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts | |
| 2922 | +** are called "tag" artifacts by this function. | |
| 2923 | +*/ | |
| 2924 | +const char * artifact_type_to_name(int typeId){ | |
| 2925 | + switch(typeId){ | |
| 2926 | + case CFTYPE_MANIFEST: return "checkin"; | |
| 2927 | + case CFTYPE_CLUSTER: return "cluster"; | |
| 2928 | + case CFTYPE_CONTROL: return "tag"; | |
| 2929 | + case CFTYPE_WIKI: return "wiki"; | |
| 2930 | + case CFTYPE_TICKET: return "ticket"; | |
| 2931 | + case CFTYPE_ATTACHMENT: return "attachment"; | |
| 2932 | + case CFTYPE_EVENT: return "technote"; | |
| 2933 | + case CFTYPE_FORUM: return "forumpost"; | |
| 2934 | + } | |
| 2935 | + return NULL; | |
| 2936 | +} | |
| 2937 | + | |
| 2938 | +/* | |
| 2939 | +** Creates a JSON representation of p, appending it to b. | |
| 2940 | +** | |
| 2941 | +** b is not cleared before rendering, so the caller needs to do that | |
| 2942 | +** if it's important for their use case. | |
| 2943 | +** | |
| 2944 | +** Pedantic note: this routine traverses p->aFile directly, rather | |
| 2945 | +** than using manifest_file_next(), so that delta manifests are | |
| 2946 | +** rendered as-is instead of containing their derived F-cards. If that | |
| 2947 | +** policy is ever changed, p will need to be non-const. | |
| 2948 | +*/ | |
| 2949 | +void artifact_to_json(Manifest const *p, Blob *b){ | |
| 2950 | + int i; | |
| 2951 | + | |
| 2952 | + blob_append_literal(b, "{"); | |
| 2953 | + blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid)); | |
| 2954 | + /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/ | |
| 2955 | + blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type)); | |
| 2956 | +#define ISA(TYPE) if( p->type==TYPE ) | |
| 2957 | +#define CARD_LETTER(LETTER) \ | |
| 2958 | + blob_append_literal(b, ",\"" #LETTER "\":") | |
| 2959 | +#define CARD_STR(LETTER, VAL) \ | |
| 2960 | + assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL) | |
| 2961 | +#define CARD_STR2(LETTER, VAL) \ | |
| 2962 | + if( VAL ) { CARD_STR(LETTER, VAL); } (void)0 | |
| 2963 | +#define STR_OR_NULL(VAL) \ | |
| 2964 | + if( VAL ) blob_appendf(b, "%!j", VAL); \ | |
| 2965 | + else blob_append(b, "null", 4) | |
| 2966 | +#define KVP_STR(ADDCOMMA, KEY,VAL) \ | |
| 2967 | + if(ADDCOMMA) blob_append_char(b, ','); \ | |
| 2968 | + blob_appendf(b, "%!j:", #KEY); \ | |
| 2969 | + STR_OR_NULL(VAL) | |
| 2970 | + | |
| 2971 | + ISA( CFTYPE_ATTACHMENT ){ | |
| 2972 | + CARD_LETTER(A); | |
| 2973 | + blob_append_char(b, '{'); | |
| 2974 | + KVP_STR(0, filename, p->zAttachName); | |
| 2975 | + KVP_STR(1, target, p->zAttachTarget); | |
| 2976 | + KVP_STR(1, source, p->zAttachSrc); | |
| 2977 | + blob_append_char(b, '}'); | |
| 2978 | + } | |
| 2979 | + CARD_STR2(B, p->zBaseline); | |
| 2980 | + CARD_STR2(C, p->zComment); | |
| 2981 | + CARD_LETTER(D); blob_appendf(b, "%f", p->rDate); | |
| 2982 | + ISA( CFTYPE_EVENT ){ | |
| 2983 | + blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}", | |
| 2984 | + p->rEventDate, p->zEventId); | |
| 2985 | + } | |
| 2986 | + ISA( CFTYPE_MANIFEST ){ | |
| 2987 | + CARD_LETTER(F); | |
| 2988 | + blob_append_char(b, '['); | |
| 2989 | + for( i = 0; i < p->nFile; ++i ){ | |
| 2990 | + ManifestFile const * const pF = &p->aFile[i]; | |
| 2991 | + if( i>0 ) blob_append_char(b, ','); | |
| 2992 | + blob_append_char(b, '{'); | |
| 2993 | + KVP_STR(0, name, pF->zName); | |
| 2994 | + KVP_STR(1, uuid, pF->zUuid); | |
| 2995 | + KVP_STR(1, perm, pF->zPerm); | |
| 2996 | + KVP_STR(1, rename, pF->zPrior); | |
| 2997 | + blob_append_char(b, '}'); | |
| 2998 | + } | |
| 2999 | + /* Special case: model checkins with no F-card as having an empty | |
| 3000 | + ** array, rather than no F-cards, to hypothetically simplify | |
| 3001 | + ** handling in JSON queries. */ | |
| 3002 | + blob_append_char(b, ']'); | |
| 3003 | + } | |
| 3004 | + CARD_STR2(G, p->zThreadRoot); | |
| 3005 | + ISA( CFTYPE_FORUM ){ | |
| 3006 | + CARD_LETTER(H); | |
| 3007 | + STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL); | |
| 3008 | + CARD_STR2(I, p->zInReplyTo); | |
| 3009 | + } | |
| 3010 | + if( p->nField ){ | |
| 3011 | + CARD_LETTER(J); | |
| 3012 | + blob_append_char(b, '['); | |
| 3013 | + for( i = 0; i < p->nField; ++i ){ | |
| 3014 | + const char * zName = p->aField[i].zName; | |
| 3015 | + if( i>0 ) blob_append_char(b, ','); | |
| 3016 | + blob_append_char(b, '{'); | |
| 3017 | + KVP_STR(0, name, '+'==*zName ? &zName[1] : zName); | |
| 3018 | + KVP_STR(1, value, p->aField[i].zValue); | |
| 3019 | + blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false"); | |
| 3020 | + blob_append_char(b, '}'); | |
| 3021 | + } | |
| 3022 | + blob_append_char(b, ']'); | |
| 3023 | + } | |
| 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 | + } | |
| 3035 | + CARD_STR2(N, p->zMimetype); | |
| 3036 | + ISA( CFTYPE_MANIFEST || p->nParent>0 ){ | |
| 3037 | + CARD_LETTER(P); | |
| 3038 | + blob_append_char(b, '['); | |
| 3039 | + for( i = 0; i < p->nParent; ++i ){ | |
| 3040 | + if( i>0 ) blob_append_char(b, ','); | |
| 3041 | + blob_appendf(b, "%!j", p->azParent[i]); | |
| 3042 | + } | |
| 3043 | + /* Special case: model checkins with no P-card as having an empty | |
| 3044 | + ** array, as per F-cards. */ | |
| 3045 | + blob_append_char(b, ']'); | |
| 3046 | + } | |
| 3047 | + if( p->nCherrypick ){ | |
| 3048 | + CARD_LETTER(Q); | |
| 3049 | + blob_append_char(b, '['); | |
| 3050 | + for( i = 0; i < p->nCherrypick; ++i ){ | |
| 3051 | + if( i>0 ) blob_append_char(b, ','); | |
| 3052 | + blob_append_char(b, '{'); | |
| 3053 | + blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]); | |
| 3054 | + KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]); | |
| 3055 | + KVP_STR(1, base, p->aCherrypick[i].zCPBase); | |
| 3056 | + blob_append_char(b, '}'); | |
| 3057 | + } | |
| 3058 | + blob_append_char(b, ']'); | |
| 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 | + KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*") | |
| 3071 | + /* We could arguably resolve the "*" as null or p's uuid. */; | |
| 3072 | + KVP_STR(1, value, p->aTag[i].zValue); | |
| 3073 | + blob_append_char(b, '}'); | |
| 3074 | + } | |
| 3075 | + blob_append_char(b, ']'); | |
| 3076 | + } | |
| 3077 | + CARD_STR2(U, p->zUser); | |
| 3078 | + if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type | |
| 3079 | + || CFTYPE_EVENT==p->type ){ | |
| 3080 | + CARD_LETTER(W); | |
| 3081 | + STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL); | |
| 3082 | + } | |
| 3083 | + blob_append_literal(b, "}"); | |
| 3084 | +#undef CARD_FMT | |
| 3085 | +#undef CARD_LETTER | |
| 3086 | +#undef CARD_STR | |
| 3087 | +#undef CARD_STR2 | |
| 3088 | +#undef ISA | |
| 3089 | +#undef KVP_STR | |
| 3090 | +#undef STR_OR_NULL | |
| 3091 | +} | |
| 3092 | + | |
| 3093 | +/* | |
| 3094 | +** Convenience wrapper around artifact_to_json() which expects rid to | |
| 3095 | +** be the blob.rid of any artifact type. If it can load a Manifest | |
| 3096 | +** with that rid, it returns rid, else it returns 0. | |
| 3097 | +*/ | |
| 3098 | +int artifact_to_json_by_rid(int rid, Blob *pOut){ | |
| 3099 | + Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); | |
| 3100 | + if( p ){ | |
| 3101 | + artifact_to_json(p, pOut); | |
| 3102 | + manifest_destroy(p); | |
| 3103 | + }else{ | |
| 3104 | + rid = 0; | |
| 3105 | + } | |
| 3106 | + return rid; | |
| 3107 | +} | |
| 3108 | + | |
| 3109 | +/* | |
| 3110 | +** Convenience wrapper around artifact_to_json() which accepts any | |
| 3111 | +** artifact name which is legal for symbolic_name_to_rid(). On success | |
| 3112 | +** it returns the rid of the artifact. Returns 0 if no such artifact | |
| 3113 | +** exists and a negative value if the name is ambiguous. | |
| 3114 | +** | |
| 3115 | +** pOut is not cleared before rendering, so the caller needs to do | |
| 3116 | +** that if it's important for their use case. | |
| 3117 | +*/ | |
| 3118 | +int artifact_to_json_by_name(const char *zName, Blob *pOut){ | |
| 3119 | + const int rid = symbolic_name_to_rid(zName, 0); | |
| 3120 | + return rid>0 | |
| 3121 | + ? artifact_to_json_by_rid(rid, pOut) | |
| 3122 | + : rid; | |
| 3123 | +} | |
| 3124 | + | |
| 3125 | +/* | |
| 3126 | +** SQLite UDF for artifact_to_json(). Its single argument should be | |
| 3127 | +** either an INTEGER (blob.rid value) or a TEXT symbolic artifact | |
| 3128 | +** name, as per symbolic_name_to_rid(). If an artifact is found then | |
| 3129 | +** the result of the UDF is that JSON as a string, else it evaluates | |
| 3130 | +** to NULL. | |
| 3131 | +*/ | |
| 3132 | +void artifact_to_json_sql_func( | |
| 3133 | + sqlite3_context *context, | |
| 3134 | + int argc, | |
| 3135 | + sqlite3_value **argv | |
| 3136 | +){ | |
| 3137 | + int rid = 0; | |
| 3138 | + Blob b = empty_blob; | |
| 3139 | + | |
| 3140 | + if(1 != argc){ | |
| 3141 | + goto error_usage; | |
| 3142 | + } | |
| 3143 | + switch( sqlite3_value_type(argv[0]) ){ | |
| 3144 | + case SQLITE_INTEGER: | |
| 3145 | + rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b); | |
| 3146 | + break; | |
| 3147 | + case SQLITE_TEXT:{ | |
| 3148 | + const char * z = (const char *)sqlite3_value_text(argv[0]); | |
| 3149 | + if( z ){ | |
| 3150 | + rid = artifact_to_json_by_name(z, &b); | |
| 3151 | + } | |
| 3152 | + break; | |
| 3153 | + } | |
| 3154 | + default: | |
| 3155 | + goto error_usage; | |
| 3156 | + } | |
| 3157 | + if( rid>0 ){ | |
| 3158 | + sqlite3_result_text(context, blob_str(&b), blob_size(&b), | |
| 3159 | + SQLITE_TRANSIENT); | |
| 3160 | + blob_reset(&b); | |
| 3161 | + }else{ | |
| 3162 | + /* We should arguably error out if rid<0 (ambiguous name) */ | |
| 3163 | + sqlite3_result_null(context); | |
| 3164 | + } | |
| 3165 | + return; | |
| 3166 | +error_usage: | |
| 3167 | + sqlite3_result_error(context, "Expecting one argument: blob.rid or " | |
| 3168 | + "artifact symbolic name", -1); | |
| 3169 | +} | |
| 3170 | + | |
| 3171 | + | |
| 3172 | + | |
| 3173 | +/* | |
| 3174 | +** COMMAND: test-artifact-to-json | |
| 3175 | +** | |
| 3176 | +** Usage: %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names] | |
| 3177 | +** | |
| 3178 | +** Tests the artifact_to_json() and artifact_to_json_by_name() APIs. | |
| 3179 | +*/ | |
| 3180 | +void test_manifest_to_json(void){ | |
| 3181 | + int i; | |
| 3182 | + Blob b = empty_blob; | |
| 3183 | + Stmt q; | |
| 3184 | + const int bPretty = find_option("pretty","p",0)!=0; | |
| 3185 | + int nErr = 0; | |
| 3186 | + | |
| 3187 | + db_find_and_open_repository(0,0); | |
| 3188 | + db_prepare(&q, "select json_pretty(:json)"); | |
| 3189 | + for( i=2; i<g.argc; ++i ){ | |
| 3190 | + char const *zName = g.argv[i]; | |
| 3191 | + const int rc = artifact_to_json_by_name(zName, &b); | |
| 3192 | + if( rc<=0 ){ | |
| 3193 | + ++nErr; | |
| 3194 | + fossil_warning("Error reading artifact %Q", zName); | |
| 3195 | + continue; | |
| 3196 | + }else if( bPretty ){ | |
| 3197 | + db_bind_blob(&q, ":json", &b); | |
| 3198 | + b.nUsed = 0; | |
| 3199 | + db_step(&q); | |
| 3200 | + db_column_blob(&q, 0, &b); | |
| 3201 | + db_reset(&q); | |
| 3202 | + } | |
| 3203 | + fossil_print("%b\n", &b); | |
| 3204 | + blob_reset(&b); | |
| 3205 | + } | |
| 3206 | + db_finalize(&q); | |
| 3207 | + if( nErr ){ | |
| 3208 | + fossil_warning("Error count: %d", nErr); | |
| 3209 | + } | |
| 3210 | +} | |
| 2916 | 3211 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2911,5 +2911,300 @@ | |
| 2911 | if( g.argc!=3 ) usage("RECORDID"); |
| 2912 | rid = name_to_rid(g.argv[2]); |
| 2913 | content_get(rid, &content); |
| 2914 | manifest_crosslink(rid, &content, MC_NONE); |
| 2915 | } |
| 2916 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -2911,5 +2911,300 @@ | |
| 2911 | if( g.argc!=3 ) usage("RECORDID"); |
| 2912 | rid = name_to_rid(g.argv[2]); |
| 2913 | content_get(rid, &content); |
| 2914 | manifest_crosslink(rid, &content, MC_NONE); |
| 2915 | } |
| 2916 | |
| 2917 | /* |
| 2918 | ** For a given CATYPE_... value, returns a human-friendly name, or |
| 2919 | ** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by |
| 2920 | ** this function are geared towards use with artifact_to_json(), and |
| 2921 | ** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts |
| 2922 | ** are called "tag" artifacts by this function. |
| 2923 | */ |
| 2924 | const char * artifact_type_to_name(int typeId){ |
| 2925 | switch(typeId){ |
| 2926 | case CFTYPE_MANIFEST: return "checkin"; |
| 2927 | case CFTYPE_CLUSTER: return "cluster"; |
| 2928 | case CFTYPE_CONTROL: return "tag"; |
| 2929 | case CFTYPE_WIKI: return "wiki"; |
| 2930 | case CFTYPE_TICKET: return "ticket"; |
| 2931 | case CFTYPE_ATTACHMENT: return "attachment"; |
| 2932 | case CFTYPE_EVENT: return "technote"; |
| 2933 | case CFTYPE_FORUM: return "forumpost"; |
| 2934 | } |
| 2935 | return NULL; |
| 2936 | } |
| 2937 | |
| 2938 | /* |
| 2939 | ** Creates a JSON representation of p, appending it to b. |
| 2940 | ** |
| 2941 | ** b is not cleared before rendering, so the caller needs to do that |
| 2942 | ** if it's important for their use case. |
| 2943 | ** |
| 2944 | ** Pedantic note: this routine traverses p->aFile directly, rather |
| 2945 | ** than using manifest_file_next(), so that delta manifests are |
| 2946 | ** rendered as-is instead of containing their derived F-cards. If that |
| 2947 | ** policy is ever changed, p will need to be non-const. |
| 2948 | */ |
| 2949 | void artifact_to_json(Manifest const *p, Blob *b){ |
| 2950 | int i; |
| 2951 | |
| 2952 | blob_append_literal(b, "{"); |
| 2953 | blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid)); |
| 2954 | /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/ |
| 2955 | blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type)); |
| 2956 | #define ISA(TYPE) if( p->type==TYPE ) |
| 2957 | #define CARD_LETTER(LETTER) \ |
| 2958 | blob_append_literal(b, ",\"" #LETTER "\":") |
| 2959 | #define CARD_STR(LETTER, VAL) \ |
| 2960 | assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL) |
| 2961 | #define CARD_STR2(LETTER, VAL) \ |
| 2962 | if( VAL ) { CARD_STR(LETTER, VAL); } (void)0 |
| 2963 | #define STR_OR_NULL(VAL) \ |
| 2964 | if( VAL ) blob_appendf(b, "%!j", VAL); \ |
| 2965 | else blob_append(b, "null", 4) |
| 2966 | #define KVP_STR(ADDCOMMA, KEY,VAL) \ |
| 2967 | if(ADDCOMMA) blob_append_char(b, ','); \ |
| 2968 | blob_appendf(b, "%!j:", #KEY); \ |
| 2969 | STR_OR_NULL(VAL) |
| 2970 | |
| 2971 | ISA( CFTYPE_ATTACHMENT ){ |
| 2972 | CARD_LETTER(A); |
| 2973 | blob_append_char(b, '{'); |
| 2974 | KVP_STR(0, filename, p->zAttachName); |
| 2975 | KVP_STR(1, target, p->zAttachTarget); |
| 2976 | KVP_STR(1, source, p->zAttachSrc); |
| 2977 | blob_append_char(b, '}'); |
| 2978 | } |
| 2979 | CARD_STR2(B, p->zBaseline); |
| 2980 | CARD_STR2(C, p->zComment); |
| 2981 | CARD_LETTER(D); blob_appendf(b, "%f", p->rDate); |
| 2982 | ISA( CFTYPE_EVENT ){ |
| 2983 | blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}", |
| 2984 | p->rEventDate, p->zEventId); |
| 2985 | } |
| 2986 | ISA( CFTYPE_MANIFEST ){ |
| 2987 | CARD_LETTER(F); |
| 2988 | blob_append_char(b, '['); |
| 2989 | for( i = 0; i < p->nFile; ++i ){ |
| 2990 | ManifestFile const * const pF = &p->aFile[i]; |
| 2991 | if( i>0 ) blob_append_char(b, ','); |
| 2992 | blob_append_char(b, '{'); |
| 2993 | KVP_STR(0, name, pF->zName); |
| 2994 | KVP_STR(1, uuid, pF->zUuid); |
| 2995 | KVP_STR(1, perm, pF->zPerm); |
| 2996 | KVP_STR(1, rename, pF->zPrior); |
| 2997 | blob_append_char(b, '}'); |
| 2998 | } |
| 2999 | /* Special case: model checkins with no F-card as having an empty |
| 3000 | ** array, rather than no F-cards, to hypothetically simplify |
| 3001 | ** handling in JSON queries. */ |
| 3002 | blob_append_char(b, ']'); |
| 3003 | } |
| 3004 | CARD_STR2(G, p->zThreadRoot); |
| 3005 | ISA( CFTYPE_FORUM ){ |
| 3006 | CARD_LETTER(H); |
| 3007 | STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL); |
| 3008 | CARD_STR2(I, p->zInReplyTo); |
| 3009 | } |
| 3010 | if( p->nField ){ |
| 3011 | CARD_LETTER(J); |
| 3012 | blob_append_char(b, '['); |
| 3013 | for( i = 0; i < p->nField; ++i ){ |
| 3014 | const char * zName = p->aField[i].zName; |
| 3015 | if( i>0 ) blob_append_char(b, ','); |
| 3016 | blob_append_char(b, '{'); |
| 3017 | KVP_STR(0, name, '+'==*zName ? &zName[1] : zName); |
| 3018 | KVP_STR(1, value, p->aField[i].zValue); |
| 3019 | blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false"); |
| 3020 | blob_append_char(b, '}'); |
| 3021 | } |
| 3022 | blob_append_char(b, ']'); |
| 3023 | } |
| 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 | } |
| 3035 | CARD_STR2(N, p->zMimetype); |
| 3036 | ISA( CFTYPE_MANIFEST || p->nParent>0 ){ |
| 3037 | CARD_LETTER(P); |
| 3038 | blob_append_char(b, '['); |
| 3039 | for( i = 0; i < p->nParent; ++i ){ |
| 3040 | if( i>0 ) blob_append_char(b, ','); |
| 3041 | blob_appendf(b, "%!j", p->azParent[i]); |
| 3042 | } |
| 3043 | /* Special case: model checkins with no P-card as having an empty |
| 3044 | ** array, as per F-cards. */ |
| 3045 | blob_append_char(b, ']'); |
| 3046 | } |
| 3047 | if( p->nCherrypick ){ |
| 3048 | CARD_LETTER(Q); |
| 3049 | blob_append_char(b, '['); |
| 3050 | for( i = 0; i < p->nCherrypick; ++i ){ |
| 3051 | if( i>0 ) blob_append_char(b, ','); |
| 3052 | blob_append_char(b, '{'); |
| 3053 | blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]); |
| 3054 | KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]); |
| 3055 | KVP_STR(1, base, p->aCherrypick[i].zCPBase); |
| 3056 | blob_append_char(b, '}'); |
| 3057 | } |
| 3058 | blob_append_char(b, ']'); |
| 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 | KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*") |
| 3071 | /* We could arguably resolve the "*" as null or p's uuid. */; |
| 3072 | KVP_STR(1, value, p->aTag[i].zValue); |
| 3073 | blob_append_char(b, '}'); |
| 3074 | } |
| 3075 | blob_append_char(b, ']'); |
| 3076 | } |
| 3077 | CARD_STR2(U, p->zUser); |
| 3078 | if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type |
| 3079 | || CFTYPE_EVENT==p->type ){ |
| 3080 | CARD_LETTER(W); |
| 3081 | STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL); |
| 3082 | } |
| 3083 | blob_append_literal(b, "}"); |
| 3084 | #undef CARD_FMT |
| 3085 | #undef CARD_LETTER |
| 3086 | #undef CARD_STR |
| 3087 | #undef CARD_STR2 |
| 3088 | #undef ISA |
| 3089 | #undef KVP_STR |
| 3090 | #undef STR_OR_NULL |
| 3091 | } |
| 3092 | |
| 3093 | /* |
| 3094 | ** Convenience wrapper around artifact_to_json() which expects rid to |
| 3095 | ** be the blob.rid of any artifact type. If it can load a Manifest |
| 3096 | ** with that rid, it returns rid, else it returns 0. |
| 3097 | */ |
| 3098 | int artifact_to_json_by_rid(int rid, Blob *pOut){ |
| 3099 | Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0); |
| 3100 | if( p ){ |
| 3101 | artifact_to_json(p, pOut); |
| 3102 | manifest_destroy(p); |
| 3103 | }else{ |
| 3104 | rid = 0; |
| 3105 | } |
| 3106 | return rid; |
| 3107 | } |
| 3108 | |
| 3109 | /* |
| 3110 | ** Convenience wrapper around artifact_to_json() which accepts any |
| 3111 | ** artifact name which is legal for symbolic_name_to_rid(). On success |
| 3112 | ** it returns the rid of the artifact. Returns 0 if no such artifact |
| 3113 | ** exists and a negative value if the name is ambiguous. |
| 3114 | ** |
| 3115 | ** pOut is not cleared before rendering, so the caller needs to do |
| 3116 | ** that if it's important for their use case. |
| 3117 | */ |
| 3118 | int artifact_to_json_by_name(const char *zName, Blob *pOut){ |
| 3119 | const int rid = symbolic_name_to_rid(zName, 0); |
| 3120 | return rid>0 |
| 3121 | ? artifact_to_json_by_rid(rid, pOut) |
| 3122 | : rid; |
| 3123 | } |
| 3124 | |
| 3125 | /* |
| 3126 | ** SQLite UDF for artifact_to_json(). Its single argument should be |
| 3127 | ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact |
| 3128 | ** name, as per symbolic_name_to_rid(). If an artifact is found then |
| 3129 | ** the result of the UDF is that JSON as a string, else it evaluates |
| 3130 | ** to NULL. |
| 3131 | */ |
| 3132 | void artifact_to_json_sql_func( |
| 3133 | sqlite3_context *context, |
| 3134 | int argc, |
| 3135 | sqlite3_value **argv |
| 3136 | ){ |
| 3137 | int rid = 0; |
| 3138 | Blob b = empty_blob; |
| 3139 | |
| 3140 | if(1 != argc){ |
| 3141 | goto error_usage; |
| 3142 | } |
| 3143 | switch( sqlite3_value_type(argv[0]) ){ |
| 3144 | case SQLITE_INTEGER: |
| 3145 | rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b); |
| 3146 | break; |
| 3147 | case SQLITE_TEXT:{ |
| 3148 | const char * z = (const char *)sqlite3_value_text(argv[0]); |
| 3149 | if( z ){ |
| 3150 | rid = artifact_to_json_by_name(z, &b); |
| 3151 | } |
| 3152 | break; |
| 3153 | } |
| 3154 | default: |
| 3155 | goto error_usage; |
| 3156 | } |
| 3157 | if( rid>0 ){ |
| 3158 | sqlite3_result_text(context, blob_str(&b), blob_size(&b), |
| 3159 | SQLITE_TRANSIENT); |
| 3160 | blob_reset(&b); |
| 3161 | }else{ |
| 3162 | /* We should arguably error out if rid<0 (ambiguous name) */ |
| 3163 | sqlite3_result_null(context); |
| 3164 | } |
| 3165 | return; |
| 3166 | error_usage: |
| 3167 | sqlite3_result_error(context, "Expecting one argument: blob.rid or " |
| 3168 | "artifact symbolic name", -1); |
| 3169 | } |
| 3170 | |
| 3171 | |
| 3172 | |
| 3173 | /* |
| 3174 | ** COMMAND: test-artifact-to-json |
| 3175 | ** |
| 3176 | ** Usage: %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names] |
| 3177 | ** |
| 3178 | ** Tests the artifact_to_json() and artifact_to_json_by_name() APIs. |
| 3179 | */ |
| 3180 | void test_manifest_to_json(void){ |
| 3181 | int i; |
| 3182 | Blob b = empty_blob; |
| 3183 | Stmt q; |
| 3184 | const int bPretty = find_option("pretty","p",0)!=0; |
| 3185 | int nErr = 0; |
| 3186 | |
| 3187 | db_find_and_open_repository(0,0); |
| 3188 | db_prepare(&q, "select json_pretty(:json)"); |
| 3189 | for( i=2; i<g.argc; ++i ){ |
| 3190 | char const *zName = g.argv[i]; |
| 3191 | const int rc = artifact_to_json_by_name(zName, &b); |
| 3192 | if( rc<=0 ){ |
| 3193 | ++nErr; |
| 3194 | fossil_warning("Error reading artifact %Q", zName); |
| 3195 | continue; |
| 3196 | }else if( bPretty ){ |
| 3197 | db_bind_blob(&q, ":json", &b); |
| 3198 | b.nUsed = 0; |
| 3199 | db_step(&q); |
| 3200 | db_column_blob(&q, 0, &b); |
| 3201 | db_reset(&q); |
| 3202 | } |
| 3203 | fossil_print("%b\n", &b); |
| 3204 | blob_reset(&b); |
| 3205 | } |
| 3206 | db_finalize(&q); |
| 3207 | if( nErr ){ |
| 3208 | fossil_warning("Error count: %d", nErr); |
| 3209 | } |
| 3210 | } |
| 3211 |
+56
-1
| --- src/name.c | ||
| +++ src/name.c | ||
| @@ -70,11 +70,11 @@ | ||
| 70 | 70 | ** ^^^^--- Added |
| 71 | 71 | ** |
| 72 | 72 | ** 202503171234 -> 2025-03-17 12:34:59.999 |
| 73 | 73 | ** ^^^^^^^--- Added |
| 74 | 74 | ** 20250317 -> 2025-03-17 23:59:59.999 |
| 75 | -** ^^^^^^^^^^^^--- Added | |
| 75 | +** ^^^^^^^^^^^^^--- Added | |
| 76 | 76 | ** |
| 77 | 77 | ** If the bVerifyNotAHash flag is true, then a check is made to see if |
| 78 | 78 | ** the input string is a hash prefix and NULL is returned if it is. If the |
| 79 | 79 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 80 | 80 | ** of the input string only, without reference to the artifact table. |
| @@ -718,10 +718,65 @@ | ||
| 718 | 718 | rid = symbolic_name_to_rid(zNew,zType); |
| 719 | 719 | fossil_free(zNew); |
| 720 | 720 | } |
| 721 | 721 | return rid; |
| 722 | 722 | } |
| 723 | + | |
| 724 | +/* | |
| 725 | +** Convert a symbolic name used as an argument to the a=, b=, or c= | |
| 726 | +** query parameters of timeline into a julianday mtime value. | |
| 727 | +** | |
| 728 | +** If pzDisplay is not null, then display text for the symbolic name might | |
| 729 | +** be written into *pzDisplay. But that is not guaranteed. | |
| 730 | +** | |
| 731 | +** If bRoundUp is true and the symbolic name is a timestamp with less | |
| 732 | +** than millisecond resolution, then the timestamp is rounding up to the | |
| 733 | +** largest millisecond consistent with that timestamp. If bRoundUp is | |
| 734 | +** false, then the resulting time is obtained by extending the timestamp | |
| 735 | +** with zeros (hence rounding down). Use bRoundUp==1 if the result | |
| 736 | +** will be used in mtime<=$RESULT and use bRoundUp==0 if the result | |
| 737 | +** will be used in mtime>=$RESULT. | |
| 738 | +*/ | |
| 739 | +double symbolic_name_to_mtime( | |
| 740 | + const char *z, /* Input symbolic name */ | |
| 741 | + const char **pzDisplay, /* Perhaps write display text here, if not NULL */ | |
| 742 | + int bRoundUp /* Round up if true */ | |
| 743 | +){ | |
| 744 | + double mtime; | |
| 745 | + int rid; | |
| 746 | + const char *zDate; | |
| 747 | + if( z==0 ) return -1.0; | |
| 748 | + if( fossil_isdate(z) ){ | |
| 749 | + mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z); | |
| 750 | + if( mtime>0.0 ) return mtime; | |
| 751 | + } | |
| 752 | + zDate = fossil_expand_datetime(z, 1, bRoundUp); | |
| 753 | + if( zDate!=0 ){ | |
| 754 | + mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", | |
| 755 | + bRoundUp ? fossil_roundup_date(zDate) : zDate); | |
| 756 | + if( mtime>0.0 ){ | |
| 757 | + if( pzDisplay ){ | |
| 758 | + zDate = fossil_expand_datetime(z,0,0); | |
| 759 | + *pzDisplay = fossil_strdup(zDate); | |
| 760 | + } | |
| 761 | + return mtime; | |
| 762 | + } | |
| 763 | + } | |
| 764 | + rid = symbolic_name_to_rid(z, "*"); | |
| 765 | + if( rid ){ | |
| 766 | + mtime = mtime_of_rid(rid, 0.0); | |
| 767 | + }else{ | |
| 768 | + mtime = db_double(-1.0, | |
| 769 | + "SELECT max(event.mtime) FROM event, tag, tagxref" | |
| 770 | + " WHERE tag.tagname GLOB 'event-%q*'" | |
| 771 | + " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" | |
| 772 | + " AND event.objid=tagxref.rid", | |
| 773 | + z | |
| 774 | + ); | |
| 775 | + } | |
| 776 | + return mtime; | |
| 777 | +} | |
| 723 | 778 | |
| 724 | 779 | /* |
| 725 | 780 | ** This routine takes a user-entered string and tries to convert it to |
| 726 | 781 | ** an artifact hash. |
| 727 | 782 | ** |
| 728 | 783 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -70,11 +70,11 @@ | |
| 70 | ** ^^^^--- Added |
| 71 | ** |
| 72 | ** 202503171234 -> 2025-03-17 12:34:59.999 |
| 73 | ** ^^^^^^^--- Added |
| 74 | ** 20250317 -> 2025-03-17 23:59:59.999 |
| 75 | ** ^^^^^^^^^^^^--- Added |
| 76 | ** |
| 77 | ** If the bVerifyNotAHash flag is true, then a check is made to see if |
| 78 | ** the input string is a hash prefix and NULL is returned if it is. If the |
| 79 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 80 | ** of the input string only, without reference to the artifact table. |
| @@ -718,10 +718,65 @@ | |
| 718 | rid = symbolic_name_to_rid(zNew,zType); |
| 719 | fossil_free(zNew); |
| 720 | } |
| 721 | return rid; |
| 722 | } |
| 723 | |
| 724 | /* |
| 725 | ** This routine takes a user-entered string and tries to convert it to |
| 726 | ** an artifact hash. |
| 727 | ** |
| 728 |
| --- src/name.c | |
| +++ src/name.c | |
| @@ -70,11 +70,11 @@ | |
| 70 | ** ^^^^--- Added |
| 71 | ** |
| 72 | ** 202503171234 -> 2025-03-17 12:34:59.999 |
| 73 | ** ^^^^^^^--- Added |
| 74 | ** 20250317 -> 2025-03-17 23:59:59.999 |
| 75 | ** ^^^^^^^^^^^^^--- Added |
| 76 | ** |
| 77 | ** If the bVerifyNotAHash flag is true, then a check is made to see if |
| 78 | ** the input string is a hash prefix and NULL is returned if it is. If the |
| 79 | ** bVerifyNotAHash flag is false, then the result is determined by syntax |
| 80 | ** of the input string only, without reference to the artifact table. |
| @@ -718,10 +718,65 @@ | |
| 718 | rid = symbolic_name_to_rid(zNew,zType); |
| 719 | fossil_free(zNew); |
| 720 | } |
| 721 | return rid; |
| 722 | } |
| 723 | |
| 724 | /* |
| 725 | ** Convert a symbolic name used as an argument to the a=, b=, or c= |
| 726 | ** query parameters of timeline into a julianday mtime value. |
| 727 | ** |
| 728 | ** If pzDisplay is not null, then display text for the symbolic name might |
| 729 | ** be written into *pzDisplay. But that is not guaranteed. |
| 730 | ** |
| 731 | ** If bRoundUp is true and the symbolic name is a timestamp with less |
| 732 | ** than millisecond resolution, then the timestamp is rounding up to the |
| 733 | ** largest millisecond consistent with that timestamp. If bRoundUp is |
| 734 | ** false, then the resulting time is obtained by extending the timestamp |
| 735 | ** with zeros (hence rounding down). Use bRoundUp==1 if the result |
| 736 | ** will be used in mtime<=$RESULT and use bRoundUp==0 if the result |
| 737 | ** will be used in mtime>=$RESULT. |
| 738 | */ |
| 739 | double symbolic_name_to_mtime( |
| 740 | const char *z, /* Input symbolic name */ |
| 741 | const char **pzDisplay, /* Perhaps write display text here, if not NULL */ |
| 742 | int bRoundUp /* Round up if true */ |
| 743 | ){ |
| 744 | double mtime; |
| 745 | int rid; |
| 746 | const char *zDate; |
| 747 | if( z==0 ) return -1.0; |
| 748 | if( fossil_isdate(z) ){ |
| 749 | mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z); |
| 750 | if( mtime>0.0 ) return mtime; |
| 751 | } |
| 752 | zDate = fossil_expand_datetime(z, 1, bRoundUp); |
| 753 | if( zDate!=0 ){ |
| 754 | mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", |
| 755 | bRoundUp ? fossil_roundup_date(zDate) : zDate); |
| 756 | if( mtime>0.0 ){ |
| 757 | if( pzDisplay ){ |
| 758 | zDate = fossil_expand_datetime(z,0,0); |
| 759 | *pzDisplay = fossil_strdup(zDate); |
| 760 | } |
| 761 | return mtime; |
| 762 | } |
| 763 | } |
| 764 | rid = symbolic_name_to_rid(z, "*"); |
| 765 | if( rid ){ |
| 766 | mtime = mtime_of_rid(rid, 0.0); |
| 767 | }else{ |
| 768 | mtime = db_double(-1.0, |
| 769 | "SELECT max(event.mtime) FROM event, tag, tagxref" |
| 770 | " WHERE tag.tagname GLOB 'event-%q*'" |
| 771 | " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" |
| 772 | " AND event.objid=tagxref.rid", |
| 773 | z |
| 774 | ); |
| 775 | } |
| 776 | return mtime; |
| 777 | } |
| 778 | |
| 779 | /* |
| 780 | ** This routine takes a user-entered string and tries to convert it to |
| 781 | ** an artifact hash. |
| 782 | ** |
| 783 |
+9
-24
| --- src/printf.c | ||
| +++ src/printf.c | ||
| @@ -248,18 +248,10 @@ | ||
| 248 | 248 | ** If enabled, no wiki-formatting is done for timeline comment messages. |
| 249 | 249 | ** Hyperlinks are activated, but they show up on screen using the |
| 250 | 250 | ** complete input text, not just the display text. No other formatting |
| 251 | 251 | ** is done. |
| 252 | 252 | */ |
| 253 | -/* | |
| 254 | -** SETTING: timeline-block-markup boolean default=off | |
| 255 | -** | |
| 256 | -** If enabled, block markup (paragraph brakes, tables, lists, headings, etc) | |
| 257 | -** is enabled while rendering check-in comment message on the timeline. | |
| 258 | -** This is disabled by default, because the timeline works best if the | |
| 259 | -** check-in comments are short and do not take up too much vertical space. | |
| 260 | -*/ | |
| 261 | 253 | /* |
| 262 | 254 | ** SETTING: timeline-hard-newlines boolean default=off |
| 263 | 255 | ** |
| 264 | 256 | ** If enabled, the timeline honors newline characters in check-in comments. |
| 265 | 257 | ** In other words, newlines are coverted into <br> for HTML display. |
| @@ -271,36 +263,29 @@ | ||
| 271 | 263 | ** Return an appropriate set of flags for wiki_convert() for displaying |
| 272 | 264 | ** comments on a timeline. These flag settings are determined by |
| 273 | 265 | ** configuration parameters. |
| 274 | 266 | ** |
| 275 | 267 | ** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 |
| 276 | -** flags) and is false for plain "%W". The ! indicates that the text is | |
| 277 | -** to be rendered on a form rather than the timeline and that block markup | |
| 278 | -** is acceptable even if the "timeline-block-markup" setting is false. | |
| 268 | +** flags) and is false for plain "%W". The ! flag indicates that the | |
| 269 | +** formatting is for display of a check-in comment on the timeline. Such | |
| 270 | +** comments used to be renderedd differently, but ever since 2020, they | |
| 271 | +** have been rendered identially, so the ! flag does not make any different | |
| 272 | +** in the output any more. | |
| 279 | 273 | */ |
| 280 | -static int wiki_convert_flags(int altForm2){ | |
| 274 | +int wiki_convert_flags(int altForm2){ | |
| 281 | 275 | static int wikiFlags = 0; |
| 276 | + (void)altForm2; | |
| 282 | 277 | if( wikiFlags==0 ){ |
| 283 | - if( db_get_boolean("timeline-block-markup", 0) ){ | |
| 284 | - wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; | |
| 285 | - }else{ | |
| 286 | - wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS; | |
| 287 | - } | |
| 278 | + wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; | |
| 288 | 279 | if( db_get_boolean("timeline-plaintext", 0) ){ |
| 289 | 280 | wikiFlags |= WIKI_LINKSONLY; |
| 290 | 281 | } |
| 291 | 282 | if( db_get_boolean("timeline-hard-newlines", 0) ){ |
| 292 | 283 | wikiFlags |= WIKI_NEWLINE; |
| 293 | 284 | } |
| 294 | 285 | } |
| 295 | - if( altForm2 ){ | |
| 296 | - /* block markup (ex: <p>, <table>) allowed */ | |
| 297 | - return wikiFlags & ~WIKI_NOBLOCK; | |
| 298 | - }else{ | |
| 299 | - /* Do not allow any block format. Everything in a <span> */ | |
| 300 | - return wikiFlags; | |
| 301 | - } | |
| 286 | + return wikiFlags; | |
| 302 | 287 | } |
| 303 | 288 | |
| 304 | 289 | |
| 305 | 290 | |
| 306 | 291 | /* |
| 307 | 292 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -248,18 +248,10 @@ | |
| 248 | ** If enabled, no wiki-formatting is done for timeline comment messages. |
| 249 | ** Hyperlinks are activated, but they show up on screen using the |
| 250 | ** complete input text, not just the display text. No other formatting |
| 251 | ** is done. |
| 252 | */ |
| 253 | /* |
| 254 | ** SETTING: timeline-block-markup boolean default=off |
| 255 | ** |
| 256 | ** If enabled, block markup (paragraph brakes, tables, lists, headings, etc) |
| 257 | ** is enabled while rendering check-in comment message on the timeline. |
| 258 | ** This is disabled by default, because the timeline works best if the |
| 259 | ** check-in comments are short and do not take up too much vertical space. |
| 260 | */ |
| 261 | /* |
| 262 | ** SETTING: timeline-hard-newlines boolean default=off |
| 263 | ** |
| 264 | ** If enabled, the timeline honors newline characters in check-in comments. |
| 265 | ** In other words, newlines are coverted into <br> for HTML display. |
| @@ -271,36 +263,29 @@ | |
| 271 | ** Return an appropriate set of flags for wiki_convert() for displaying |
| 272 | ** comments on a timeline. These flag settings are determined by |
| 273 | ** configuration parameters. |
| 274 | ** |
| 275 | ** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 |
| 276 | ** flags) and is false for plain "%W". The ! indicates that the text is |
| 277 | ** to be rendered on a form rather than the timeline and that block markup |
| 278 | ** is acceptable even if the "timeline-block-markup" setting is false. |
| 279 | */ |
| 280 | static int wiki_convert_flags(int altForm2){ |
| 281 | static int wikiFlags = 0; |
| 282 | if( wikiFlags==0 ){ |
| 283 | if( db_get_boolean("timeline-block-markup", 0) ){ |
| 284 | wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; |
| 285 | }else{ |
| 286 | wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS; |
| 287 | } |
| 288 | if( db_get_boolean("timeline-plaintext", 0) ){ |
| 289 | wikiFlags |= WIKI_LINKSONLY; |
| 290 | } |
| 291 | if( db_get_boolean("timeline-hard-newlines", 0) ){ |
| 292 | wikiFlags |= WIKI_NEWLINE; |
| 293 | } |
| 294 | } |
| 295 | if( altForm2 ){ |
| 296 | /* block markup (ex: <p>, <table>) allowed */ |
| 297 | return wikiFlags & ~WIKI_NOBLOCK; |
| 298 | }else{ |
| 299 | /* Do not allow any block format. Everything in a <span> */ |
| 300 | return wikiFlags; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | |
| 305 | |
| 306 | /* |
| 307 |
| --- src/printf.c | |
| +++ src/printf.c | |
| @@ -248,18 +248,10 @@ | |
| 248 | ** If enabled, no wiki-formatting is done for timeline comment messages. |
| 249 | ** Hyperlinks are activated, but they show up on screen using the |
| 250 | ** complete input text, not just the display text. No other formatting |
| 251 | ** is done. |
| 252 | */ |
| 253 | /* |
| 254 | ** SETTING: timeline-hard-newlines boolean default=off |
| 255 | ** |
| 256 | ** If enabled, the timeline honors newline characters in check-in comments. |
| 257 | ** In other words, newlines are coverted into <br> for HTML display. |
| @@ -271,36 +263,29 @@ | |
| 263 | ** Return an appropriate set of flags for wiki_convert() for displaying |
| 264 | ** comments on a timeline. These flag settings are determined by |
| 265 | ** configuration parameters. |
| 266 | ** |
| 267 | ** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 |
| 268 | ** flags) and is false for plain "%W". The ! flag indicates that the |
| 269 | ** formatting is for display of a check-in comment on the timeline. Such |
| 270 | ** comments used to be renderedd differently, but ever since 2020, they |
| 271 | ** have been rendered identially, so the ! flag does not make any different |
| 272 | ** in the output any more. |
| 273 | */ |
| 274 | int wiki_convert_flags(int altForm2){ |
| 275 | static int wikiFlags = 0; |
| 276 | (void)altForm2; |
| 277 | if( wikiFlags==0 ){ |
| 278 | wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; |
| 279 | if( db_get_boolean("timeline-plaintext", 0) ){ |
| 280 | wikiFlags |= WIKI_LINKSONLY; |
| 281 | } |
| 282 | if( db_get_boolean("timeline-hard-newlines", 0) ){ |
| 283 | wikiFlags |= WIKI_NEWLINE; |
| 284 | } |
| 285 | } |
| 286 | return wikiFlags; |
| 287 | } |
| 288 | |
| 289 | |
| 290 | |
| 291 | /* |
| 292 |
+3
-2
| --- src/repolist.c | ||
| +++ src/repolist.c | ||
| @@ -316,20 +316,21 @@ | ||
| 316 | 316 | style_header("Repository List"); |
| 317 | 317 | @ %s(blob_str(&html)) |
| 318 | 318 | style_table_sorter(); |
| 319 | 319 | style_finish_page(); |
| 320 | 320 | }else{ |
| 321 | + const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List"); | |
| 321 | 322 | /* If no repositories were found that had the "repolist_skin" |
| 322 | 323 | ** property set, then use a default skin */ |
| 323 | 324 | @ <html> |
| 324 | 325 | @ <head> |
| 325 | 326 | @ <base href="%s(g.zBaseURL)/"> |
| 326 | 327 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 327 | - @ <title>Repository List</title> | |
| 328 | + @ <title>%h(zTitle)</title> | |
| 328 | 329 | @ </head> |
| 329 | 330 | @ <body> |
| 330 | - @ <h1 align="center">Fossil Repositories</h1> | |
| 331 | + @ <h1 align="center">%h(zTitle)</h1> | |
| 331 | 332 | @ %s(blob_str(&html)) |
| 332 | 333 | @ <script>%s(builtin_text("sorttable.js"))</script> |
| 333 | 334 | @ </body> |
| 334 | 335 | @ </html> |
| 335 | 336 | } |
| 336 | 337 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -316,20 +316,21 @@ | |
| 316 | style_header("Repository List"); |
| 317 | @ %s(blob_str(&html)) |
| 318 | style_table_sorter(); |
| 319 | style_finish_page(); |
| 320 | }else{ |
| 321 | /* If no repositories were found that had the "repolist_skin" |
| 322 | ** property set, then use a default skin */ |
| 323 | @ <html> |
| 324 | @ <head> |
| 325 | @ <base href="%s(g.zBaseURL)/"> |
| 326 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 327 | @ <title>Repository List</title> |
| 328 | @ </head> |
| 329 | @ <body> |
| 330 | @ <h1 align="center">Fossil Repositories</h1> |
| 331 | @ %s(blob_str(&html)) |
| 332 | @ <script>%s(builtin_text("sorttable.js"))</script> |
| 333 | @ </body> |
| 334 | @ </html> |
| 335 | } |
| 336 |
| --- src/repolist.c | |
| +++ src/repolist.c | |
| @@ -316,20 +316,21 @@ | |
| 316 | style_header("Repository List"); |
| 317 | @ %s(blob_str(&html)) |
| 318 | style_table_sorter(); |
| 319 | style_finish_page(); |
| 320 | }else{ |
| 321 | const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List"); |
| 322 | /* If no repositories were found that had the "repolist_skin" |
| 323 | ** property set, then use a default skin */ |
| 324 | @ <html> |
| 325 | @ <head> |
| 326 | @ <base href="%s(g.zBaseURL)/"> |
| 327 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 328 | @ <title>%h(zTitle)</title> |
| 329 | @ </head> |
| 330 | @ <body> |
| 331 | @ <h1 align="center">%h(zTitle)</h1> |
| 332 | @ %s(blob_str(&html)) |
| 333 | @ <script>%s(builtin_text("sorttable.js"))</script> |
| 334 | @ </body> |
| 335 | @ </html> |
| 336 | } |
| 337 |
+9
-95
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -559,93 +559,10 @@ | ||
| 559 | 559 | search_body_sqlfunc, 0, 0); |
| 560 | 560 | sqlite3_create_function(db, "urlencode", 1, enc, 0, |
| 561 | 561 | search_urlencode_sqlfunc, 0, 0); |
| 562 | 562 | } |
| 563 | 563 | |
| 564 | -/* | |
| 565 | -** The pSnip input contains snippet text from a search formatted | |
| 566 | -** as HTML. Attempt to make that text more readable on a TTY. | |
| 567 | -** | |
| 568 | -** If nTty is positive, use ANSI escape codes "\e[Nm" where N is nTty | |
| 569 | -** to highly marked text. | |
| 570 | -*/ | |
| 571 | -void search_snippet_to_plaintext(Blob *pSnip, int nTty){ | |
| 572 | - char *zSnip; | |
| 573 | - unsigned int j, k; | |
| 574 | - | |
| 575 | - zSnip = pSnip->aData; | |
| 576 | - for(j=k=0; j<pSnip->nUsed; j++){ | |
| 577 | - char c = zSnip[j]; | |
| 578 | - if( c=='<' ){ | |
| 579 | - if( memcmp(&zSnip[j],"<mark>",6)==0 ){ | |
| 580 | - if( nTty ){ | |
| 581 | - zSnip[k++] = 0x1b; | |
| 582 | - zSnip[k++] = '['; | |
| 583 | - if( nTty>=10 ) zSnip[k++] = (nTty/10)%10 + '0'; | |
| 584 | - zSnip[k++] = nTty%10 + '0'; | |
| 585 | - zSnip[k++] = 'm'; | |
| 586 | - } | |
| 587 | - j += 5; | |
| 588 | - }else if( memcmp(&zSnip[j],"</mark>",7)==0 ){ | |
| 589 | - if( nTty ){ | |
| 590 | - zSnip[k++] = 0x1b; | |
| 591 | - zSnip[k++] = '['; | |
| 592 | - zSnip[k++] = '0'; | |
| 593 | - zSnip[k++] = 'm'; | |
| 594 | - } | |
| 595 | - j += 6; | |
| 596 | - }else{ | |
| 597 | - zSnip[k++] = zSnip[j]; | |
| 598 | - } | |
| 599 | - }else if( fossil_isspace(c) ){ | |
| 600 | - zSnip[k++] = ' '; | |
| 601 | - while( fossil_isspace(zSnip[j+1]) ) j++; | |
| 602 | - }else if( c=='&' ){ | |
| 603 | - if( zSnip[j+1]=='#' && fossil_isdigit(zSnip[j+2]) ){ | |
| 604 | - int n = 3; | |
| 605 | - int x = zSnip[j+2] - '0'; | |
| 606 | - if( fossil_isdigit(zSnip[j+3]) ){ | |
| 607 | - x = x*10 + zSnip[j+3] - '0'; | |
| 608 | - n++; | |
| 609 | - if( fossil_isdigit(zSnip[j+4]) ){ | |
| 610 | - x = x*10 + zSnip[j+4] - '0'; | |
| 611 | - n++; | |
| 612 | - } | |
| 613 | - } | |
| 614 | - if( zSnip[j+n]==';' ){ | |
| 615 | - zSnip[k++] = (char)x; | |
| 616 | - j += n; | |
| 617 | - }else{ | |
| 618 | - zSnip[k++] = c; | |
| 619 | - } | |
| 620 | - }else if( memcmp(&zSnip[j],"<",4)==0 ){ | |
| 621 | - zSnip[k++] = '<'; | |
| 622 | - j += 3; | |
| 623 | - }else if( memcmp(&zSnip[j],">",4)==0 ){ | |
| 624 | - zSnip[k++] = '>'; | |
| 625 | - j += 3; | |
| 626 | - }else if( memcmp(&zSnip[j],""",6)==0 ){ | |
| 627 | - zSnip[k++] = '"'; | |
| 628 | - j += 5; | |
| 629 | - }else if( memcmp(&zSnip[j],"&",5)==0 ){ | |
| 630 | - zSnip[k++] = '&'; | |
| 631 | - j += 4; | |
| 632 | - }else{ | |
| 633 | - zSnip[k++] = c; | |
| 634 | - } | |
| 635 | - }else if( c=='%' && strncmp(&zSnip[j],"%fossil",7)==0 ){ | |
| 636 | - /* no-op */ | |
| 637 | - }else if( (c=='[' || c==']') && zSnip[j+1]==c ){ | |
| 638 | - j++; | |
| 639 | - }else{ | |
| 640 | - zSnip[k++] = c; | |
| 641 | - } | |
| 642 | - } | |
| 643 | - zSnip[k] = 0; | |
| 644 | - pSnip->nUsed = k; | |
| 645 | -} | |
| 646 | - | |
| 647 | 564 | /* |
| 648 | 565 | ** Testing the search function. |
| 649 | 566 | ** |
| 650 | 567 | ** COMMAND: search* |
| 651 | 568 | ** |
| @@ -702,18 +619,11 @@ | ||
| 702 | 619 | int nLimit = zLimit ? atoi(zLimit) : -1000; |
| 703 | 620 | int width; |
| 704 | 621 | int nTty = 0; /* VT100 highlight color for matching text */ |
| 705 | 622 | const char *zHighlight = 0; |
| 706 | 623 | |
| 707 | - /* Only colorize the output if talking to a tty and NO_COLOR does not | |
| 708 | - ** exist or is false. */ | |
| 709 | - if( fossil_isatty(1) ){ | |
| 710 | - char *zNoColor = fossil_getenv("NO_COLOR"); | |
| 711 | - if( zNoColor==0 || zNoColor[0]==0 || is_false(zNoColor) ){ | |
| 712 | - nTty = 91; | |
| 713 | - } | |
| 714 | - } | |
| 624 | + nTty = terminal_is_vt100(); | |
| 715 | 625 | |
| 716 | 626 | /* Undocumented option to change highlight color */ |
| 717 | 627 | zHighlight = find_option("highlight",0,1); |
| 718 | 628 | if( zHighlight ) nTty = atoi(zHighlight); |
| 719 | 629 | |
| @@ -805,19 +715,23 @@ | ||
| 805 | 715 | db_prepare(&q, "SELECT snip, label, score, id, date" |
| 806 | 716 | " FROM x" |
| 807 | 717 | " ORDER BY score DESC, date DESC;"); |
| 808 | 718 | blob_init(&com, 0, 0); |
| 809 | 719 | blob_init(&snip, 0, 0); |
| 810 | - if( width<0 ) width = 80; | |
| 720 | + if( width<0 ) width = terminal_get_width(80); | |
| 811 | 721 | while( db_step(&q)==SQLITE_ROW ){ |
| 812 | 722 | const char *zSnippet = db_column_text(&q, 0); |
| 813 | 723 | const char *zLabel = db_column_text(&q, 1); |
| 814 | 724 | const char *zDate = db_column_text(&q, 4); |
| 815 | 725 | const char *zScore = db_column_text(&q, 2); |
| 816 | 726 | const char *zId = db_column_text(&q, 3); |
| 727 | + char *zOrig; | |
| 817 | 728 | blob_appendf(&snip, "%s", zSnippet); |
| 818 | - search_snippet_to_plaintext(&snip, nTty); | |
| 729 | + zOrig = blob_materialize(&snip); | |
| 730 | + blob_init(&snip, 0, 0); | |
| 731 | + html_to_plaintext(zOrig, &snip, (nTty?HTOT_VT100:0)|HTOT_FLOW|HTOT_TRIM); | |
| 732 | + fossil_free(zOrig); | |
| 819 | 733 | blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate); |
| 820 | 734 | if( bDebug ){ |
| 821 | 735 | blob_appendf(&com," score: %s id: %s", zScore, zId); |
| 822 | 736 | } |
| 823 | 737 | comment_print(blob_str(&com), 0, 5, width, |
| @@ -1557,20 +1471,20 @@ | ||
| 1557 | 1471 | }else{ |
| 1558 | 1472 | blob_append(pOut, "\n", 1); |
| 1559 | 1473 | wiki_convert(pIn, &html, 0); |
| 1560 | 1474 | } |
| 1561 | 1475 | } |
| 1562 | - html_to_plaintext(blob_str(&html), pOut); | |
| 1476 | + html_to_plaintext(blob_str(&html), pOut, 0); | |
| 1563 | 1477 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1564 | 1478 | markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html); |
| 1565 | 1479 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 1566 | 1480 | if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title); |
| 1567 | 1481 | pHtml = pIn; |
| 1568 | 1482 | } |
| 1569 | 1483 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1570 | 1484 | if( blob_size(pHtml) ){ |
| 1571 | - html_to_plaintext(blob_str(pHtml), pOut); | |
| 1485 | + html_to_plaintext(blob_str(pHtml), pOut, 0); | |
| 1572 | 1486 | }else{ |
| 1573 | 1487 | blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); |
| 1574 | 1488 | } |
| 1575 | 1489 | blob_reset(&html); |
| 1576 | 1490 | blob_reset(&title); |
| 1577 | 1491 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -559,93 +559,10 @@ | |
| 559 | search_body_sqlfunc, 0, 0); |
| 560 | sqlite3_create_function(db, "urlencode", 1, enc, 0, |
| 561 | search_urlencode_sqlfunc, 0, 0); |
| 562 | } |
| 563 | |
| 564 | /* |
| 565 | ** The pSnip input contains snippet text from a search formatted |
| 566 | ** as HTML. Attempt to make that text more readable on a TTY. |
| 567 | ** |
| 568 | ** If nTty is positive, use ANSI escape codes "\e[Nm" where N is nTty |
| 569 | ** to highly marked text. |
| 570 | */ |
| 571 | void search_snippet_to_plaintext(Blob *pSnip, int nTty){ |
| 572 | char *zSnip; |
| 573 | unsigned int j, k; |
| 574 | |
| 575 | zSnip = pSnip->aData; |
| 576 | for(j=k=0; j<pSnip->nUsed; j++){ |
| 577 | char c = zSnip[j]; |
| 578 | if( c=='<' ){ |
| 579 | if( memcmp(&zSnip[j],"<mark>",6)==0 ){ |
| 580 | if( nTty ){ |
| 581 | zSnip[k++] = 0x1b; |
| 582 | zSnip[k++] = '['; |
| 583 | if( nTty>=10 ) zSnip[k++] = (nTty/10)%10 + '0'; |
| 584 | zSnip[k++] = nTty%10 + '0'; |
| 585 | zSnip[k++] = 'm'; |
| 586 | } |
| 587 | j += 5; |
| 588 | }else if( memcmp(&zSnip[j],"</mark>",7)==0 ){ |
| 589 | if( nTty ){ |
| 590 | zSnip[k++] = 0x1b; |
| 591 | zSnip[k++] = '['; |
| 592 | zSnip[k++] = '0'; |
| 593 | zSnip[k++] = 'm'; |
| 594 | } |
| 595 | j += 6; |
| 596 | }else{ |
| 597 | zSnip[k++] = zSnip[j]; |
| 598 | } |
| 599 | }else if( fossil_isspace(c) ){ |
| 600 | zSnip[k++] = ' '; |
| 601 | while( fossil_isspace(zSnip[j+1]) ) j++; |
| 602 | }else if( c=='&' ){ |
| 603 | if( zSnip[j+1]=='#' && fossil_isdigit(zSnip[j+2]) ){ |
| 604 | int n = 3; |
| 605 | int x = zSnip[j+2] - '0'; |
| 606 | if( fossil_isdigit(zSnip[j+3]) ){ |
| 607 | x = x*10 + zSnip[j+3] - '0'; |
| 608 | n++; |
| 609 | if( fossil_isdigit(zSnip[j+4]) ){ |
| 610 | x = x*10 + zSnip[j+4] - '0'; |
| 611 | n++; |
| 612 | } |
| 613 | } |
| 614 | if( zSnip[j+n]==';' ){ |
| 615 | zSnip[k++] = (char)x; |
| 616 | j += n; |
| 617 | }else{ |
| 618 | zSnip[k++] = c; |
| 619 | } |
| 620 | }else if( memcmp(&zSnip[j],"<",4)==0 ){ |
| 621 | zSnip[k++] = '<'; |
| 622 | j += 3; |
| 623 | }else if( memcmp(&zSnip[j],">",4)==0 ){ |
| 624 | zSnip[k++] = '>'; |
| 625 | j += 3; |
| 626 | }else if( memcmp(&zSnip[j],""",6)==0 ){ |
| 627 | zSnip[k++] = '"'; |
| 628 | j += 5; |
| 629 | }else if( memcmp(&zSnip[j],"&",5)==0 ){ |
| 630 | zSnip[k++] = '&'; |
| 631 | j += 4; |
| 632 | }else{ |
| 633 | zSnip[k++] = c; |
| 634 | } |
| 635 | }else if( c=='%' && strncmp(&zSnip[j],"%fossil",7)==0 ){ |
| 636 | /* no-op */ |
| 637 | }else if( (c=='[' || c==']') && zSnip[j+1]==c ){ |
| 638 | j++; |
| 639 | }else{ |
| 640 | zSnip[k++] = c; |
| 641 | } |
| 642 | } |
| 643 | zSnip[k] = 0; |
| 644 | pSnip->nUsed = k; |
| 645 | } |
| 646 | |
| 647 | /* |
| 648 | ** Testing the search function. |
| 649 | ** |
| 650 | ** COMMAND: search* |
| 651 | ** |
| @@ -702,18 +619,11 @@ | |
| 702 | int nLimit = zLimit ? atoi(zLimit) : -1000; |
| 703 | int width; |
| 704 | int nTty = 0; /* VT100 highlight color for matching text */ |
| 705 | const char *zHighlight = 0; |
| 706 | |
| 707 | /* Only colorize the output if talking to a tty and NO_COLOR does not |
| 708 | ** exist or is false. */ |
| 709 | if( fossil_isatty(1) ){ |
| 710 | char *zNoColor = fossil_getenv("NO_COLOR"); |
| 711 | if( zNoColor==0 || zNoColor[0]==0 || is_false(zNoColor) ){ |
| 712 | nTty = 91; |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | /* Undocumented option to change highlight color */ |
| 717 | zHighlight = find_option("highlight",0,1); |
| 718 | if( zHighlight ) nTty = atoi(zHighlight); |
| 719 | |
| @@ -805,19 +715,23 @@ | |
| 805 | db_prepare(&q, "SELECT snip, label, score, id, date" |
| 806 | " FROM x" |
| 807 | " ORDER BY score DESC, date DESC;"); |
| 808 | blob_init(&com, 0, 0); |
| 809 | blob_init(&snip, 0, 0); |
| 810 | if( width<0 ) width = 80; |
| 811 | while( db_step(&q)==SQLITE_ROW ){ |
| 812 | const char *zSnippet = db_column_text(&q, 0); |
| 813 | const char *zLabel = db_column_text(&q, 1); |
| 814 | const char *zDate = db_column_text(&q, 4); |
| 815 | const char *zScore = db_column_text(&q, 2); |
| 816 | const char *zId = db_column_text(&q, 3); |
| 817 | blob_appendf(&snip, "%s", zSnippet); |
| 818 | search_snippet_to_plaintext(&snip, nTty); |
| 819 | blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate); |
| 820 | if( bDebug ){ |
| 821 | blob_appendf(&com," score: %s id: %s", zScore, zId); |
| 822 | } |
| 823 | comment_print(blob_str(&com), 0, 5, width, |
| @@ -1557,20 +1471,20 @@ | |
| 1557 | }else{ |
| 1558 | blob_append(pOut, "\n", 1); |
| 1559 | wiki_convert(pIn, &html, 0); |
| 1560 | } |
| 1561 | } |
| 1562 | html_to_plaintext(blob_str(&html), pOut); |
| 1563 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1564 | markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html); |
| 1565 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 1566 | if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title); |
| 1567 | pHtml = pIn; |
| 1568 | } |
| 1569 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1570 | if( blob_size(pHtml) ){ |
| 1571 | html_to_plaintext(blob_str(pHtml), pOut); |
| 1572 | }else{ |
| 1573 | blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); |
| 1574 | } |
| 1575 | blob_reset(&html); |
| 1576 | blob_reset(&title); |
| 1577 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -559,93 +559,10 @@ | |
| 559 | search_body_sqlfunc, 0, 0); |
| 560 | sqlite3_create_function(db, "urlencode", 1, enc, 0, |
| 561 | search_urlencode_sqlfunc, 0, 0); |
| 562 | } |
| 563 | |
| 564 | /* |
| 565 | ** Testing the search function. |
| 566 | ** |
| 567 | ** COMMAND: search* |
| 568 | ** |
| @@ -702,18 +619,11 @@ | |
| 619 | int nLimit = zLimit ? atoi(zLimit) : -1000; |
| 620 | int width; |
| 621 | int nTty = 0; /* VT100 highlight color for matching text */ |
| 622 | const char *zHighlight = 0; |
| 623 | |
| 624 | nTty = terminal_is_vt100(); |
| 625 | |
| 626 | /* Undocumented option to change highlight color */ |
| 627 | zHighlight = find_option("highlight",0,1); |
| 628 | if( zHighlight ) nTty = atoi(zHighlight); |
| 629 | |
| @@ -805,19 +715,23 @@ | |
| 715 | db_prepare(&q, "SELECT snip, label, score, id, date" |
| 716 | " FROM x" |
| 717 | " ORDER BY score DESC, date DESC;"); |
| 718 | blob_init(&com, 0, 0); |
| 719 | blob_init(&snip, 0, 0); |
| 720 | if( width<0 ) width = terminal_get_width(80); |
| 721 | while( db_step(&q)==SQLITE_ROW ){ |
| 722 | const char *zSnippet = db_column_text(&q, 0); |
| 723 | const char *zLabel = db_column_text(&q, 1); |
| 724 | const char *zDate = db_column_text(&q, 4); |
| 725 | const char *zScore = db_column_text(&q, 2); |
| 726 | const char *zId = db_column_text(&q, 3); |
| 727 | char *zOrig; |
| 728 | blob_appendf(&snip, "%s", zSnippet); |
| 729 | zOrig = blob_materialize(&snip); |
| 730 | blob_init(&snip, 0, 0); |
| 731 | html_to_plaintext(zOrig, &snip, (nTty?HTOT_VT100:0)|HTOT_FLOW|HTOT_TRIM); |
| 732 | fossil_free(zOrig); |
| 733 | blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate); |
| 734 | if( bDebug ){ |
| 735 | blob_appendf(&com," score: %s id: %s", zScore, zId); |
| 736 | } |
| 737 | comment_print(blob_str(&com), 0, 5, width, |
| @@ -1557,20 +1471,20 @@ | |
| 1471 | }else{ |
| 1472 | blob_append(pOut, "\n", 1); |
| 1473 | wiki_convert(pIn, &html, 0); |
| 1474 | } |
| 1475 | } |
| 1476 | html_to_plaintext(blob_str(&html), pOut, 0); |
| 1477 | }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ |
| 1478 | markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html); |
| 1479 | }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ |
| 1480 | if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title); |
| 1481 | pHtml = pIn; |
| 1482 | } |
| 1483 | blob_appendf(pOut, "%s\n", blob_str(&title)); |
| 1484 | if( blob_size(pHtml) ){ |
| 1485 | html_to_plaintext(blob_str(pHtml), pOut, 0); |
| 1486 | }else{ |
| 1487 | blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); |
| 1488 | } |
| 1489 | blob_reset(&html); |
| 1490 | blob_reset(&title); |
| 1491 |
+20
-9
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -814,10 +814,11 @@ | ||
| 814 | 814 | ** |
| 815 | 815 | ** y=0x01 Show only hack attempts |
| 816 | 816 | ** y=0x02 Show only panics and assertion faults |
| 817 | 817 | ** y=0x04 Show hung backoffice processes |
| 818 | 818 | ** y=0x08 Show POST requests from a different origin |
| 819 | +** y=0x10 Show SQLITE_AUTH and similar | |
| 819 | 820 | ** y=0x40 Show other uncategorized messages |
| 820 | 821 | ** |
| 821 | 822 | ** If y is omitted or is zero, a count of the various message types is |
| 822 | 823 | ** shown. |
| 823 | 824 | */ |
| @@ -824,19 +825,20 @@ | ||
| 824 | 825 | void errorlog_page(void){ |
| 825 | 826 | i64 szFile; |
| 826 | 827 | FILE *in; |
| 827 | 828 | char *zLog; |
| 828 | 829 | const char *zType = P("y"); |
| 829 | - static const int eAllTypes = 0x4f; | |
| 830 | + static const int eAllTypes = 0x5f; | |
| 830 | 831 | long eType = 0; |
| 831 | 832 | int bOutput = 0; |
| 832 | 833 | int prevWasTime = 0; |
| 833 | 834 | int nHack = 0; |
| 834 | 835 | int nPanic = 0; |
| 835 | 836 | int nOther = 0; |
| 836 | 837 | int nHang = 0; |
| 837 | 838 | int nXPost = 0; |
| 839 | + int nAuth = 0; | |
| 838 | 840 | char z[10000]; |
| 839 | 841 | char zTime[10000]; |
| 840 | 842 | |
| 841 | 843 | login_check_credentials(); |
| 842 | 844 | if( !g.perm.Admin ){ |
| @@ -906,10 +908,13 @@ | ||
| 906 | 908 | @ <li>Hung backoffice processes |
| 907 | 909 | } |
| 908 | 910 | if( eType & 0x08 ){ |
| 909 | 911 | @ <li>POST requests from different origin |
| 910 | 912 | } |
| 913 | + if( eType & 0x10 ){ | |
| 914 | + @ <li>SQLITE_AUTH and similar errors | |
| 915 | + } | |
| 911 | 916 | if( eType & 0x40 ){ |
| 912 | 917 | @ <li>Other uncategorized messages |
| 913 | 918 | } |
| 914 | 919 | @ </ul> |
| 915 | 920 | } |
| @@ -933,10 +938,16 @@ | ||
| 933 | 938 | }else |
| 934 | 939 | if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){ |
| 935 | 940 | bOutput = (eType & 0x08)!=0; |
| 936 | 941 | nXPost++; |
| 937 | 942 | }else |
| 943 | + if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0 | |
| 944 | + || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 | |
| 945 | + ){ | |
| 946 | + bOutput = (eType & 0x10)!=0; | |
| 947 | + nAuth++; | |
| 948 | + }else | |
| 938 | 949 | { |
| 939 | 950 | bOutput = (eType & 0x40)!=0; |
| 940 | 951 | nOther++; |
| 941 | 952 | } |
| 942 | 953 | if( bOutput ){ |
| @@ -958,11 +969,11 @@ | ||
| 958 | 969 | fclose(in); |
| 959 | 970 | if( eType ){ |
| 960 | 971 | @ </pre> |
| 961 | 972 | } |
| 962 | 973 | if( eType==0 ){ |
| 963 | - int nNonHack = nPanic + nHang + nOther; | |
| 974 | + int nNonHack = nPanic + nHang + nAuth + nOther; | |
| 964 | 975 | int nTotal = nNonHack + nHack + nXPost; |
| 965 | 976 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 966 | 977 | if( nPanic>0 ){ |
| 967 | 978 | @ <tr><td align="right">%d(nPanic)</td> |
| 968 | 979 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| @@ -971,23 +982,23 @@ | ||
| 971 | 982 | @ <tr><td align="right">%d(nHack)</td> |
| 972 | 983 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 973 | 984 | } |
| 974 | 985 | if( nHang>0 ){ |
| 975 | 986 | @ <tr><td align="right">%d(nHang)</td> |
| 976 | - @ <td><a href="./errorlog?y=4/">Hung Backoffice</a></td> | |
| 987 | + @ <td><a href="./errorlog?y=4">Hung Backoffice</a></td> | |
| 977 | 988 | } |
| 978 | 989 | if( nXPost>0 ){ |
| 979 | 990 | @ <tr><td align="right">%d(nXPost)</td> |
| 980 | - @ <td><a href="./errorlog?y=8/">POSTs from different origin</a></td> | |
| 991 | + @ <td><a href="./errorlog?y=8">POSTs from different origin</a></td> | |
| 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> | |
| 981 | 996 | } |
| 982 | 997 | if( nOther>0 ){ |
| 983 | 998 | @ <tr><td align="right">%d(nOther)</td> |
| 984 | - @ <td><a href="./errorlog?y=64/">Other</a></td> | |
| 985 | - } | |
| 986 | - if( nHack+nXPost>0 && nNonHack>0 ){ | |
| 987 | - @ <tr><td align="right">%d(nNonHack)</td> | |
| 988 | - @ <td><a href="%R/errorlog?y=70">Other than hack attempts</a></td> | |
| 999 | + @ <td><a href="./errorlog?y=64">Other</a></td> | |
| 989 | 1000 | } |
| 990 | 1001 | @ <tr><td align="right">%d(nTotal)</td> |
| 991 | 1002 | if( nTotal>0 ){ |
| 992 | 1003 | @ <td><a href="./errorlog?y=255">All Messages</a></td> |
| 993 | 1004 | }else{ |
| 994 | 1005 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -814,10 +814,11 @@ | |
| 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=0x40 Show other uncategorized messages |
| 820 | ** |
| 821 | ** If y is omitted or is zero, a count of the various message types is |
| 822 | ** shown. |
| 823 | */ |
| @@ -824,19 +825,20 @@ | |
| 824 | void errorlog_page(void){ |
| 825 | i64 szFile; |
| 826 | FILE *in; |
| 827 | char *zLog; |
| 828 | const char *zType = P("y"); |
| 829 | static const int eAllTypes = 0x4f; |
| 830 | long eType = 0; |
| 831 | int bOutput = 0; |
| 832 | int prevWasTime = 0; |
| 833 | int nHack = 0; |
| 834 | int nPanic = 0; |
| 835 | int nOther = 0; |
| 836 | int nHang = 0; |
| 837 | int nXPost = 0; |
| 838 | char z[10000]; |
| 839 | char zTime[10000]; |
| 840 | |
| 841 | login_check_credentials(); |
| 842 | if( !g.perm.Admin ){ |
| @@ -906,10 +908,13 @@ | |
| 906 | @ <li>Hung backoffice processes |
| 907 | } |
| 908 | if( eType & 0x08 ){ |
| 909 | @ <li>POST requests from different origin |
| 910 | } |
| 911 | if( eType & 0x40 ){ |
| 912 | @ <li>Other uncategorized messages |
| 913 | } |
| 914 | @ </ul> |
| 915 | } |
| @@ -933,10 +938,16 @@ | |
| 933 | }else |
| 934 | if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){ |
| 935 | bOutput = (eType & 0x08)!=0; |
| 936 | nXPost++; |
| 937 | }else |
| 938 | { |
| 939 | bOutput = (eType & 0x40)!=0; |
| 940 | nOther++; |
| 941 | } |
| 942 | if( bOutput ){ |
| @@ -958,11 +969,11 @@ | |
| 958 | fclose(in); |
| 959 | if( eType ){ |
| 960 | @ </pre> |
| 961 | } |
| 962 | if( eType==0 ){ |
| 963 | int nNonHack = nPanic + nHang + nOther; |
| 964 | int nTotal = nNonHack + nHack + nXPost; |
| 965 | @ <p><table border="a" cellspacing="0" cellpadding="5"> |
| 966 | if( nPanic>0 ){ |
| 967 | @ <tr><td align="right">%d(nPanic)</td> |
| 968 | @ <td><a href="./errorlog?y=2">Panics</a></td> |
| @@ -971,23 +982,23 @@ | |
| 971 | @ <tr><td align="right">%d(nHack)</td> |
| 972 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 973 | } |
| 974 | if( nHang>0 ){ |
| 975 | @ <tr><td align="right">%d(nHang)</td> |
| 976 | @ <td><a href="./errorlog?y=4/">Hung Backoffice</a></td> |
| 977 | } |
| 978 | if( nXPost>0 ){ |
| 979 | @ <tr><td align="right">%d(nXPost)</td> |
| 980 | @ <td><a href="./errorlog?y=8/">POSTs from different origin</a></td> |
| 981 | } |
| 982 | if( nOther>0 ){ |
| 983 | @ <tr><td align="right">%d(nOther)</td> |
| 984 | @ <td><a href="./errorlog?y=64/">Other</a></td> |
| 985 | } |
| 986 | if( nHack+nXPost>0 && nNonHack>0 ){ |
| 987 | @ <tr><td align="right">%d(nNonHack)</td> |
| 988 | @ <td><a href="%R/errorlog?y=70">Other than hack attempts</a></td> |
| 989 | } |
| 990 | @ <tr><td align="right">%d(nTotal)</td> |
| 991 | if( nTotal>0 ){ |
| 992 | @ <td><a href="./errorlog?y=255">All Messages</a></td> |
| 993 | }else{ |
| 994 |
| --- src/security_audit.c | |
| +++ src/security_audit.c | |
| @@ -814,10 +814,11 @@ | |
| 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 | */ |
| @@ -824,19 +825,20 @@ | |
| 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 ){ |
| @@ -906,10 +908,13 @@ | |
| 908 | @ <li>Hung backoffice processes |
| 909 | } |
| 910 | if( eType & 0x08 ){ |
| 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 | } |
| @@ -933,10 +938,16 @@ | |
| 938 | }else |
| 939 | if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){ |
| 940 | bOutput = (eType & 0x08)!=0; |
| 941 | nXPost++; |
| 942 | }else |
| 943 | if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0 |
| 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 ){ |
| @@ -958,11 +969,11 @@ | |
| 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> |
| @@ -971,23 +982,23 @@ | |
| 982 | @ <tr><td align="right">%d(nHack)</td> |
| 983 | @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> |
| 984 | } |
| 985 | if( nHang>0 ){ |
| 986 | @ <tr><td align="right">%d(nHang)</td> |
| 987 | @ <td><a href="./errorlog?y=4">Hung Backoffice</a></td> |
| 988 | } |
| 989 | if( nXPost>0 ){ |
| 990 | @ <tr><td align="right">%d(nXPost)</td> |
| 991 | @ <td><a href="./errorlog?y=8">POSTs from different origin</a></td> |
| 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 |
-7
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -981,17 +981,10 @@ | ||
| 981 | 981 | db_begin_transaction(); |
| 982 | 982 | @ <form action="%R/setup_timeline" method="post"><div> |
| 983 | 983 | login_insert_csrf_secret(); |
| 984 | 984 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 985 | 985 | |
| 986 | - @ <hr> | |
| 987 | - onoff_attribute("Allow block-markup in timeline", | |
| 988 | - "timeline-block-markup", "tbm", 0, 0); | |
| 989 | - @ <p>In timeline displays, check-in comments can be displayed with or | |
| 990 | - @ without block markup such as paragraphs, tables, etc. | |
| 991 | - @ (Property: "timeline-block-markup")</p> | |
| 992 | - | |
| 993 | 986 | @ <hr> |
| 994 | 987 | onoff_attribute("Plaintext comments on timelines", |
| 995 | 988 | "timeline-plaintext", "tpt", 0, 0); |
| 996 | 989 | @ <p>In timeline displays, check-in comments are displayed literally, |
| 997 | 990 | @ without any wiki or HTML interpretation. Use CSS to change |
| 998 | 991 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -981,17 +981,10 @@ | |
| 981 | db_begin_transaction(); |
| 982 | @ <form action="%R/setup_timeline" method="post"><div> |
| 983 | login_insert_csrf_secret(); |
| 984 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 985 | |
| 986 | @ <hr> |
| 987 | onoff_attribute("Allow block-markup in timeline", |
| 988 | "timeline-block-markup", "tbm", 0, 0); |
| 989 | @ <p>In timeline displays, check-in comments can be displayed with or |
| 990 | @ without block markup such as paragraphs, tables, etc. |
| 991 | @ (Property: "timeline-block-markup")</p> |
| 992 | |
| 993 | @ <hr> |
| 994 | onoff_attribute("Plaintext comments on timelines", |
| 995 | "timeline-plaintext", "tpt", 0, 0); |
| 996 | @ <p>In timeline displays, check-in comments are displayed literally, |
| 997 | @ without any wiki or HTML interpretation. Use CSS to change |
| 998 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -981,17 +981,10 @@ | |
| 981 | db_begin_transaction(); |
| 982 | @ <form action="%R/setup_timeline" method="post"><div> |
| 983 | login_insert_csrf_secret(); |
| 984 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 985 | |
| 986 | @ <hr> |
| 987 | onoff_attribute("Plaintext comments on timelines", |
| 988 | "timeline-plaintext", "tpt", 0, 0); |
| 989 | @ <p>In timeline displays, check-in comments are displayed literally, |
| 990 | @ without any wiki or HTML interpretation. Use CSS to change |
| 991 |
+1
| --- src/sitemap.c | ||
| +++ src/sitemap.c | ||
| @@ -293,10 +293,11 @@ | ||
| 293 | 293 | @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li> |
| 294 | 294 | } |
| 295 | 295 | @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li> |
| 296 | 296 | @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li> |
| 297 | 297 | @ <li>%z(href("%R/hash-color-test"))Hash color test</a> |
| 298 | + @ <li>%z(href("%R/test-bgcolor"))Background color test</a> | |
| 298 | 299 | if( g.perm.Admin ){ |
| 299 | 300 | @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li> |
| 300 | 301 | @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li> |
| 301 | 302 | @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li> |
| 302 | 303 | @ <li>%z(href("%R/test-warning"))Error Log test page</a></li> |
| 303 | 304 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -293,10 +293,11 @@ | |
| 293 | @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li> |
| 294 | } |
| 295 | @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li> |
| 296 | @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li> |
| 297 | @ <li>%z(href("%R/hash-color-test"))Hash color test</a> |
| 298 | if( g.perm.Admin ){ |
| 299 | @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li> |
| 300 | @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li> |
| 301 | @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li> |
| 302 | @ <li>%z(href("%R/test-warning"))Error Log test page</a></li> |
| 303 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -293,10 +293,11 @@ | |
| 293 | @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li> |
| 294 | } |
| 295 | @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li> |
| 296 | @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li> |
| 297 | @ <li>%z(href("%R/hash-color-test"))Hash color test</a> |
| 298 | @ <li>%z(href("%R/test-bgcolor"))Background color test</a> |
| 299 | if( g.perm.Admin ){ |
| 300 | @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li> |
| 301 | @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li> |
| 302 | @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li> |
| 303 | @ <li>%z(href("%R/test-warning"))Error Log test page</a></li> |
| 304 |
+5
-3
| --- src/statrep.c | ||
| +++ src/statrep.c | ||
| @@ -318,15 +318,16 @@ | ||
| 318 | 318 | && nMaxEvents>0 |
| 319 | 319 | ){ |
| 320 | 320 | /* If the timespan covered by this row contains "now", then project |
| 321 | 321 | ** the number of changes until the completion of the timespan and |
| 322 | 322 | ** show a dashed box of that projection. */ |
| 323 | + int nProj = (int)(((double)nCount)/rNowFraction); | |
| 323 | 324 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 324 | 325 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 325 | 326 | @ <span class='statistics-report-graph-line' \ |
| 326 | 327 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 327 | - @ <span class='statistics-report-graph-extra' \ | |
| 328 | + @ <span class='statistics-report-graph-extra' title='%d(nProj)' \ | |
| 328 | 329 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 329 | 330 | }else{ |
| 330 | 331 | @ <div class='statistics-report-graph-line' \ |
| 331 | 332 | @ style='width:%d(nSize)%%;'> </div> \ |
| 332 | 333 | } |
| @@ -747,18 +748,19 @@ | ||
| 747 | 748 | if( zCurrentWeek!=0 |
| 748 | 749 | && strcmp(zWeek, zCurrentWeek)==0 |
| 749 | 750 | && rNowFraction>0.05 |
| 750 | 751 | && nMaxEvents>0 |
| 751 | 752 | ){ |
| 752 | - /* If the covered covered by this row contains "now", then project | |
| 753 | + /* If the timespan covered by this row contains "now", then project | |
| 753 | 754 | ** the number of changes until the completion of the week and |
| 754 | 755 | ** show a dashed box of that projection. */ |
| 756 | + int nProj = (int)(((double)nCount)/rNowFraction); | |
| 755 | 757 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 756 | 758 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 757 | 759 | @ <span class='statistics-report-graph-line' \ |
| 758 | 760 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 759 | - @ <span class='statistics-report-graph-extra' \ | |
| 761 | + @ <span class='statistics-report-graph-extra' title='%d(nProj)' \ | |
| 760 | 762 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 761 | 763 | }else{ |
| 762 | 764 | @ <div class='statistics-report-graph-line' \ |
| 763 | 765 | @ style='width:%d(nSize)%%;'> </div> \ |
| 764 | 766 | } |
| 765 | 767 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -318,15 +318,16 @@ | |
| 318 | && nMaxEvents>0 |
| 319 | ){ |
| 320 | /* If the timespan covered by this row contains "now", then project |
| 321 | ** the number of changes until the completion of the timespan and |
| 322 | ** show a dashed box of that projection. */ |
| 323 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 324 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 325 | @ <span class='statistics-report-graph-line' \ |
| 326 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 327 | @ <span class='statistics-report-graph-extra' \ |
| 328 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 329 | }else{ |
| 330 | @ <div class='statistics-report-graph-line' \ |
| 331 | @ style='width:%d(nSize)%%;'> </div> \ |
| 332 | } |
| @@ -747,18 +748,19 @@ | |
| 747 | if( zCurrentWeek!=0 |
| 748 | && strcmp(zWeek, zCurrentWeek)==0 |
| 749 | && rNowFraction>0.05 |
| 750 | && nMaxEvents>0 |
| 751 | ){ |
| 752 | /* If the covered covered by this row contains "now", then project |
| 753 | ** the number of changes until the completion of the week and |
| 754 | ** show a dashed box of that projection. */ |
| 755 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 756 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 757 | @ <span class='statistics-report-graph-line' \ |
| 758 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 759 | @ <span class='statistics-report-graph-extra' \ |
| 760 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 761 | }else{ |
| 762 | @ <div class='statistics-report-graph-line' \ |
| 763 | @ style='width:%d(nSize)%%;'> </div> \ |
| 764 | } |
| 765 |
| --- src/statrep.c | |
| +++ src/statrep.c | |
| @@ -318,15 +318,16 @@ | |
| 318 | && nMaxEvents>0 |
| 319 | ){ |
| 320 | /* If the timespan covered by this row contains "now", then project |
| 321 | ** the number of changes until the completion of the timespan and |
| 322 | ** show a dashed box of that projection. */ |
| 323 | int nProj = (int)(((double)nCount)/rNowFraction); |
| 324 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 325 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 326 | @ <span class='statistics-report-graph-line' \ |
| 327 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 328 | @ <span class='statistics-report-graph-extra' title='%d(nProj)' \ |
| 329 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 330 | }else{ |
| 331 | @ <div class='statistics-report-graph-line' \ |
| 332 | @ style='width:%d(nSize)%%;'> </div> \ |
| 333 | } |
| @@ -747,18 +748,19 @@ | |
| 748 | if( zCurrentWeek!=0 |
| 749 | && strcmp(zWeek, zCurrentWeek)==0 |
| 750 | && rNowFraction>0.05 |
| 751 | && nMaxEvents>0 |
| 752 | ){ |
| 753 | /* If the timespan covered by this row contains "now", then project |
| 754 | ** the number of changes until the completion of the week and |
| 755 | ** show a dashed box of that projection. */ |
| 756 | int nProj = (int)(((double)nCount)/rNowFraction); |
| 757 | int nExtra = (int)(((double)nCount)/rNowFraction) - nCount; |
| 758 | int nXSize = (100 * nExtra)/nMaxEvents; |
| 759 | @ <span class='statistics-report-graph-line' \ |
| 760 | @ style='display:inline-block;min-width:%d(nSize)%%;'> </span>\ |
| 761 | @ <span class='statistics-report-graph-extra' title='%d(nProj)' \ |
| 762 | @ style='display:inline-block;min-width:%d(nXSize)%%;'> </span>\ |
| 763 | }else{ |
| 764 | @ <div class='statistics-report-graph-line' \ |
| 765 | @ style='width:%d(nSize)%%;'> </div> \ |
| 766 | } |
| 767 |
+52
| --- src/terminal.c | ||
| +++ src/terminal.c | ||
| @@ -138,5 +138,57 @@ | ||
| 138 | 138 | void test_terminal_size_cmd(void){ |
| 139 | 139 | TerminalSize ts; |
| 140 | 140 | terminal_get_size(&ts); |
| 141 | 141 | fossil_print("%d %d\n", ts.nColumns, ts.nLines); |
| 142 | 142 | } |
| 143 | + | |
| 144 | +/* | |
| 145 | +** Return true if it is reasonable is emit VT100 escape codes. | |
| 146 | +*/ | |
| 147 | +int terminal_is_vt100(void){ | |
| 148 | + char *zNoColor; | |
| 149 | +#ifdef _WIN32 | |
| 150 | + if( !win32_terminal_is_vt100(1) ) return 0; | |
| 151 | +#endif /* _WIN32 */ | |
| 152 | + if( !fossil_isatty(1) ) return 0; | |
| 153 | + zNoColor =fossil_getenv("NO_COLOR"); | |
| 154 | + if( zNoColor==0 ) return 1; | |
| 155 | + if( zNoColor[0]==0 ) return 1; | |
| 156 | + if( is_false(zNoColor) ) return 1; | |
| 157 | + return 0; | |
| 158 | +} | |
| 159 | + | |
| 160 | +#ifdef _WIN32 | |
| 161 | +/* | |
| 162 | +** Return true if the Windows console supports VT100 escape codes. | |
| 163 | +** | |
| 164 | +** Support for VT100 escape codes is enabled by default in Windows Terminal | |
| 165 | +** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles | |
| 166 | +** and on older versions of Windows. Programs can turn on VT100 support for | |
| 167 | +** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag. | |
| 168 | +** | |
| 169 | +** NOTE: If this function needs to be called in more complex scenarios with | |
| 170 | +** reassigned stdout and stderr streams, the following CRT calls are useful | |
| 171 | +** to translate from CRT streams to file descriptors and to Win32 handles: | |
| 172 | +** | |
| 173 | +** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>)); | |
| 174 | +*/ | |
| 175 | +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING | |
| 176 | +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 | |
| 177 | +#endif | |
| 178 | +int win32_terminal_is_vt100(int fd){ | |
| 179 | + HANDLE hConsole = NULL; | |
| 180 | + DWORD dwConsoleMode; | |
| 181 | + switch( fd ){ | |
| 182 | + case 1: | |
| 183 | + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); | |
| 184 | + break; | |
| 185 | + case 2: | |
| 186 | + hConsole = GetStdHandle(STD_ERROR_HANDLE); | |
| 187 | + break; | |
| 188 | + } | |
| 189 | + if( GetConsoleMode(hConsole,&dwConsoleMode) ){ | |
| 190 | + return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0; | |
| 191 | + } | |
| 192 | + return 0; | |
| 193 | +} | |
| 194 | +#endif /* _WIN32 */ | |
| 143 | 195 |
| --- src/terminal.c | |
| +++ src/terminal.c | |
| @@ -138,5 +138,57 @@ | |
| 138 | void test_terminal_size_cmd(void){ |
| 139 | TerminalSize ts; |
| 140 | terminal_get_size(&ts); |
| 141 | fossil_print("%d %d\n", ts.nColumns, ts.nLines); |
| 142 | } |
| 143 |
| --- src/terminal.c | |
| +++ src/terminal.c | |
| @@ -138,5 +138,57 @@ | |
| 138 | void test_terminal_size_cmd(void){ |
| 139 | TerminalSize ts; |
| 140 | terminal_get_size(&ts); |
| 141 | fossil_print("%d %d\n", ts.nColumns, ts.nLines); |
| 142 | } |
| 143 | |
| 144 | /* |
| 145 | ** Return true if it is reasonable is emit VT100 escape codes. |
| 146 | */ |
| 147 | int terminal_is_vt100(void){ |
| 148 | char *zNoColor; |
| 149 | #ifdef _WIN32 |
| 150 | if( !win32_terminal_is_vt100(1) ) return 0; |
| 151 | #endif /* _WIN32 */ |
| 152 | if( !fossil_isatty(1) ) return 0; |
| 153 | zNoColor =fossil_getenv("NO_COLOR"); |
| 154 | if( zNoColor==0 ) return 1; |
| 155 | if( zNoColor[0]==0 ) return 1; |
| 156 | if( is_false(zNoColor) ) return 1; |
| 157 | return 0; |
| 158 | } |
| 159 | |
| 160 | #ifdef _WIN32 |
| 161 | /* |
| 162 | ** Return true if the Windows console supports VT100 escape codes. |
| 163 | ** |
| 164 | ** Support for VT100 escape codes is enabled by default in Windows Terminal |
| 165 | ** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles |
| 166 | ** and on older versions of Windows. Programs can turn on VT100 support for |
| 167 | ** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag. |
| 168 | ** |
| 169 | ** NOTE: If this function needs to be called in more complex scenarios with |
| 170 | ** reassigned stdout and stderr streams, the following CRT calls are useful |
| 171 | ** to translate from CRT streams to file descriptors and to Win32 handles: |
| 172 | ** |
| 173 | ** HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>)); |
| 174 | */ |
| 175 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING |
| 176 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 |
| 177 | #endif |
| 178 | int win32_terminal_is_vt100(int fd){ |
| 179 | HANDLE hConsole = NULL; |
| 180 | DWORD dwConsoleMode; |
| 181 | switch( fd ){ |
| 182 | case 1: |
| 183 | hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
| 184 | break; |
| 185 | case 2: |
| 186 | hConsole = GetStdHandle(STD_ERROR_HANDLE); |
| 187 | break; |
| 188 | } |
| 189 | if( GetConsoleMode(hConsole,&dwConsoleMode) ){ |
| 190 | return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0; |
| 191 | } |
| 192 | return 0; |
| 193 | } |
| 194 | #endif /* _WIN32 */ |
| 195 |
+29
-72
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -221,28 +221,28 @@ | ||
| 221 | 221 | vid = db_lget_int("checkout", 0); |
| 222 | 222 | } |
| 223 | 223 | zPrevDate[0] = 0; |
| 224 | 224 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 225 | 225 | dateFormat = db_get_int("timeline-date-format", 0); |
| 226 | - /* | |
| 227 | - ** SETTING: timeline-truncate-at-blank boolean default=off | |
| 228 | - ** | |
| 229 | - ** If enabled, check-in comments displayed on the timeline are truncated | |
| 230 | - ** at the first blank line of the comment text. The comment text after | |
| 231 | - ** the first blank line is only seen in the /info or similar pages that | |
| 232 | - ** show details about the check-in. | |
| 233 | - */ | |
| 226 | +/* | |
| 227 | +** SETTING: timeline-truncate-at-blank boolean default=off | |
| 228 | +** | |
| 229 | +** If enabled, check-in comments displayed on the timeline are truncated | |
| 230 | +** at the first blank line of the comment text. The comment text after | |
| 231 | +** the first blank line is only seen in the /info or similar pages that | |
| 232 | +** show details about the check-in. | |
| 233 | +*/ | |
| 234 | 234 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 235 | - /* | |
| 236 | - ** SETTING: timeline-tslink-info boolean default=off | |
| 237 | - ** | |
| 238 | - ** The hyperlink on the timestamp associated with each timeline entry, | |
| 239 | - ** on the far left-hand side of the screen, normally targets another | |
| 240 | - ** /timeline page that shows the entry in context. However, if this | |
| 241 | - ** option is turned on, that hyperlink targets the /info page showing | |
| 242 | - ** the details of the entry. | |
| 243 | - */ | |
| 235 | +/* | |
| 236 | +** SETTING: timeline-tslink-info boolean default=off | |
| 237 | +** | |
| 238 | +** The hyperlink on the timestamp associated with each timeline entry, | |
| 239 | +** on the far left-hand side of the screen, normally targets another | |
| 240 | +** /timeline page that shows the entry in context. However, if this | |
| 241 | +** option is turned on, that hyperlink targets the /info page showing | |
| 242 | +** the details of the entry. | |
| 243 | +*/ | |
| 244 | 244 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| 245 | 245 | if( (tmFlags & TIMELINE_VIEWS)==0 ){ |
| 246 | 246 | tmFlags |= timeline_ss_cookie(); |
| 247 | 247 | } |
| 248 | 248 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| @@ -402,10 +402,12 @@ | ||
| 402 | 402 | zDateLink = mprintf("<a>"); |
| 403 | 403 | } |
| 404 | 404 | @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td> |
| 405 | 405 | @ <td class="timelineGraph"> |
| 406 | 406 | if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 407 | + /* Don't use the requested background color. Use the background color | |
| 408 | + ** override from query parameters instead. */ | |
| 407 | 409 | if( tmFlags & TIMELINE_UCOLOR ){ |
| 408 | 410 | zBgClr = zUser ? user_color(zUser) : 0; |
| 409 | 411 | }else if( tmFlags & TIMELINE_NOCOLOR ){ |
| 410 | 412 | zBgClr = 0; |
| 411 | 413 | }else if( zType[0]=='c' ){ |
| @@ -420,16 +422,21 @@ | ||
| 420 | 422 | }else{ |
| 421 | 423 | zBgClr = hash_color("f"); /* delta manifest */ |
| 422 | 424 | } |
| 423 | 425 | db_reset(&qdelta); |
| 424 | 426 | } |
| 427 | + }else{ | |
| 428 | + /* Make sure the user-specified background color is reasonable */ | |
| 429 | + zBgClr = reasonable_bg_color(zBgClr, 0); | |
| 425 | 430 | } |
| 426 | 431 | if( zType[0]=='c' |
| 427 | 432 | && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0) |
| 428 | 433 | ){ |
| 429 | 434 | zBr = branch_of_rid(rid); |
| 430 | 435 | if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){ |
| 436 | + /* If no background color is specified, use a color based on the | |
| 437 | + ** branch name */ | |
| 431 | 438 | if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 432 | 439 | }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){ |
| 433 | 440 | zBgClr = 0; |
| 434 | 441 | }else{ |
| 435 | 442 | zBgClr = hash_color(zBr); |
| @@ -1106,62 +1113,10 @@ | ||
| 1106 | 1113 | @ WHERE blob.rid=event.objid |
| 1107 | 1114 | ; |
| 1108 | 1115 | return zBase; |
| 1109 | 1116 | } |
| 1110 | 1117 | |
| 1111 | -/* | |
| 1112 | -** Convert a symbolic name used as an argument to the a=, b=, or c= | |
| 1113 | -** query parameters of timeline into a julianday mtime value. | |
| 1114 | -** | |
| 1115 | -** If pzDisplay is not null, then display text for the symbolic name might | |
| 1116 | -** be written into *pzDisplay. But that is not guaranteed. | |
| 1117 | -** | |
| 1118 | -** If bRoundUp is true and the symbolic name is a timestamp with less | |
| 1119 | -** than millisecond resolution, then the timestamp is rounding up to the | |
| 1120 | -** largest millisecond consistent with that timestamp. If bRoundUp is | |
| 1121 | -** false, then the resulting time is obtained by extending the timestamp | |
| 1122 | -** with zeros (hence rounding down). Use bRoundUp==1 if the result | |
| 1123 | -** will be used in mtime<=$RESULT and use bRoundUp==0 if the result | |
| 1124 | -** will be used in mtime>=$RESULT. | |
| 1125 | -*/ | |
| 1126 | -double symbolic_name_to_mtime( | |
| 1127 | - const char *z, /* Input symbolic name */ | |
| 1128 | - const char **pzDisplay, /* Perhaps write display text here, if not NULL */ | |
| 1129 | - int bRoundUp /* Round up if true */ | |
| 1130 | -){ | |
| 1131 | - double mtime; | |
| 1132 | - int rid; | |
| 1133 | - const char *zDate; | |
| 1134 | - if( z==0 ) return -1.0; | |
| 1135 | - if( fossil_isdate(z) ){ | |
| 1136 | - mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z); | |
| 1137 | - if( mtime>0.0 ) return mtime; | |
| 1138 | - } | |
| 1139 | - zDate = fossil_expand_datetime(z, 1, bRoundUp); | |
| 1140 | - if( zDate!=0 ){ | |
| 1141 | - mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", | |
| 1142 | - bRoundUp ? fossil_roundup_date(zDate) : zDate); | |
| 1143 | - if( mtime>0.0 ){ | |
| 1144 | - if( pzDisplay ) *pzDisplay = fossil_strdup(zDate); | |
| 1145 | - return mtime; | |
| 1146 | - } | |
| 1147 | - } | |
| 1148 | - rid = symbolic_name_to_rid(z, "*"); | |
| 1149 | - if( rid ){ | |
| 1150 | - mtime = mtime_of_rid(rid, 0.0); | |
| 1151 | - }else{ | |
| 1152 | - mtime = db_double(-1.0, | |
| 1153 | - "SELECT max(event.mtime) FROM event, tag, tagxref" | |
| 1154 | - " WHERE tag.tagname GLOB 'event-%q*'" | |
| 1155 | - " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" | |
| 1156 | - " AND event.objid=tagxref.rid", | |
| 1157 | - z | |
| 1158 | - ); | |
| 1159 | - } | |
| 1160 | - return mtime; | |
| 1161 | -} | |
| 1162 | - | |
| 1163 | 1118 | /* |
| 1164 | 1119 | ** zDate is a localtime date. Insert records into the |
| 1165 | 1120 | ** "timeline" table to cause <hr> to be inserted on zDate. |
| 1166 | 1121 | */ |
| 1167 | 1122 | static int timeline_add_divider(double rDate){ |
| @@ -3471,11 +3426,13 @@ | ||
| 3471 | 3426 | } |
| 3472 | 3427 | |
| 3473 | 3428 | /* |
| 3474 | 3429 | ** wiki_to_text(TEXT) |
| 3475 | 3430 | ** |
| 3476 | -** Return a plain-text rendering of Fossil-Wiki TEXT. | |
| 3431 | +** Return a text rendering of Fossil-Wiki TEXT, intended for display | |
| 3432 | +** on a timeline. The timeline-plaintext and timeline-hard-newlines | |
| 3433 | +** settings are considered when doing this rendering. | |
| 3477 | 3434 | */ |
| 3478 | 3435 | static void wiki_to_text_sqlfunc( |
| 3479 | 3436 | sqlite3_context *context, |
| 3480 | 3437 | int argc, |
| 3481 | 3438 | sqlite3_value **argv |
| @@ -3486,14 +3443,14 @@ | ||
| 3486 | 3443 | zIn = (const char*)sqlite3_value_text(argv[0]); |
| 3487 | 3444 | if( zIn==0 ) return; |
| 3488 | 3445 | nIn = sqlite3_value_bytes(argv[0]); |
| 3489 | 3446 | blob_init(&in, zIn, nIn); |
| 3490 | 3447 | blob_init(&html, 0, 0); |
| 3491 | - wiki_convert(&in, &html, WIKI_INLINE); | |
| 3448 | + wiki_convert(&in, &html, wiki_convert_flags(0)); | |
| 3492 | 3449 | blob_reset(&in); |
| 3493 | 3450 | blob_init(&txt, 0, 0); |
| 3494 | - html_to_plaintext(blob_str(&html), &txt); | |
| 3451 | + html_to_plaintext(blob_str(&html), &txt, 0); | |
| 3495 | 3452 | blob_reset(&html); |
| 3496 | 3453 | nOut = blob_size(&txt); |
| 3497 | 3454 | zOut = blob_str(&txt); |
| 3498 | 3455 | while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; } |
| 3499 | 3456 | while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; } |
| 3500 | 3457 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -221,28 +221,28 @@ | |
| 221 | vid = db_lget_int("checkout", 0); |
| 222 | } |
| 223 | zPrevDate[0] = 0; |
| 224 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 225 | dateFormat = db_get_int("timeline-date-format", 0); |
| 226 | /* |
| 227 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| 228 | ** |
| 229 | ** If enabled, check-in comments displayed on the timeline are truncated |
| 230 | ** at the first blank line of the comment text. The comment text after |
| 231 | ** the first blank line is only seen in the /info or similar pages that |
| 232 | ** show details about the check-in. |
| 233 | */ |
| 234 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 235 | /* |
| 236 | ** SETTING: timeline-tslink-info boolean default=off |
| 237 | ** |
| 238 | ** The hyperlink on the timestamp associated with each timeline entry, |
| 239 | ** on the far left-hand side of the screen, normally targets another |
| 240 | ** /timeline page that shows the entry in context. However, if this |
| 241 | ** option is turned on, that hyperlink targets the /info page showing |
| 242 | ** the details of the entry. |
| 243 | */ |
| 244 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| 245 | if( (tmFlags & TIMELINE_VIEWS)==0 ){ |
| 246 | tmFlags |= timeline_ss_cookie(); |
| 247 | } |
| 248 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| @@ -402,10 +402,12 @@ | |
| 402 | zDateLink = mprintf("<a>"); |
| 403 | } |
| 404 | @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td> |
| 405 | @ <td class="timelineGraph"> |
| 406 | if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 407 | if( tmFlags & TIMELINE_UCOLOR ){ |
| 408 | zBgClr = zUser ? user_color(zUser) : 0; |
| 409 | }else if( tmFlags & TIMELINE_NOCOLOR ){ |
| 410 | zBgClr = 0; |
| 411 | }else if( zType[0]=='c' ){ |
| @@ -420,16 +422,21 @@ | |
| 420 | }else{ |
| 421 | zBgClr = hash_color("f"); /* delta manifest */ |
| 422 | } |
| 423 | db_reset(&qdelta); |
| 424 | } |
| 425 | } |
| 426 | if( zType[0]=='c' |
| 427 | && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0) |
| 428 | ){ |
| 429 | zBr = branch_of_rid(rid); |
| 430 | if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){ |
| 431 | if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 432 | }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){ |
| 433 | zBgClr = 0; |
| 434 | }else{ |
| 435 | zBgClr = hash_color(zBr); |
| @@ -1106,62 +1113,10 @@ | |
| 1106 | @ WHERE blob.rid=event.objid |
| 1107 | ; |
| 1108 | return zBase; |
| 1109 | } |
| 1110 | |
| 1111 | /* |
| 1112 | ** Convert a symbolic name used as an argument to the a=, b=, or c= |
| 1113 | ** query parameters of timeline into a julianday mtime value. |
| 1114 | ** |
| 1115 | ** If pzDisplay is not null, then display text for the symbolic name might |
| 1116 | ** be written into *pzDisplay. But that is not guaranteed. |
| 1117 | ** |
| 1118 | ** If bRoundUp is true and the symbolic name is a timestamp with less |
| 1119 | ** than millisecond resolution, then the timestamp is rounding up to the |
| 1120 | ** largest millisecond consistent with that timestamp. If bRoundUp is |
| 1121 | ** false, then the resulting time is obtained by extending the timestamp |
| 1122 | ** with zeros (hence rounding down). Use bRoundUp==1 if the result |
| 1123 | ** will be used in mtime<=$RESULT and use bRoundUp==0 if the result |
| 1124 | ** will be used in mtime>=$RESULT. |
| 1125 | */ |
| 1126 | double symbolic_name_to_mtime( |
| 1127 | const char *z, /* Input symbolic name */ |
| 1128 | const char **pzDisplay, /* Perhaps write display text here, if not NULL */ |
| 1129 | int bRoundUp /* Round up if true */ |
| 1130 | ){ |
| 1131 | double mtime; |
| 1132 | int rid; |
| 1133 | const char *zDate; |
| 1134 | if( z==0 ) return -1.0; |
| 1135 | if( fossil_isdate(z) ){ |
| 1136 | mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z); |
| 1137 | if( mtime>0.0 ) return mtime; |
| 1138 | } |
| 1139 | zDate = fossil_expand_datetime(z, 1, bRoundUp); |
| 1140 | if( zDate!=0 ){ |
| 1141 | mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", |
| 1142 | bRoundUp ? fossil_roundup_date(zDate) : zDate); |
| 1143 | if( mtime>0.0 ){ |
| 1144 | if( pzDisplay ) *pzDisplay = fossil_strdup(zDate); |
| 1145 | return mtime; |
| 1146 | } |
| 1147 | } |
| 1148 | rid = symbolic_name_to_rid(z, "*"); |
| 1149 | if( rid ){ |
| 1150 | mtime = mtime_of_rid(rid, 0.0); |
| 1151 | }else{ |
| 1152 | mtime = db_double(-1.0, |
| 1153 | "SELECT max(event.mtime) FROM event, tag, tagxref" |
| 1154 | " WHERE tag.tagname GLOB 'event-%q*'" |
| 1155 | " AND tagxref.tagid=tag.tagid AND tagxref.tagtype" |
| 1156 | " AND event.objid=tagxref.rid", |
| 1157 | z |
| 1158 | ); |
| 1159 | } |
| 1160 | return mtime; |
| 1161 | } |
| 1162 | |
| 1163 | /* |
| 1164 | ** zDate is a localtime date. Insert records into the |
| 1165 | ** "timeline" table to cause <hr> to be inserted on zDate. |
| 1166 | */ |
| 1167 | static int timeline_add_divider(double rDate){ |
| @@ -3471,11 +3426,13 @@ | |
| 3471 | } |
| 3472 | |
| 3473 | /* |
| 3474 | ** wiki_to_text(TEXT) |
| 3475 | ** |
| 3476 | ** Return a plain-text rendering of Fossil-Wiki TEXT. |
| 3477 | */ |
| 3478 | static void wiki_to_text_sqlfunc( |
| 3479 | sqlite3_context *context, |
| 3480 | int argc, |
| 3481 | sqlite3_value **argv |
| @@ -3486,14 +3443,14 @@ | |
| 3486 | zIn = (const char*)sqlite3_value_text(argv[0]); |
| 3487 | if( zIn==0 ) return; |
| 3488 | nIn = sqlite3_value_bytes(argv[0]); |
| 3489 | blob_init(&in, zIn, nIn); |
| 3490 | blob_init(&html, 0, 0); |
| 3491 | wiki_convert(&in, &html, WIKI_INLINE); |
| 3492 | blob_reset(&in); |
| 3493 | blob_init(&txt, 0, 0); |
| 3494 | html_to_plaintext(blob_str(&html), &txt); |
| 3495 | blob_reset(&html); |
| 3496 | nOut = blob_size(&txt); |
| 3497 | zOut = blob_str(&txt); |
| 3498 | while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; } |
| 3499 | while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; } |
| 3500 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -221,28 +221,28 @@ | |
| 221 | vid = db_lget_int("checkout", 0); |
| 222 | } |
| 223 | zPrevDate[0] = 0; |
| 224 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 225 | dateFormat = db_get_int("timeline-date-format", 0); |
| 226 | /* |
| 227 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| 228 | ** |
| 229 | ** If enabled, check-in comments displayed on the timeline are truncated |
| 230 | ** at the first blank line of the comment text. The comment text after |
| 231 | ** the first blank line is only seen in the /info or similar pages that |
| 232 | ** show details about the check-in. |
| 233 | */ |
| 234 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 235 | /* |
| 236 | ** SETTING: timeline-tslink-info boolean default=off |
| 237 | ** |
| 238 | ** The hyperlink on the timestamp associated with each timeline entry, |
| 239 | ** on the far left-hand side of the screen, normally targets another |
| 240 | ** /timeline page that shows the entry in context. However, if this |
| 241 | ** option is turned on, that hyperlink targets the /info page showing |
| 242 | ** the details of the entry. |
| 243 | */ |
| 244 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| 245 | if( (tmFlags & TIMELINE_VIEWS)==0 ){ |
| 246 | tmFlags |= timeline_ss_cookie(); |
| 247 | } |
| 248 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| @@ -402,10 +402,12 @@ | |
| 402 | zDateLink = mprintf("<a>"); |
| 403 | } |
| 404 | @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td> |
| 405 | @ <td class="timelineGraph"> |
| 406 | if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 407 | /* Don't use the requested background color. Use the background color |
| 408 | ** override from query parameters instead. */ |
| 409 | if( tmFlags & TIMELINE_UCOLOR ){ |
| 410 | zBgClr = zUser ? user_color(zUser) : 0; |
| 411 | }else if( tmFlags & TIMELINE_NOCOLOR ){ |
| 412 | zBgClr = 0; |
| 413 | }else if( zType[0]=='c' ){ |
| @@ -420,16 +422,21 @@ | |
| 422 | }else{ |
| 423 | zBgClr = hash_color("f"); /* delta manifest */ |
| 424 | } |
| 425 | db_reset(&qdelta); |
| 426 | } |
| 427 | }else{ |
| 428 | /* Make sure the user-specified background color is reasonable */ |
| 429 | zBgClr = reasonable_bg_color(zBgClr, 0); |
| 430 | } |
| 431 | if( zType[0]=='c' |
| 432 | && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0) |
| 433 | ){ |
| 434 | zBr = branch_of_rid(rid); |
| 435 | if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){ |
| 436 | /* If no background color is specified, use a color based on the |
| 437 | ** branch name */ |
| 438 | if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){ |
| 439 | }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){ |
| 440 | zBgClr = 0; |
| 441 | }else{ |
| 442 | zBgClr = hash_color(zBr); |
| @@ -1106,62 +1113,10 @@ | |
| 1113 | @ WHERE blob.rid=event.objid |
| 1114 | ; |
| 1115 | return zBase; |
| 1116 | } |
| 1117 | |
| 1118 | /* |
| 1119 | ** zDate is a localtime date. Insert records into the |
| 1120 | ** "timeline" table to cause <hr> to be inserted on zDate. |
| 1121 | */ |
| 1122 | static int timeline_add_divider(double rDate){ |
| @@ -3471,11 +3426,13 @@ | |
| 3426 | } |
| 3427 | |
| 3428 | /* |
| 3429 | ** wiki_to_text(TEXT) |
| 3430 | ** |
| 3431 | ** Return a text rendering of Fossil-Wiki TEXT, intended for display |
| 3432 | ** on a timeline. The timeline-plaintext and timeline-hard-newlines |
| 3433 | ** settings are considered when doing this rendering. |
| 3434 | */ |
| 3435 | static void wiki_to_text_sqlfunc( |
| 3436 | sqlite3_context *context, |
| 3437 | int argc, |
| 3438 | sqlite3_value **argv |
| @@ -3486,14 +3443,14 @@ | |
| 3443 | zIn = (const char*)sqlite3_value_text(argv[0]); |
| 3444 | if( zIn==0 ) return; |
| 3445 | nIn = sqlite3_value_bytes(argv[0]); |
| 3446 | blob_init(&in, zIn, nIn); |
| 3447 | blob_init(&html, 0, 0); |
| 3448 | wiki_convert(&in, &html, wiki_convert_flags(0)); |
| 3449 | blob_reset(&in); |
| 3450 | blob_init(&txt, 0, 0); |
| 3451 | html_to_plaintext(blob_str(&html), &txt, 0); |
| 3452 | blob_reset(&html); |
| 3453 | nOut = blob_size(&txt); |
| 3454 | zOut = blob_str(&txt); |
| 3455 | while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; } |
| 3456 | while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; } |
| 3457 |
+322
-185
| --- src/wikiformat.c | ||
| +++ src/wikiformat.c | ||
| @@ -23,23 +23,43 @@ | ||
| 23 | 23 | |
| 24 | 24 | #if INTERFACE |
| 25 | 25 | /* |
| 26 | 26 | ** Allowed wiki transformation operations |
| 27 | 27 | */ |
| 28 | -#define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */ | |
| 29 | -#define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */ | |
| 30 | -#define WIKI_NOBLOCK 0x004 /* No block markup of any kind */ | |
| 31 | -#define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */ | |
| 32 | -#define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */ | |
| 33 | -#define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */ | |
| 34 | -#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */ | |
| 35 | -#define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */ | |
| 36 | -#define WIKI_SAFE 0x100 /* Make the result safe for embedding */ | |
| 37 | -#define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */ | |
| 38 | -#define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */ | |
| 39 | -#define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */ | |
| 40 | -#endif | |
| 28 | +#define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */ | |
| 29 | +#define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */ | |
| 30 | +/* avalable for reuse: 0x0004 --- formerly WIKI_NOBLOCK */ | |
| 31 | +#define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */ | |
| 32 | +#define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */ | |
| 33 | +#define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */ | |
| 34 | +#define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */ | |
| 35 | +#define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */ | |
| 36 | +#define WIKI_SAFE 0x0100 /* Make the result safe for embedding */ | |
| 37 | +#define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */ | |
| 38 | +#define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */ | |
| 39 | +#define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */ | |
| 40 | +#define WIKI_MARK 0x1000 /* Add <mark>..</mark> around problems */ | |
| 41 | + | |
| 42 | +/* | |
| 43 | +** Return values from wiki_convert | |
| 44 | +*/ | |
| 45 | +#define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */ | |
| 46 | +#define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: <) */ | |
| 47 | +#define RENDER_TAG 0x0004 /* One or more HTML tags */ | |
| 48 | +#define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */ | |
| 49 | +#define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */ | |
| 50 | +#define RENDER_MARK 0x0020 /* Output contains <mark>..</mark> */ | |
| 51 | +#define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */ | |
| 52 | +#define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */ | |
| 53 | +#define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */ | |
| 54 | +#define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */ | |
| 55 | +#define RENDER_BADHTML 0x1000 /* Bad HTML seen */ | |
| 56 | +#define RENDER_ERROR 0x8000 /* Some other kind of error */ | |
| 57 | +/* Composite values: */ | |
| 58 | +#define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */ | |
| 59 | + | |
| 60 | +#endif /* INTERFACE */ | |
| 41 | 61 | |
| 42 | 62 | |
| 43 | 63 | /* |
| 44 | 64 | ** These are the only markup attributes allowed. |
| 45 | 65 | */ |
| @@ -445,20 +465,20 @@ | ||
| 445 | 465 | #define AT_NEWLINE 0x0010000 /* At start of a line */ |
| 446 | 466 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 447 | 467 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 448 | 468 | #define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */ |
| 449 | 469 | #define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */ |
| 450 | -#define INLINE_MARKUP_ONLY 0x0200000 /* Allow only "inline" markup */ | |
| 451 | -#define IN_LIST 0x0400000 /* Within wiki <ul> or <ol> */ | |
| 470 | +#define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */ | |
| 452 | 471 | |
| 453 | 472 | /* |
| 454 | 473 | ** Current state of the rendering engine |
| 455 | 474 | */ |
| 456 | 475 | typedef struct Renderer Renderer; |
| 457 | 476 | struct Renderer { |
| 458 | 477 | Blob *pOut; /* Output appended to this blob */ |
| 459 | 478 | int state; /* Flag that govern rendering */ |
| 479 | + int mRender; /* Mask of RENDER_* values to return */ | |
| 460 | 480 | unsigned renderFlags; /* Flags from the client */ |
| 461 | 481 | int wikiList; /* Current wiki list type */ |
| 462 | 482 | int inVerbatim; /* True in <verbatim> mode */ |
| 463 | 483 | int preVerbState; /* Value of state prior to verbatim */ |
| 464 | 484 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +700,26 @@ | ||
| 680 | 700 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 681 | 701 | int n; |
| 682 | 702 | if( z[0]=='<' ){ |
| 683 | 703 | n = html_tag_length(z); |
| 684 | 704 | if( n>0 ){ |
| 705 | + p->mRender |= RENDER_TAG; | |
| 685 | 706 | *pTokenType = TOKEN_MARKUP; |
| 686 | 707 | return n; |
| 687 | 708 | }else{ |
| 709 | + p->mRender |= RENDER_BADTAG; | |
| 710 | + *pTokenType = TOKEN_CHARACTER; | |
| 711 | + return 1; | |
| 712 | + } | |
| 713 | + } | |
| 714 | + if( z[0]=='&' ){ | |
| 715 | + p->mRender |= RENDER_ENTITY; | |
| 716 | + if( (p->inVerbatim || !isElement(z)) ){ | |
| 688 | 717 | *pTokenType = TOKEN_CHARACTER; |
| 689 | 718 | return 1; |
| 690 | 719 | } |
| 691 | 720 | } |
| 692 | - if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){ | |
| 693 | - *pTokenType = TOKEN_CHARACTER; | |
| 694 | - return 1; | |
| 695 | - } | |
| 696 | 721 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 697 | 722 | if( z[0]=='\n' ){ |
| 698 | 723 | n = paragraphBreakLength(z); |
| 699 | 724 | if( n>0 ){ |
| 700 | 725 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +751,31 @@ | ||
| 726 | 751 | if( n>0 ){ |
| 727 | 752 | *pTokenType = TOKEN_INDENT; |
| 728 | 753 | return n; |
| 729 | 754 | } |
| 730 | 755 | } |
| 731 | - if( z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 756 | + if( z[0]=='[' ){ | |
| 757 | + if( (n = linkLength(z))>0 ){ | |
| 758 | + *pTokenType = TOKEN_LINK; | |
| 759 | + return n; | |
| 760 | + }else if( p->state & WIKI_MARK ){ | |
| 761 | + blob_append_string(p->pOut, "<mark>"); | |
| 762 | + p->mRender |= RENDER_BADLINK|RENDER_MARK; | |
| 763 | + }else{ | |
| 764 | + p->mRender |= RENDER_BADLINK; | |
| 765 | + } | |
| 766 | + } | |
| 767 | + }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){ | |
| 768 | + if( (n = linkLength(z))>0 ){ | |
| 732 | 769 | *pTokenType = TOKEN_LINK; |
| 733 | 770 | return n; |
| 771 | + }else if( p->state & WIKI_MARK ){ | |
| 772 | + blob_append_string(p->pOut, "<mark>"); | |
| 773 | + p->mRender |= RENDER_BADLINK|RENDER_MARK; | |
| 774 | + }else{ | |
| 775 | + p->mRender |= RENDER_BADLINK; | |
| 734 | 776 | } |
| 735 | - }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 736 | - *pTokenType = TOKEN_LINK; | |
| 737 | - return n; | |
| 738 | 777 | } |
| 739 | 778 | *pTokenType = TOKEN_TEXT; |
| 740 | 779 | return 1 + textLength(z+1, p->state); |
| 741 | 780 | } |
| 742 | 781 | |
| @@ -746,13 +785,20 @@ | ||
| 746 | 785 | ** z points to the start of a token. Return the number of |
| 747 | 786 | ** characters in that token. Write the token type into *pTokenType. |
| 748 | 787 | */ |
| 749 | 788 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 750 | 789 | int n; |
| 751 | - if( z[0]=='[' && (n = linkLength(z))>0 ){ | |
| 752 | - *pTokenType = TOKEN_LINK; | |
| 753 | - return n; | |
| 790 | + if( z[0]=='[' ){ | |
| 791 | + if( (n = linkLength(z))>0 ){ | |
| 792 | + *pTokenType = TOKEN_LINK; | |
| 793 | + return n; | |
| 794 | + }else if( p->state & WIKI_MARK ){ | |
| 795 | + blob_append_string(p->pOut, "<mark>"); | |
| 796 | + p->mRender |= RENDER_BADLINK|RENDER_MARK; | |
| 797 | + }else{ | |
| 798 | + p->mRender |= RENDER_BADLINK; | |
| 799 | + } | |
| 754 | 800 | } |
| 755 | 801 | *pTokenType = TOKEN_RAW; |
| 756 | 802 | return 1 + textLength(z+1, p->state); |
| 757 | 803 | } |
| 758 | 804 | |
| @@ -1249,12 +1295,16 @@ | ||
| 1249 | 1295 | ** [wiki:WikiPageName] |
| 1250 | 1296 | ** |
| 1251 | 1297 | ** [2010-02-27 07:13] |
| 1252 | 1298 | ** |
| 1253 | 1299 | ** [InterMap:Link] -> Interwiki link |
| 1300 | +** | |
| 1301 | +** The return value is a mask of RENDER_* values indicating what happened. | |
| 1302 | +** Probably the return value is 0 on success and RENDER_BADTARGET or | |
| 1303 | +** RENDER_BADLINK if there are problems. | |
| 1254 | 1304 | */ |
| 1255 | -void wiki_resolve_hyperlink( | |
| 1305 | +int wiki_resolve_hyperlink( | |
| 1256 | 1306 | Blob *pOut, /* Write the HTML output here */ |
| 1257 | 1307 | int mFlags, /* Rendering option flags */ |
| 1258 | 1308 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1259 | 1309 | char *zClose, /* Write hyperlink closing text here */ |
| 1260 | 1310 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1314,11 @@ | ||
| 1264 | 1314 | const char *zTerm = "</a>"; |
| 1265 | 1315 | const char *z; |
| 1266 | 1316 | char *zExtra = 0; |
| 1267 | 1317 | const char *zExtraNS = 0; |
| 1268 | 1318 | char *zRemote = 0; |
| 1319 | + int rc = 0; | |
| 1269 | 1320 | |
| 1270 | 1321 | if( zTitle ){ |
| 1271 | 1322 | zExtra = mprintf(" title='%h'", zTitle); |
| 1272 | 1323 | zExtraNS = zExtra+1; |
| 1273 | 1324 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1317,14 +1368,19 @@ | ||
| 1317 | 1368 | } |
| 1318 | 1369 | } |
| 1319 | 1370 | }else if( !in_this_repo(zTarget) ){ |
| 1320 | 1371 | if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ |
| 1321 | 1372 | zTerm = ""; |
| 1373 | + }else if( (mFlags & WIKI_MARK)!=0 ){ | |
| 1374 | + blob_appendf(pOut, "<mark>%s", zLB); | |
| 1375 | + zTerm = "]</mark>"; | |
| 1376 | + rc |= RENDER_MARK; | |
| 1322 | 1377 | }else{ |
| 1323 | 1378 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1324 | 1379 | zTerm = "]</span>"; |
| 1325 | 1380 | } |
| 1381 | + rc |= RENDER_BADTARGET; | |
| 1326 | 1382 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1327 | 1383 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1328 | 1384 | zTerm = "]</a>"; |
| 1329 | 1385 | }else{ |
| 1330 | 1386 | zTerm = ""; |
| @@ -1341,33 +1397,40 @@ | ||
| 1341 | 1397 | }else{ |
| 1342 | 1398 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1343 | 1399 | } |
| 1344 | 1400 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1345 | 1401 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1346 | - /* Dates or date-and-times in ISO8610 resolve to a link to the | |
| 1402 | + /* Dates or date-and-times in ISO8601 resolve to a link to the | |
| 1347 | 1403 | ** timeline for that date */ |
| 1348 | 1404 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1349 | 1405 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1350 | 1406 | /* If none of the above, and if rendering links for markdown, then |
| 1351 | 1407 | ** create a link to the literal text of the target */ |
| 1352 | 1408 | blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra); |
| 1409 | + }else if( mFlags & WIKI_MARK ){ | |
| 1410 | + blob_appendf(pOut, "<mark>["); | |
| 1411 | + zTerm = "]</mark>"; | |
| 1412 | + rc |= RENDER_BADTARGET|RENDER_MARK; | |
| 1353 | 1413 | }else if( zOrig && zTarget>=&zOrig[2] |
| 1354 | 1414 | && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){ |
| 1355 | 1415 | /* If the hyperlink markup is not preceded by whitespace, then it |
| 1356 | 1416 | ** is probably a C-language subscript or similar, not really a |
| 1357 | 1417 | ** hyperlink. Just ignore it. */ |
| 1358 | 1418 | zTerm = ""; |
| 1359 | 1419 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1360 | 1420 | /* Also ignore the link if various flags are set */ |
| 1361 | 1421 | zTerm = ""; |
| 1422 | + rc |= RENDER_BADTARGET; | |
| 1362 | 1423 | }else{ |
| 1363 | 1424 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1364 | 1425 | zTerm = "</span>"; |
| 1426 | + rc |= RENDER_BADTARGET; | |
| 1365 | 1427 | } |
| 1366 | 1428 | if( zExtra ) fossil_free(zExtra); |
| 1367 | 1429 | assert( (int)strlen(zTerm)<nClose ); |
| 1368 | 1430 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1431 | + return rc; | |
| 1369 | 1432 | } |
| 1370 | 1433 | |
| 1371 | 1434 | /* |
| 1372 | 1435 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1373 | 1436 | ** Return true if it does seem valid and false if not. |
| @@ -1465,11 +1528,10 @@ | ||
| 1465 | 1528 | */ |
| 1466 | 1529 | static void wiki_render(Renderer *p, char *z){ |
| 1467 | 1530 | int tokenType; |
| 1468 | 1531 | ParsedMarkup markup; |
| 1469 | 1532 | int n; |
| 1470 | - int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; | |
| 1471 | 1533 | int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; |
| 1472 | 1534 | int linksOnly = (p->state & WIKI_LINKSONLY)!=0; |
| 1473 | 1535 | char *zOrig = z; |
| 1474 | 1536 | |
| 1475 | 1537 | /* Make sure the attribute constants and names still align |
| @@ -1483,22 +1545,17 @@ | ||
| 1483 | 1545 | n = nextWikiToken(z, p, &tokenType); |
| 1484 | 1546 | } |
| 1485 | 1547 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| 1486 | 1548 | switch( tokenType ){ |
| 1487 | 1549 | case TOKEN_PARAGRAPH: { |
| 1488 | - if( inlineOnly ){ | |
| 1489 | - /* blob_append_string(p->pOut, " ¶ "); */ | |
| 1490 | - blob_append_string(p->pOut, " "); | |
| 1491 | - }else{ | |
| 1492 | - if( p->wikiList ){ | |
| 1493 | - popStackToTag(p, p->wikiList); | |
| 1494 | - p->wikiList = 0; | |
| 1495 | - } | |
| 1496 | - endAutoParagraph(p); | |
| 1497 | - blob_append_string(p->pOut, "\n\n"); | |
| 1498 | - p->wantAutoParagraph = 1; | |
| 1499 | - } | |
| 1550 | + if( p->wikiList ){ | |
| 1551 | + popStackToTag(p, p->wikiList); | |
| 1552 | + p->wikiList = 0; | |
| 1553 | + } | |
| 1554 | + endAutoParagraph(p); | |
| 1555 | + blob_append_string(p->pOut, "\n\n"); | |
| 1556 | + p->wantAutoParagraph = 1; | |
| 1500 | 1557 | p->state |= AT_PARAGRAPH|AT_NEWLINE; |
| 1501 | 1558 | break; |
| 1502 | 1559 | } |
| 1503 | 1560 | case TOKEN_NEWLINE: { |
| 1504 | 1561 | if( p->renderFlags & WIKI_NEWLINE ){ |
| @@ -1508,86 +1565,91 @@ | ||
| 1508 | 1565 | } |
| 1509 | 1566 | p->state |= AT_NEWLINE; |
| 1510 | 1567 | break; |
| 1511 | 1568 | } |
| 1512 | 1569 | case TOKEN_BUL_LI: { |
| 1513 | - if( inlineOnly ){ | |
| 1514 | - blob_append_string(p->pOut, " • "); | |
| 1515 | - }else{ | |
| 1516 | - if( p->wikiList!=MARKUP_UL ){ | |
| 1517 | - if( p->wikiList ){ | |
| 1518 | - popStackToTag(p, p->wikiList); | |
| 1519 | - } | |
| 1520 | - endAutoParagraph(p); | |
| 1521 | - pushStack(p, MARKUP_UL); | |
| 1522 | - blob_append_string(p->pOut, "<ul>"); | |
| 1523 | - p->wikiList = MARKUP_UL; | |
| 1524 | - } | |
| 1525 | - popStackToTag(p, MARKUP_LI); | |
| 1526 | - startAutoParagraph(p); | |
| 1527 | - pushStack(p, MARKUP_LI); | |
| 1528 | - blob_append_string(p->pOut, "<li>"); | |
| 1529 | - } | |
| 1570 | + p->mRender |= RENDER_BLOCK; | |
| 1571 | + if( p->wikiList!=MARKUP_UL ){ | |
| 1572 | + if( p->wikiList ){ | |
| 1573 | + popStackToTag(p, p->wikiList); | |
| 1574 | + } | |
| 1575 | + endAutoParagraph(p); | |
| 1576 | + pushStack(p, MARKUP_UL); | |
| 1577 | + blob_append_string(p->pOut, "<ul>"); | |
| 1578 | + p->wikiList = MARKUP_UL; | |
| 1579 | + } | |
| 1580 | + popStackToTag(p, MARKUP_LI); | |
| 1581 | + startAutoParagraph(p); | |
| 1582 | + pushStack(p, MARKUP_LI); | |
| 1583 | + blob_append_string(p->pOut, "<li>"); | |
| 1530 | 1584 | break; |
| 1531 | 1585 | } |
| 1532 | 1586 | case TOKEN_NUM_LI: { |
| 1533 | - if( inlineOnly ){ | |
| 1534 | - blob_append_string(p->pOut, " # "); | |
| 1535 | - }else{ | |
| 1536 | - if( p->wikiList!=MARKUP_OL ){ | |
| 1537 | - if( p->wikiList ){ | |
| 1538 | - popStackToTag(p, p->wikiList); | |
| 1539 | - } | |
| 1540 | - endAutoParagraph(p); | |
| 1541 | - pushStack(p, MARKUP_OL); | |
| 1542 | - blob_append_string(p->pOut, "<ol>"); | |
| 1543 | - p->wikiList = MARKUP_OL; | |
| 1544 | - } | |
| 1545 | - popStackToTag(p, MARKUP_LI); | |
| 1546 | - startAutoParagraph(p); | |
| 1547 | - pushStack(p, MARKUP_LI); | |
| 1548 | - blob_append_string(p->pOut, "<li>"); | |
| 1549 | - } | |
| 1587 | + p->mRender |= RENDER_BLOCK; | |
| 1588 | + if( p->wikiList!=MARKUP_OL ){ | |
| 1589 | + if( p->wikiList ){ | |
| 1590 | + popStackToTag(p, p->wikiList); | |
| 1591 | + } | |
| 1592 | + endAutoParagraph(p); | |
| 1593 | + pushStack(p, MARKUP_OL); | |
| 1594 | + blob_append_string(p->pOut, "<ol>"); | |
| 1595 | + p->wikiList = MARKUP_OL; | |
| 1596 | + } | |
| 1597 | + popStackToTag(p, MARKUP_LI); | |
| 1598 | + startAutoParagraph(p); | |
| 1599 | + pushStack(p, MARKUP_LI); | |
| 1600 | + blob_append_string(p->pOut, "<li>"); | |
| 1550 | 1601 | break; |
| 1551 | 1602 | } |
| 1552 | 1603 | case TOKEN_ENUM: { |
| 1553 | - if( inlineOnly ){ | |
| 1554 | - blob_appendf(p->pOut, " (%d) ", atoi(z)); | |
| 1555 | - }else{ | |
| 1556 | - if( p->wikiList!=MARKUP_OL ){ | |
| 1557 | - if( p->wikiList ){ | |
| 1558 | - popStackToTag(p, p->wikiList); | |
| 1559 | - } | |
| 1560 | - endAutoParagraph(p); | |
| 1561 | - pushStack(p, MARKUP_OL); | |
| 1562 | - blob_append_string(p->pOut, "<ol>"); | |
| 1563 | - p->wikiList = MARKUP_OL; | |
| 1564 | - } | |
| 1565 | - popStackToTag(p, MARKUP_LI); | |
| 1566 | - startAutoParagraph(p); | |
| 1567 | - pushStack(p, MARKUP_LI); | |
| 1568 | - blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); | |
| 1569 | - } | |
| 1604 | + p->mRender |= RENDER_BLOCK; | |
| 1605 | + if( p->wikiList!=MARKUP_OL ){ | |
| 1606 | + if( p->wikiList ){ | |
| 1607 | + popStackToTag(p, p->wikiList); | |
| 1608 | + } | |
| 1609 | + endAutoParagraph(p); | |
| 1610 | + pushStack(p, MARKUP_OL); | |
| 1611 | + blob_append_string(p->pOut, "<ol>"); | |
| 1612 | + p->wikiList = MARKUP_OL; | |
| 1613 | + } | |
| 1614 | + popStackToTag(p, MARKUP_LI); | |
| 1615 | + startAutoParagraph(p); | |
| 1616 | + pushStack(p, MARKUP_LI); | |
| 1617 | + blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); | |
| 1570 | 1618 | break; |
| 1571 | 1619 | } |
| 1572 | 1620 | case TOKEN_INDENT: { |
| 1573 | - if( !inlineOnly ){ | |
| 1574 | - assert( p->wikiList==0 ); | |
| 1575 | - pushStack(p, MARKUP_BLOCKQUOTE); | |
| 1576 | - blob_append_string(p->pOut, "<blockquote>"); | |
| 1577 | - p->wantAutoParagraph = 0; | |
| 1578 | - p->wikiList = MARKUP_BLOCKQUOTE; | |
| 1579 | - } | |
| 1621 | + p->mRender |= RENDER_BLOCK; | |
| 1622 | + assert( p->wikiList==0 ); | |
| 1623 | + pushStack(p, MARKUP_BLOCKQUOTE); | |
| 1624 | + blob_append_string(p->pOut, "<blockquote>"); | |
| 1625 | + p->wantAutoParagraph = 0; | |
| 1626 | + p->wikiList = MARKUP_BLOCKQUOTE; | |
| 1580 | 1627 | break; |
| 1581 | 1628 | } |
| 1582 | 1629 | case TOKEN_CHARACTER: { |
| 1583 | 1630 | startAutoParagraph(p); |
| 1631 | + if( p->state & WIKI_MARK ){ | |
| 1632 | + blob_append_string(p->pOut, "<mark>"); | |
| 1633 | + p->mRender |= RENDER_MARK; | |
| 1634 | + } | |
| 1584 | 1635 | if( z[0]=='<' ){ |
| 1636 | + p->mRender |= RENDER_BADTAG; | |
| 1585 | 1637 | blob_append_string(p->pOut, "<"); |
| 1586 | 1638 | }else if( z[0]=='&' ){ |
| 1639 | + p->mRender |= RENDER_BADENTITY; | |
| 1587 | 1640 | blob_append_string(p->pOut, "&"); |
| 1588 | 1641 | } |
| 1642 | + if( p->state & WIKI_MARK ){ | |
| 1643 | + if( fossil_isalnum(z[1]) || (z[1]=='/' && fossil_isalnum(z[2])) ){ | |
| 1644 | + int kk; | |
| 1645 | + for(kk=2; fossil_isalnum(z[kk]); kk++){} | |
| 1646 | + blob_append(p->pOut, &z[1], kk-1); | |
| 1647 | + n = kk; | |
| 1648 | + } | |
| 1649 | + blob_append_string(p->pOut, "</mark>"); | |
| 1650 | + } | |
| 1589 | 1651 | break; |
| 1590 | 1652 | } |
| 1591 | 1653 | case TOKEN_LINK: { |
| 1592 | 1654 | char *zTarget; |
| 1593 | 1655 | char *zDisplay = 0; |
| @@ -1596,10 +1658,11 @@ | ||
| 1596 | 1658 | char zClose[20]; |
| 1597 | 1659 | char cS1 = 0; |
| 1598 | 1660 | int iS1 = 0; |
| 1599 | 1661 | |
| 1600 | 1662 | startAutoParagraph(p); |
| 1663 | + p->mRender |= RENDER_LINK; | |
| 1601 | 1664 | zTarget = &z[1]; |
| 1602 | 1665 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1603 | 1666 | if( z[i]=='|' && zDisplay==0 ){ |
| 1604 | 1667 | zDisplay = &z[i+1]; |
| 1605 | 1668 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1675,11 @@ | ||
| 1612 | 1675 | if( zDisplay==0 ){ |
| 1613 | 1676 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1614 | 1677 | }else{ |
| 1615 | 1678 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1616 | 1679 | } |
| 1617 | - wiki_resolve_hyperlink(p->pOut, p->state, | |
| 1680 | + p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state, | |
| 1618 | 1681 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1619 | 1682 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1620 | 1683 | if( cS1 ) z[iS1] = cS1; |
| 1621 | 1684 | if( zClose[0]!=']' ){ |
| 1622 | 1685 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,14 +1765,22 @@ | ||
| 1702 | 1765 | |
| 1703 | 1766 | /* Render invalid markup literally. The markup appears in the |
| 1704 | 1767 | ** final output as plain text. |
| 1705 | 1768 | */ |
| 1706 | 1769 | if( markup.iCode==MARKUP_INVALID ){ |
| 1770 | + p->mRender |= RENDER_BADTAG; | |
| 1707 | 1771 | unparseMarkup(&markup); |
| 1708 | 1772 | startAutoParagraph(p); |
| 1709 | - blob_append_string(p->pOut, "<"); | |
| 1710 | - n = 1; | |
| 1773 | + if( p->state & WIKI_MARK ){ | |
| 1774 | + p->mRender |= RENDER_MARK; | |
| 1775 | + blob_append_string(p->pOut, "<mark>"); | |
| 1776 | + htmlize_to_blob(p->pOut, z, n); | |
| 1777 | + blob_append_string(p->pOut, "</mark>"); | |
| 1778 | + }else{ | |
| 1779 | + blob_append_string(p->pOut, "<"); | |
| 1780 | + htmlize_to_blob(p->pOut, z+1, n-1); | |
| 1781 | + } | |
| 1711 | 1782 | }else |
| 1712 | 1783 | |
| 1713 | 1784 | /* If the markup is not font-change markup ignore it if the |
| 1714 | 1785 | ** font-change-only flag is set. |
| 1715 | 1786 | */ |
| @@ -1723,16 +1794,10 @@ | ||
| 1723 | 1794 | }else{ |
| 1724 | 1795 | p->state &= ~ALLOW_WIKI; |
| 1725 | 1796 | } |
| 1726 | 1797 | }else |
| 1727 | 1798 | |
| 1728 | - /* Ignore block markup for in-line rendering. | |
| 1729 | - */ | |
| 1730 | - if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ | |
| 1731 | - /* Do nothing */ | |
| 1732 | - }else | |
| 1733 | - | |
| 1734 | 1799 | /* Generate end-tags */ |
| 1735 | 1800 | if( markup.endTag ){ |
| 1736 | 1801 | popStackToTag(p, markup.iCode); |
| 1737 | 1802 | }else |
| 1738 | 1803 | |
| @@ -1814,10 +1879,11 @@ | ||
| 1814 | 1879 | }else |
| 1815 | 1880 | { |
| 1816 | 1881 | if( markup.iType==MUTYPE_FONT ){ |
| 1817 | 1882 | startAutoParagraph(p); |
| 1818 | 1883 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1884 | + p->mRender |= RENDER_BLOCKTAG; | |
| 1819 | 1885 | p->wantAutoParagraph = 0; |
| 1820 | 1886 | } |
| 1821 | 1887 | if( markup.iCode==MARKUP_HR |
| 1822 | 1888 | || markup.iCode==MARKUP_H1 |
| 1823 | 1889 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1910,14 @@ | ||
| 1844 | 1910 | ** Transform the text in the pIn blob. Write the results |
| 1845 | 1911 | ** into the pOut blob. The pOut blob should already be |
| 1846 | 1912 | ** initialized. The output is merely appended to pOut. |
| 1847 | 1913 | ** If pOut is NULL, then the output is appended to the CGI |
| 1848 | 1914 | ** reply. |
| 1915 | +** | |
| 1916 | +** Return a mask of RENDER_ flags indicating what happened. | |
| 1849 | 1917 | */ |
| 1850 | -void wiki_convert(Blob *pIn, Blob *pOut, int flags){ | |
| 1918 | +int wiki_convert(Blob *pIn, Blob *pOut, int flags){ | |
| 1851 | 1919 | Renderer renderer; |
| 1852 | 1920 | |
| 1853 | 1921 | memset(&renderer, 0, sizeof(renderer)); |
| 1854 | 1922 | renderer.renderFlags = flags; |
| 1855 | 1923 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1941,11 @@ | ||
| 1873 | 1941 | while( renderer.nStack ){ |
| 1874 | 1942 | popStack(&renderer); |
| 1875 | 1943 | } |
| 1876 | 1944 | blob_append_char(renderer.pOut, '\n'); |
| 1877 | 1945 | free(renderer.aStack); |
| 1946 | + return renderer.mRender; | |
| 1878 | 1947 | } |
| 1879 | 1948 | |
| 1880 | 1949 | /* |
| 1881 | 1950 | ** COMMAND: test-wiki-render |
| 1882 | 1951 | ** |
| @@ -1886,45 +1955,84 @@ | ||
| 1886 | 1955 | ** the resulting HTML on standard output. |
| 1887 | 1956 | ** |
| 1888 | 1957 | ** Options: |
| 1889 | 1958 | ** --buttons Set the WIKI_BUTTONS flag |
| 1890 | 1959 | ** --dark-pikchr Render pikchrs in dark mode |
| 1960 | +** --flow Render as text using comment_format | |
| 1891 | 1961 | ** --htmlonly Set the WIKI_HTMLONLY flag |
| 1892 | 1962 | ** --inline Set the WIKI_INLINE flag |
| 1893 | 1963 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1964 | +** -m TEXT Use TEXT in place of the content of FILE | |
| 1965 | +** --mark Add <mark>...</mark> around problems | |
| 1894 | 1966 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1895 | -** --noblock Set the WIKI_NOBLOCK flag | |
| 1896 | -** --text Run the output through html_to_plaintext(). | |
| 1967 | +** --text Run the output through html_to_plaintext() | |
| 1968 | +** --type Break down the return code from wiki_convert() | |
| 1897 | 1969 | */ |
| 1898 | 1970 | void test_wiki_render(void){ |
| 1899 | 1971 | Blob in, out; |
| 1900 | 1972 | int flags = 0; |
| 1901 | 1973 | int bText; |
| 1974 | + int bFlow = 0; | |
| 1975 | + int showType = 0; | |
| 1976 | + int mType; | |
| 1977 | + const char *zIn; | |
| 1902 | 1978 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1903 | 1979 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1904 | 1980 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1905 | 1981 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1906 | 1982 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| 1907 | - if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; | |
| 1983 | + if( find_option("mark",0,0)!=0 ) flags |= WIKI_MARK; | |
| 1908 | 1984 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1909 | 1985 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1910 | 1986 | } |
| 1911 | 1987 | bText = find_option("text",0,0)!=0; |
| 1988 | + bFlow = find_option("flow",0,0)!=0; | |
| 1989 | + showType = find_option("type",0,0)!=0; | |
| 1990 | + zIn = find_option("msg","m",1); | |
| 1912 | 1991 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1913 | 1992 | verify_all_options(); |
| 1914 | - if( g.argc!=3 ) usage("FILE"); | |
| 1993 | + if( (zIn==0 && g.argc!=3) || (zIn!=0 && g.argc!=2) ) usage("FILE"); | |
| 1915 | 1994 | blob_zero(&out); |
| 1916 | - blob_read_from_file(&in, g.argv[2], ExtFILE); | |
| 1917 | - wiki_convert(&in, &out, flags); | |
| 1995 | + if( zIn ){ | |
| 1996 | + blob_init(&in, zIn, -1); | |
| 1997 | + }else{ | |
| 1998 | + blob_read_from_file(&in, g.argv[2], ExtFILE); | |
| 1999 | + } | |
| 2000 | + mType = wiki_convert(&in, &out, flags); | |
| 1918 | 2001 | if( bText ){ |
| 1919 | 2002 | Blob txt; |
| 2003 | + int htot = HTOT_TRIM; | |
| 2004 | + if( terminal_is_vt100() ) htot |= HTOT_VT100; | |
| 2005 | + if( bFlow ) htot |= HTOT_FLOW; | |
| 1920 | 2006 | blob_init(&txt, 0, 0); |
| 1921 | - html_to_plaintext(blob_str(&out),&txt); | |
| 2007 | + html_to_plaintext(blob_str(&out),&txt, htot); | |
| 1922 | 2008 | blob_reset(&out); |
| 1923 | 2009 | out = txt; |
| 1924 | 2010 | } |
| 1925 | - blob_write_to_file(&out, "-"); | |
| 2011 | + if( bFlow ){ | |
| 2012 | + fossil_print(" "); | |
| 2013 | + comment_print(blob_str(&out), 0, 3, terminal_get_width(80)-3, | |
| 2014 | + get_comment_format()); | |
| 2015 | + }else{ | |
| 2016 | + blob_write_to_file(&out, "-"); | |
| 2017 | + } | |
| 2018 | + if( showType ){ | |
| 2019 | + fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*'); | |
| 2020 | + if( mType & RENDER_LINK ) fossil_print(" LINK"); | |
| 2021 | + if( mType & RENDER_ENTITY ) fossil_print(" ENTITY"); | |
| 2022 | + if( mType & RENDER_TAG ) fossil_print(" TAG"); | |
| 2023 | + if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG"); | |
| 2024 | + if( mType & RENDER_BLOCK ) fossil_print(" BLOCK"); | |
| 2025 | + if( mType & RENDER_MARK ) fossil_print(" MARK"); | |
| 2026 | + if( mType & RENDER_BADLINK ) fossil_print(" BADLINK"); | |
| 2027 | + if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET"); | |
| 2028 | + if( mType & RENDER_BADTAG ) fossil_print(" BADTAG"); | |
| 2029 | + if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY"); | |
| 2030 | + if( mType & RENDER_BADHTML ) fossil_print(" BADHTML"); | |
| 2031 | + if( mType & RENDER_ERROR ) fossil_print(" ERROR"); | |
| 2032 | + fossil_print("\n"); | |
| 2033 | + } | |
| 1926 | 2034 | } |
| 1927 | 2035 | |
| 1928 | 2036 | /* |
| 1929 | 2037 | ** COMMAND: test-markdown-render |
| 1930 | 2038 | ** |
| @@ -1960,11 +2068,11 @@ | ||
| 1960 | 2068 | safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED ); |
| 1961 | 2069 | safe_html(&out); |
| 1962 | 2070 | if( bText ){ |
| 1963 | 2071 | Blob txt; |
| 1964 | 2072 | blob_init(&txt, 0, 0); |
| 1965 | - html_to_plaintext(blob_str(&out), &txt); | |
| 2073 | + html_to_plaintext(blob_str(&out), &txt, HTOT_VT100); | |
| 1966 | 2074 | blob_reset(&out); |
| 1967 | 2075 | out = txt; |
| 1968 | 2076 | } |
| 1969 | 2077 | blob_write_to_file(&out, "-"); |
| 1970 | 2078 | blob_reset(&in); |
| @@ -2024,33 +2132,30 @@ | ||
| 2024 | 2132 | ** [target|...] |
| 2025 | 2133 | ** |
| 2026 | 2134 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2027 | 2135 | ** name. For each such hyperlink found, add an entry to the |
| 2028 | 2136 | ** backlink table. |
| 2137 | +** | |
| 2138 | +** The return value is a mask of RENDER_ flags. | |
| 2029 | 2139 | */ |
| 2030 | -void wiki_extract_links( | |
| 2140 | +int wiki_extract_links( | |
| 2031 | 2141 | char *z, /* The wiki text from which to extract links */ |
| 2032 | 2142 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2033 | 2143 | int flags /* wiki parsing flags */ |
| 2034 | 2144 | ){ |
| 2035 | 2145 | Renderer renderer; |
| 2036 | 2146 | int tokenType; |
| 2037 | 2147 | ParsedMarkup markup; |
| 2038 | 2148 | int n; |
| 2039 | - int inlineOnly; | |
| 2040 | 2149 | int wikiHtmlOnly = 0; |
| 2041 | 2150 | |
| 2042 | 2151 | memset(&renderer, 0, sizeof(renderer)); |
| 2043 | 2152 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 2044 | - if( flags & WIKI_NOBLOCK ){ | |
| 2045 | - renderer.state |= INLINE_MARKUP_ONLY; | |
| 2046 | - } | |
| 2047 | 2153 | if( wikiUsesHtml() ){ |
| 2048 | 2154 | renderer.state |= WIKI_HTMLONLY; |
| 2049 | 2155 | wikiHtmlOnly = 1; |
| 2050 | 2156 | } |
| 2051 | - inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; | |
| 2052 | 2157 | |
| 2053 | 2158 | while( z[0] ){ |
| 2054 | 2159 | if( wikiHtmlOnly ){ |
| 2055 | 2160 | n = nextRawToken(z, &renderer, &tokenType); |
| 2056 | 2161 | }else{ |
| @@ -2127,16 +2232,10 @@ | ||
| 2127 | 2232 | }else{ |
| 2128 | 2233 | renderer.state &= ~ALLOW_WIKI; |
| 2129 | 2234 | } |
| 2130 | 2235 | }else |
| 2131 | 2236 | |
| 2132 | - /* Ignore block markup for in-line rendering. | |
| 2133 | - */ | |
| 2134 | - if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ | |
| 2135 | - /* Do nothing */ | |
| 2136 | - }else | |
| 2137 | - | |
| 2138 | 2237 | /* Generate end-tags */ |
| 2139 | 2238 | if( markup.endTag ){ |
| 2140 | 2239 | popStackToTag(&renderer, markup.iCode); |
| 2141 | 2240 | }else |
| 2142 | 2241 | |
| @@ -2175,10 +2274,11 @@ | ||
| 2175 | 2274 | } |
| 2176 | 2275 | } |
| 2177 | 2276 | z += n; |
| 2178 | 2277 | } |
| 2179 | 2278 | free(renderer.aStack); |
| 2279 | + return renderer.mRender; | |
| 2180 | 2280 | } |
| 2181 | 2281 | |
| 2182 | 2282 | /* |
| 2183 | 2283 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2184 | 2284 | */ |
| @@ -2427,33 +2527,61 @@ | ||
| 2427 | 2527 | blob_reset(&in); |
| 2428 | 2528 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2429 | 2529 | blob_reset(&out); |
| 2430 | 2530 | } |
| 2431 | 2531 | } |
| 2532 | + | |
| 2533 | +#if INTERFACE | |
| 2534 | +/* | |
| 2535 | +** Allowed flag options for html_to_plaintext(). | |
| 2536 | +*/ | |
| 2537 | +#define HTOT_VT100 0x01 /* <mark> becomes ^[[91m */ | |
| 2538 | +#define HTOT_FLOW 0x02 /* Collapse internal whitespace to a single space */ | |
| 2539 | +#define HTOT_TRIM 0x04 /* Trim off leading and trailing whitespace */ | |
| 2540 | + | |
| 2541 | +#endif /* INTERFACE */ | |
| 2542 | + | |
| 2543 | +/* | |
| 2544 | +** Add <MARK> or </MARK> to the output, or similar VT-100 escape | |
| 2545 | +** codes. | |
| 2546 | +*/ | |
| 2547 | +static void addMark(Blob *pOut, int mFlags, int isClose){ | |
| 2548 | + static const char *az[4] = { "<MARK>", "</MARK>", "\033[91m", "\033[0m" }; | |
| 2549 | + int i = 0; | |
| 2550 | + if( isClose ) i++; | |
| 2551 | + if( mFlags & HTOT_VT100 ) i += 2; | |
| 2552 | + blob_append(pOut, az[i], -1); | |
| 2553 | +} | |
| 2432 | 2554 | |
| 2433 | 2555 | /* |
| 2434 | 2556 | ** Remove all HTML markup from the input text. The output written into |
| 2435 | 2557 | ** pOut is pure text. |
| 2436 | 2558 | ** |
| 2437 | 2559 | ** Put the title on the first line, if there is any <title> markup. |
| 2438 | 2560 | ** If there is no <title>, then create a blank first line. |
| 2439 | 2561 | */ |
| 2440 | -void html_to_plaintext(const char *zIn, Blob *pOut){ | |
| 2562 | +void html_to_plaintext(const char *zIn, Blob *pOut, int mFlags){ | |
| 2441 | 2563 | int n; |
| 2442 | 2564 | int i, j; |
| 2443 | - int inTitle = 0; /* True between <title>...</title> */ | |
| 2444 | - int seenText = 0; /* True after first non-whitespace seen */ | |
| 2445 | - int nNL = 0; /* Number of \n characters at the end of pOut */ | |
| 2446 | - int nWS = 0; /* True if pOut ends with whitespace */ | |
| 2447 | - while( fossil_isspace(zIn[0]) ) zIn++; | |
| 2565 | + int bFlow = 0; /* Transform internal WS into a single space */ | |
| 2566 | + int prevWS = 1; /* Previous output was whitespace or start of msg */ | |
| 2567 | + int nMark = 0; /* True if inside of <mark>..</mark> */ | |
| 2568 | + | |
| 2569 | + for(i=0; fossil_isspace(zIn[i]); i++){} | |
| 2570 | + if( i>0 && (mFlags & HTOT_TRIM)==0 ){ | |
| 2571 | + blob_append(pOut, zIn, i); | |
| 2572 | + } | |
| 2573 | + zIn += i; | |
| 2574 | + if( mFlags & HTOT_FLOW ) bFlow = 1; | |
| 2448 | 2575 | while( zIn[0] ){ |
| 2449 | 2576 | n = html_token_length(zIn); |
| 2450 | 2577 | if( zIn[0]=='<' && n>1 ){ |
| 2451 | 2578 | int isCloseTag; |
| 2452 | 2579 | int eTag; |
| 2453 | 2580 | int eType; |
| 2454 | 2581 | char zTag[32]; |
| 2582 | + prevWS = 0; | |
| 2455 | 2583 | isCloseTag = zIn[1]=='/'; |
| 2456 | 2584 | for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ |
| 2457 | 2585 | zTag[i] = fossil_tolower(zIn[j]); |
| 2458 | 2586 | } |
| 2459 | 2587 | zTag[i] = 0; |
| @@ -2467,33 +2595,44 @@ | ||
| 2467 | 2595 | zIn += n; |
| 2468 | 2596 | } |
| 2469 | 2597 | if( zIn[0]=='<' ) zIn += n; |
| 2470 | 2598 | continue; |
| 2471 | 2599 | } |
| 2472 | - if( eTag==MARKUP_TITLE ){ | |
| 2473 | - inTitle = !isCloseTag; | |
| 2474 | - } | |
| 2475 | - if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ | |
| 2476 | - if( nNL==0 ){ | |
| 2477 | - blob_append_char(pOut, '\n'); | |
| 2478 | - nNL++; | |
| 2479 | - } | |
| 2480 | - nWS = 1; | |
| 2600 | + if( eTag==MARKUP_INVALID && strcmp(zTag,"mark")==0 ){ | |
| 2601 | + if( isCloseTag && nMark ){ | |
| 2602 | + addMark(pOut, mFlags, 1); | |
| 2603 | + nMark = 0; | |
| 2604 | + }else if( !isCloseTag && !nMark ){ | |
| 2605 | + addMark(pOut, mFlags, 0); | |
| 2606 | + nMark = 1; | |
| 2607 | + } | |
| 2608 | + zIn += n; | |
| 2609 | + continue; | |
| 2610 | + } | |
| 2611 | + if( eTag==MARKUP_TITLE ){ | |
| 2612 | + if( isCloseTag && (mFlags & HTOT_FLOW)==0 ){ | |
| 2613 | + bFlow = 0; | |
| 2614 | + }else{ | |
| 2615 | + bFlow = 1; | |
| 2616 | + } | |
| 2617 | + } | |
| 2618 | + if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ | |
| 2619 | + blob_append_char(pOut, '\n'); | |
| 2481 | 2620 | } |
| 2482 | 2621 | }else if( fossil_isspace(zIn[0]) ){ |
| 2483 | - if( seenText ){ | |
| 2484 | - nNL = 0; | |
| 2485 | - if( !inTitle ){ /* '\n' -> ' ' within <title> */ | |
| 2486 | - for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++; | |
| 2487 | - } | |
| 2488 | - if( !nWS ){ | |
| 2489 | - blob_append_char(pOut, nNL ? '\n' : ' '); | |
| 2490 | - nWS = 1; | |
| 2491 | - } | |
| 2622 | + if( bFlow==0 ){ | |
| 2623 | + if( zIn[n]==0 && (mFlags & HTOT_TRIM) ) break; | |
| 2624 | + blob_append(pOut, zIn, n); | |
| 2625 | + }else if( !prevWS ){ | |
| 2626 | + prevWS = 1; | |
| 2627 | + blob_append_char(pOut, ' '); | |
| 2628 | + zIn += n; | |
| 2629 | + n = 0; | |
| 2492 | 2630 | } |
| 2493 | 2631 | }else if( zIn[0]=='&' ){ |
| 2494 | 2632 | u32 c = '?'; |
| 2633 | + prevWS = 0; | |
| 2495 | 2634 | if( zIn[1]=='#' ){ |
| 2496 | 2635 | c = atoi(&zIn[2]); |
| 2497 | 2636 | if( c==0 ) c = '?'; |
| 2498 | 2637 | }else{ |
| 2499 | 2638 | static const struct { int n; u32 c; char *z; } aEntity[] = { |
| @@ -2509,65 +2648,63 @@ | ||
| 2509 | 2648 | c = aEntity[jj].c; |
| 2510 | 2649 | break; |
| 2511 | 2650 | } |
| 2512 | 2651 | } |
| 2513 | 2652 | } |
| 2514 | - if( fossil_isspace(c) ){ | |
| 2515 | - if( nWS==0 && seenText ) blob_append_char(pOut, c); | |
| 2516 | - nWS = 1; | |
| 2517 | - nNL = c=='\n'; | |
| 2518 | - }else{ | |
| 2519 | - if( !seenText && !inTitle ) blob_append_char(pOut, '\n'); | |
| 2520 | - seenText = 1; | |
| 2521 | - nNL = nWS = 0; | |
| 2522 | - if( c<0x00080 ){ | |
| 2523 | - blob_append_char(pOut, c & 0xff); | |
| 2524 | - }else if( c<0x00800 ){ | |
| 2525 | - blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f)); | |
| 2526 | - blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2527 | - }else if( c<0x10000 ){ | |
| 2528 | - blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f)); | |
| 2529 | - blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); | |
| 2530 | - blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2531 | - }else{ | |
| 2532 | - blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07)); | |
| 2533 | - blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f)); | |
| 2534 | - blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); | |
| 2535 | - blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2536 | - } | |
| 2537 | - } | |
| 2538 | - }else{ | |
| 2539 | - if( !seenText && !inTitle ) blob_append_char(pOut, '\n'); | |
| 2540 | - seenText = 1; | |
| 2541 | - nNL = nWS = 0; | |
| 2653 | + if( c<0x00080 ){ | |
| 2654 | + blob_append_char(pOut, c & 0xff); | |
| 2655 | + }else if( c<0x00800 ){ | |
| 2656 | + blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f)); | |
| 2657 | + blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2658 | + }else if( c<0x10000 ){ | |
| 2659 | + blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f)); | |
| 2660 | + blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); | |
| 2661 | + blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2662 | + }else{ | |
| 2663 | + blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07)); | |
| 2664 | + blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f)); | |
| 2665 | + blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); | |
| 2666 | + blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); | |
| 2667 | + } | |
| 2668 | + }else{ | |
| 2669 | + prevWS = 0; | |
| 2542 | 2670 | blob_append(pOut, zIn, n); |
| 2543 | 2671 | } |
| 2544 | 2672 | zIn += n; |
| 2545 | 2673 | } |
| 2546 | - if( nNL==0 ) blob_append_char(pOut, '\n'); | |
| 2674 | + if( nMark ){ | |
| 2675 | + addMark(pOut, mFlags, 1); | |
| 2676 | + } | |
| 2547 | 2677 | } |
| 2548 | 2678 | |
| 2549 | 2679 | /* |
| 2550 | 2680 | ** COMMAND: test-html-to-text |
| 2551 | 2681 | ** |
| 2552 | -** Usage: %fossil test-html-to-text FILE ... | |
| 2682 | +** Usage: %fossil test-html-to-text [OPTIONS] FILE ... | |
| 2553 | 2683 | ** |
| 2554 | 2684 | ** Read all files named on the command-line. Convert the file |
| 2555 | 2685 | ** content from HTML to text and write the results on standard |
| 2556 | 2686 | ** output. |
| 2557 | 2687 | ** |
| 2558 | 2688 | ** This command is intended as a test and debug interface for |
| 2559 | 2689 | ** the html_to_plaintext() routine. |
| 2690 | +** | |
| 2691 | +** Options: | |
| 2692 | +** | |
| 2693 | +** --vt100 Translate <mark> and </mark> into ANSI/VT100 | |
| 2694 | +** escapes to highlight the contained text. | |
| 2560 | 2695 | */ |
| 2561 | 2696 | void test_html_to_text(void){ |
| 2562 | 2697 | Blob in, out; |
| 2563 | 2698 | int i; |
| 2699 | + int mFlags = 0; | |
| 2700 | + if( find_option("vt100",0,0)!=0 ) mFlags |= HTOT_VT100; | |
| 2564 | 2701 | |
| 2565 | 2702 | for(i=2; i<g.argc; i++){ |
| 2566 | 2703 | blob_read_from_file(&in, g.argv[i], ExtFILE); |
| 2567 | 2704 | blob_zero(&out); |
| 2568 | - html_to_plaintext(blob_str(&in), &out); | |
| 2705 | + html_to_plaintext(blob_str(&in), &out, mFlags); | |
| 2569 | 2706 | blob_reset(&in); |
| 2570 | 2707 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2571 | 2708 | blob_reset(&out); |
| 2572 | 2709 | } |
| 2573 | 2710 | } |
| 2574 | 2711 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,23 +23,43 @@ | |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Allowed wiki transformation operations |
| 27 | */ |
| 28 | #define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */ |
| 29 | #define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */ |
| 30 | #define WIKI_NOBLOCK 0x004 /* No block markup of any kind */ |
| 31 | #define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */ |
| 32 | #define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */ |
| 33 | #define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */ |
| 34 | #define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */ |
| 35 | #define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */ |
| 36 | #define WIKI_SAFE 0x100 /* Make the result safe for embedding */ |
| 37 | #define WIKI_TARGET_BLANK 0x200 /* Hyperlinks go to a new window */ |
| 38 | #define WIKI_NOBRACKET 0x400 /* Omit extra [..] around hyperlinks */ |
| 39 | #define WIKI_ADMIN 0x800 /* Ignore g.perm.Hyperlink */ |
| 40 | #endif |
| 41 | |
| 42 | |
| 43 | /* |
| 44 | ** These are the only markup attributes allowed. |
| 45 | */ |
| @@ -445,20 +465,20 @@ | |
| 445 | #define AT_NEWLINE 0x0010000 /* At start of a line */ |
| 446 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 447 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 448 | #define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */ |
| 449 | #define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */ |
| 450 | #define INLINE_MARKUP_ONLY 0x0200000 /* Allow only "inline" markup */ |
| 451 | #define IN_LIST 0x0400000 /* Within wiki <ul> or <ol> */ |
| 452 | |
| 453 | /* |
| 454 | ** Current state of the rendering engine |
| 455 | */ |
| 456 | typedef struct Renderer Renderer; |
| 457 | struct Renderer { |
| 458 | Blob *pOut; /* Output appended to this blob */ |
| 459 | int state; /* Flag that govern rendering */ |
| 460 | unsigned renderFlags; /* Flags from the client */ |
| 461 | int wikiList; /* Current wiki list type */ |
| 462 | int inVerbatim; /* True in <verbatim> mode */ |
| 463 | int preVerbState; /* Value of state prior to verbatim */ |
| 464 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +700,26 @@ | |
| 680 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 681 | int n; |
| 682 | if( z[0]=='<' ){ |
| 683 | n = html_tag_length(z); |
| 684 | if( n>0 ){ |
| 685 | *pTokenType = TOKEN_MARKUP; |
| 686 | return n; |
| 687 | }else{ |
| 688 | *pTokenType = TOKEN_CHARACTER; |
| 689 | return 1; |
| 690 | } |
| 691 | } |
| 692 | if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){ |
| 693 | *pTokenType = TOKEN_CHARACTER; |
| 694 | return 1; |
| 695 | } |
| 696 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 697 | if( z[0]=='\n' ){ |
| 698 | n = paragraphBreakLength(z); |
| 699 | if( n>0 ){ |
| 700 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +751,31 @@ | |
| 726 | if( n>0 ){ |
| 727 | *pTokenType = TOKEN_INDENT; |
| 728 | return n; |
| 729 | } |
| 730 | } |
| 731 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 732 | *pTokenType = TOKEN_LINK; |
| 733 | return n; |
| 734 | } |
| 735 | }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){ |
| 736 | *pTokenType = TOKEN_LINK; |
| 737 | return n; |
| 738 | } |
| 739 | *pTokenType = TOKEN_TEXT; |
| 740 | return 1 + textLength(z+1, p->state); |
| 741 | } |
| 742 | |
| @@ -746,13 +785,20 @@ | |
| 746 | ** z points to the start of a token. Return the number of |
| 747 | ** characters in that token. Write the token type into *pTokenType. |
| 748 | */ |
| 749 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 750 | int n; |
| 751 | if( z[0]=='[' && (n = linkLength(z))>0 ){ |
| 752 | *pTokenType = TOKEN_LINK; |
| 753 | return n; |
| 754 | } |
| 755 | *pTokenType = TOKEN_RAW; |
| 756 | return 1 + textLength(z+1, p->state); |
| 757 | } |
| 758 | |
| @@ -1249,12 +1295,16 @@ | |
| 1249 | ** [wiki:WikiPageName] |
| 1250 | ** |
| 1251 | ** [2010-02-27 07:13] |
| 1252 | ** |
| 1253 | ** [InterMap:Link] -> Interwiki link |
| 1254 | */ |
| 1255 | void wiki_resolve_hyperlink( |
| 1256 | Blob *pOut, /* Write the HTML output here */ |
| 1257 | int mFlags, /* Rendering option flags */ |
| 1258 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1259 | char *zClose, /* Write hyperlink closing text here */ |
| 1260 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1314,11 @@ | |
| 1264 | const char *zTerm = "</a>"; |
| 1265 | const char *z; |
| 1266 | char *zExtra = 0; |
| 1267 | const char *zExtraNS = 0; |
| 1268 | char *zRemote = 0; |
| 1269 | |
| 1270 | if( zTitle ){ |
| 1271 | zExtra = mprintf(" title='%h'", zTitle); |
| 1272 | zExtraNS = zExtra+1; |
| 1273 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1317,14 +1368,19 @@ | |
| 1317 | } |
| 1318 | } |
| 1319 | }else if( !in_this_repo(zTarget) ){ |
| 1320 | if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ |
| 1321 | zTerm = ""; |
| 1322 | }else{ |
| 1323 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1324 | zTerm = "]</span>"; |
| 1325 | } |
| 1326 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1327 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1328 | zTerm = "]</a>"; |
| 1329 | }else{ |
| 1330 | zTerm = ""; |
| @@ -1341,33 +1397,40 @@ | |
| 1341 | }else{ |
| 1342 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1343 | } |
| 1344 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1345 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1346 | /* Dates or date-and-times in ISO8610 resolve to a link to the |
| 1347 | ** timeline for that date */ |
| 1348 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1349 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1350 | /* If none of the above, and if rendering links for markdown, then |
| 1351 | ** create a link to the literal text of the target */ |
| 1352 | blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra); |
| 1353 | }else if( zOrig && zTarget>=&zOrig[2] |
| 1354 | && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){ |
| 1355 | /* If the hyperlink markup is not preceded by whitespace, then it |
| 1356 | ** is probably a C-language subscript or similar, not really a |
| 1357 | ** hyperlink. Just ignore it. */ |
| 1358 | zTerm = ""; |
| 1359 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1360 | /* Also ignore the link if various flags are set */ |
| 1361 | zTerm = ""; |
| 1362 | }else{ |
| 1363 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1364 | zTerm = "</span>"; |
| 1365 | } |
| 1366 | if( zExtra ) fossil_free(zExtra); |
| 1367 | assert( (int)strlen(zTerm)<nClose ); |
| 1368 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1369 | } |
| 1370 | |
| 1371 | /* |
| 1372 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1373 | ** Return true if it does seem valid and false if not. |
| @@ -1465,11 +1528,10 @@ | |
| 1465 | */ |
| 1466 | static void wiki_render(Renderer *p, char *z){ |
| 1467 | int tokenType; |
| 1468 | ParsedMarkup markup; |
| 1469 | int n; |
| 1470 | int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0; |
| 1471 | int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; |
| 1472 | int linksOnly = (p->state & WIKI_LINKSONLY)!=0; |
| 1473 | char *zOrig = z; |
| 1474 | |
| 1475 | /* Make sure the attribute constants and names still align |
| @@ -1483,22 +1545,17 @@ | |
| 1483 | n = nextWikiToken(z, p, &tokenType); |
| 1484 | } |
| 1485 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| 1486 | switch( tokenType ){ |
| 1487 | case TOKEN_PARAGRAPH: { |
| 1488 | if( inlineOnly ){ |
| 1489 | /* blob_append_string(p->pOut, " ¶ "); */ |
| 1490 | blob_append_string(p->pOut, " "); |
| 1491 | }else{ |
| 1492 | if( p->wikiList ){ |
| 1493 | popStackToTag(p, p->wikiList); |
| 1494 | p->wikiList = 0; |
| 1495 | } |
| 1496 | endAutoParagraph(p); |
| 1497 | blob_append_string(p->pOut, "\n\n"); |
| 1498 | p->wantAutoParagraph = 1; |
| 1499 | } |
| 1500 | p->state |= AT_PARAGRAPH|AT_NEWLINE; |
| 1501 | break; |
| 1502 | } |
| 1503 | case TOKEN_NEWLINE: { |
| 1504 | if( p->renderFlags & WIKI_NEWLINE ){ |
| @@ -1508,86 +1565,91 @@ | |
| 1508 | } |
| 1509 | p->state |= AT_NEWLINE; |
| 1510 | break; |
| 1511 | } |
| 1512 | case TOKEN_BUL_LI: { |
| 1513 | if( inlineOnly ){ |
| 1514 | blob_append_string(p->pOut, " • "); |
| 1515 | }else{ |
| 1516 | if( p->wikiList!=MARKUP_UL ){ |
| 1517 | if( p->wikiList ){ |
| 1518 | popStackToTag(p, p->wikiList); |
| 1519 | } |
| 1520 | endAutoParagraph(p); |
| 1521 | pushStack(p, MARKUP_UL); |
| 1522 | blob_append_string(p->pOut, "<ul>"); |
| 1523 | p->wikiList = MARKUP_UL; |
| 1524 | } |
| 1525 | popStackToTag(p, MARKUP_LI); |
| 1526 | startAutoParagraph(p); |
| 1527 | pushStack(p, MARKUP_LI); |
| 1528 | blob_append_string(p->pOut, "<li>"); |
| 1529 | } |
| 1530 | break; |
| 1531 | } |
| 1532 | case TOKEN_NUM_LI: { |
| 1533 | if( inlineOnly ){ |
| 1534 | blob_append_string(p->pOut, " # "); |
| 1535 | }else{ |
| 1536 | if( p->wikiList!=MARKUP_OL ){ |
| 1537 | if( p->wikiList ){ |
| 1538 | popStackToTag(p, p->wikiList); |
| 1539 | } |
| 1540 | endAutoParagraph(p); |
| 1541 | pushStack(p, MARKUP_OL); |
| 1542 | blob_append_string(p->pOut, "<ol>"); |
| 1543 | p->wikiList = MARKUP_OL; |
| 1544 | } |
| 1545 | popStackToTag(p, MARKUP_LI); |
| 1546 | startAutoParagraph(p); |
| 1547 | pushStack(p, MARKUP_LI); |
| 1548 | blob_append_string(p->pOut, "<li>"); |
| 1549 | } |
| 1550 | break; |
| 1551 | } |
| 1552 | case TOKEN_ENUM: { |
| 1553 | if( inlineOnly ){ |
| 1554 | blob_appendf(p->pOut, " (%d) ", atoi(z)); |
| 1555 | }else{ |
| 1556 | if( p->wikiList!=MARKUP_OL ){ |
| 1557 | if( p->wikiList ){ |
| 1558 | popStackToTag(p, p->wikiList); |
| 1559 | } |
| 1560 | endAutoParagraph(p); |
| 1561 | pushStack(p, MARKUP_OL); |
| 1562 | blob_append_string(p->pOut, "<ol>"); |
| 1563 | p->wikiList = MARKUP_OL; |
| 1564 | } |
| 1565 | popStackToTag(p, MARKUP_LI); |
| 1566 | startAutoParagraph(p); |
| 1567 | pushStack(p, MARKUP_LI); |
| 1568 | blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); |
| 1569 | } |
| 1570 | break; |
| 1571 | } |
| 1572 | case TOKEN_INDENT: { |
| 1573 | if( !inlineOnly ){ |
| 1574 | assert( p->wikiList==0 ); |
| 1575 | pushStack(p, MARKUP_BLOCKQUOTE); |
| 1576 | blob_append_string(p->pOut, "<blockquote>"); |
| 1577 | p->wantAutoParagraph = 0; |
| 1578 | p->wikiList = MARKUP_BLOCKQUOTE; |
| 1579 | } |
| 1580 | break; |
| 1581 | } |
| 1582 | case TOKEN_CHARACTER: { |
| 1583 | startAutoParagraph(p); |
| 1584 | if( z[0]=='<' ){ |
| 1585 | blob_append_string(p->pOut, "<"); |
| 1586 | }else if( z[0]=='&' ){ |
| 1587 | blob_append_string(p->pOut, "&"); |
| 1588 | } |
| 1589 | break; |
| 1590 | } |
| 1591 | case TOKEN_LINK: { |
| 1592 | char *zTarget; |
| 1593 | char *zDisplay = 0; |
| @@ -1596,10 +1658,11 @@ | |
| 1596 | char zClose[20]; |
| 1597 | char cS1 = 0; |
| 1598 | int iS1 = 0; |
| 1599 | |
| 1600 | startAutoParagraph(p); |
| 1601 | zTarget = &z[1]; |
| 1602 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1603 | if( z[i]=='|' && zDisplay==0 ){ |
| 1604 | zDisplay = &z[i+1]; |
| 1605 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1675,11 @@ | |
| 1612 | if( zDisplay==0 ){ |
| 1613 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1614 | }else{ |
| 1615 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1616 | } |
| 1617 | wiki_resolve_hyperlink(p->pOut, p->state, |
| 1618 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1619 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1620 | if( cS1 ) z[iS1] = cS1; |
| 1621 | if( zClose[0]!=']' ){ |
| 1622 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,14 +1765,22 @@ | |
| 1702 | |
| 1703 | /* Render invalid markup literally. The markup appears in the |
| 1704 | ** final output as plain text. |
| 1705 | */ |
| 1706 | if( markup.iCode==MARKUP_INVALID ){ |
| 1707 | unparseMarkup(&markup); |
| 1708 | startAutoParagraph(p); |
| 1709 | blob_append_string(p->pOut, "<"); |
| 1710 | n = 1; |
| 1711 | }else |
| 1712 | |
| 1713 | /* If the markup is not font-change markup ignore it if the |
| 1714 | ** font-change-only flag is set. |
| 1715 | */ |
| @@ -1723,16 +1794,10 @@ | |
| 1723 | }else{ |
| 1724 | p->state &= ~ALLOW_WIKI; |
| 1725 | } |
| 1726 | }else |
| 1727 | |
| 1728 | /* Ignore block markup for in-line rendering. |
| 1729 | */ |
| 1730 | if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ |
| 1731 | /* Do nothing */ |
| 1732 | }else |
| 1733 | |
| 1734 | /* Generate end-tags */ |
| 1735 | if( markup.endTag ){ |
| 1736 | popStackToTag(p, markup.iCode); |
| 1737 | }else |
| 1738 | |
| @@ -1814,10 +1879,11 @@ | |
| 1814 | }else |
| 1815 | { |
| 1816 | if( markup.iType==MUTYPE_FONT ){ |
| 1817 | startAutoParagraph(p); |
| 1818 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1819 | p->wantAutoParagraph = 0; |
| 1820 | } |
| 1821 | if( markup.iCode==MARKUP_HR |
| 1822 | || markup.iCode==MARKUP_H1 |
| 1823 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1910,14 @@ | |
| 1844 | ** Transform the text in the pIn blob. Write the results |
| 1845 | ** into the pOut blob. The pOut blob should already be |
| 1846 | ** initialized. The output is merely appended to pOut. |
| 1847 | ** If pOut is NULL, then the output is appended to the CGI |
| 1848 | ** reply. |
| 1849 | */ |
| 1850 | void wiki_convert(Blob *pIn, Blob *pOut, int flags){ |
| 1851 | Renderer renderer; |
| 1852 | |
| 1853 | memset(&renderer, 0, sizeof(renderer)); |
| 1854 | renderer.renderFlags = flags; |
| 1855 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1941,11 @@ | |
| 1873 | while( renderer.nStack ){ |
| 1874 | popStack(&renderer); |
| 1875 | } |
| 1876 | blob_append_char(renderer.pOut, '\n'); |
| 1877 | free(renderer.aStack); |
| 1878 | } |
| 1879 | |
| 1880 | /* |
| 1881 | ** COMMAND: test-wiki-render |
| 1882 | ** |
| @@ -1886,45 +1955,84 @@ | |
| 1886 | ** the resulting HTML on standard output. |
| 1887 | ** |
| 1888 | ** Options: |
| 1889 | ** --buttons Set the WIKI_BUTTONS flag |
| 1890 | ** --dark-pikchr Render pikchrs in dark mode |
| 1891 | ** --htmlonly Set the WIKI_HTMLONLY flag |
| 1892 | ** --inline Set the WIKI_INLINE flag |
| 1893 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1894 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1895 | ** --noblock Set the WIKI_NOBLOCK flag |
| 1896 | ** --text Run the output through html_to_plaintext(). |
| 1897 | */ |
| 1898 | void test_wiki_render(void){ |
| 1899 | Blob in, out; |
| 1900 | int flags = 0; |
| 1901 | int bText; |
| 1902 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1903 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1904 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1905 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1906 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| 1907 | if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK; |
| 1908 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1909 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1910 | } |
| 1911 | bText = find_option("text",0,0)!=0; |
| 1912 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1913 | verify_all_options(); |
| 1914 | if( g.argc!=3 ) usage("FILE"); |
| 1915 | blob_zero(&out); |
| 1916 | blob_read_from_file(&in, g.argv[2], ExtFILE); |
| 1917 | wiki_convert(&in, &out, flags); |
| 1918 | if( bText ){ |
| 1919 | Blob txt; |
| 1920 | blob_init(&txt, 0, 0); |
| 1921 | html_to_plaintext(blob_str(&out),&txt); |
| 1922 | blob_reset(&out); |
| 1923 | out = txt; |
| 1924 | } |
| 1925 | blob_write_to_file(&out, "-"); |
| 1926 | } |
| 1927 | |
| 1928 | /* |
| 1929 | ** COMMAND: test-markdown-render |
| 1930 | ** |
| @@ -1960,11 +2068,11 @@ | |
| 1960 | safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED ); |
| 1961 | safe_html(&out); |
| 1962 | if( bText ){ |
| 1963 | Blob txt; |
| 1964 | blob_init(&txt, 0, 0); |
| 1965 | html_to_plaintext(blob_str(&out), &txt); |
| 1966 | blob_reset(&out); |
| 1967 | out = txt; |
| 1968 | } |
| 1969 | blob_write_to_file(&out, "-"); |
| 1970 | blob_reset(&in); |
| @@ -2024,33 +2132,30 @@ | |
| 2024 | ** [target|...] |
| 2025 | ** |
| 2026 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2027 | ** name. For each such hyperlink found, add an entry to the |
| 2028 | ** backlink table. |
| 2029 | */ |
| 2030 | void wiki_extract_links( |
| 2031 | char *z, /* The wiki text from which to extract links */ |
| 2032 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2033 | int flags /* wiki parsing flags */ |
| 2034 | ){ |
| 2035 | Renderer renderer; |
| 2036 | int tokenType; |
| 2037 | ParsedMarkup markup; |
| 2038 | int n; |
| 2039 | int inlineOnly; |
| 2040 | int wikiHtmlOnly = 0; |
| 2041 | |
| 2042 | memset(&renderer, 0, sizeof(renderer)); |
| 2043 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 2044 | if( flags & WIKI_NOBLOCK ){ |
| 2045 | renderer.state |= INLINE_MARKUP_ONLY; |
| 2046 | } |
| 2047 | if( wikiUsesHtml() ){ |
| 2048 | renderer.state |= WIKI_HTMLONLY; |
| 2049 | wikiHtmlOnly = 1; |
| 2050 | } |
| 2051 | inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0; |
| 2052 | |
| 2053 | while( z[0] ){ |
| 2054 | if( wikiHtmlOnly ){ |
| 2055 | n = nextRawToken(z, &renderer, &tokenType); |
| 2056 | }else{ |
| @@ -2127,16 +2232,10 @@ | |
| 2127 | }else{ |
| 2128 | renderer.state &= ~ALLOW_WIKI; |
| 2129 | } |
| 2130 | }else |
| 2131 | |
| 2132 | /* Ignore block markup for in-line rendering. |
| 2133 | */ |
| 2134 | if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){ |
| 2135 | /* Do nothing */ |
| 2136 | }else |
| 2137 | |
| 2138 | /* Generate end-tags */ |
| 2139 | if( markup.endTag ){ |
| 2140 | popStackToTag(&renderer, markup.iCode); |
| 2141 | }else |
| 2142 | |
| @@ -2175,10 +2274,11 @@ | |
| 2175 | } |
| 2176 | } |
| 2177 | z += n; |
| 2178 | } |
| 2179 | free(renderer.aStack); |
| 2180 | } |
| 2181 | |
| 2182 | /* |
| 2183 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2184 | */ |
| @@ -2427,33 +2527,61 @@ | |
| 2427 | blob_reset(&in); |
| 2428 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2429 | blob_reset(&out); |
| 2430 | } |
| 2431 | } |
| 2432 | |
| 2433 | /* |
| 2434 | ** Remove all HTML markup from the input text. The output written into |
| 2435 | ** pOut is pure text. |
| 2436 | ** |
| 2437 | ** Put the title on the first line, if there is any <title> markup. |
| 2438 | ** If there is no <title>, then create a blank first line. |
| 2439 | */ |
| 2440 | void html_to_plaintext(const char *zIn, Blob *pOut){ |
| 2441 | int n; |
| 2442 | int i, j; |
| 2443 | int inTitle = 0; /* True between <title>...</title> */ |
| 2444 | int seenText = 0; /* True after first non-whitespace seen */ |
| 2445 | int nNL = 0; /* Number of \n characters at the end of pOut */ |
| 2446 | int nWS = 0; /* True if pOut ends with whitespace */ |
| 2447 | while( fossil_isspace(zIn[0]) ) zIn++; |
| 2448 | while( zIn[0] ){ |
| 2449 | n = html_token_length(zIn); |
| 2450 | if( zIn[0]=='<' && n>1 ){ |
| 2451 | int isCloseTag; |
| 2452 | int eTag; |
| 2453 | int eType; |
| 2454 | char zTag[32]; |
| 2455 | isCloseTag = zIn[1]=='/'; |
| 2456 | for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ |
| 2457 | zTag[i] = fossil_tolower(zIn[j]); |
| 2458 | } |
| 2459 | zTag[i] = 0; |
| @@ -2467,33 +2595,44 @@ | |
| 2467 | zIn += n; |
| 2468 | } |
| 2469 | if( zIn[0]=='<' ) zIn += n; |
| 2470 | continue; |
| 2471 | } |
| 2472 | if( eTag==MARKUP_TITLE ){ |
| 2473 | inTitle = !isCloseTag; |
| 2474 | } |
| 2475 | if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ |
| 2476 | if( nNL==0 ){ |
| 2477 | blob_append_char(pOut, '\n'); |
| 2478 | nNL++; |
| 2479 | } |
| 2480 | nWS = 1; |
| 2481 | } |
| 2482 | }else if( fossil_isspace(zIn[0]) ){ |
| 2483 | if( seenText ){ |
| 2484 | nNL = 0; |
| 2485 | if( !inTitle ){ /* '\n' -> ' ' within <title> */ |
| 2486 | for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++; |
| 2487 | } |
| 2488 | if( !nWS ){ |
| 2489 | blob_append_char(pOut, nNL ? '\n' : ' '); |
| 2490 | nWS = 1; |
| 2491 | } |
| 2492 | } |
| 2493 | }else if( zIn[0]=='&' ){ |
| 2494 | u32 c = '?'; |
| 2495 | if( zIn[1]=='#' ){ |
| 2496 | c = atoi(&zIn[2]); |
| 2497 | if( c==0 ) c = '?'; |
| 2498 | }else{ |
| 2499 | static const struct { int n; u32 c; char *z; } aEntity[] = { |
| @@ -2509,65 +2648,63 @@ | |
| 2509 | c = aEntity[jj].c; |
| 2510 | break; |
| 2511 | } |
| 2512 | } |
| 2513 | } |
| 2514 | if( fossil_isspace(c) ){ |
| 2515 | if( nWS==0 && seenText ) blob_append_char(pOut, c); |
| 2516 | nWS = 1; |
| 2517 | nNL = c=='\n'; |
| 2518 | }else{ |
| 2519 | if( !seenText && !inTitle ) blob_append_char(pOut, '\n'); |
| 2520 | seenText = 1; |
| 2521 | nNL = nWS = 0; |
| 2522 | if( c<0x00080 ){ |
| 2523 | blob_append_char(pOut, c & 0xff); |
| 2524 | }else if( c<0x00800 ){ |
| 2525 | blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f)); |
| 2526 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2527 | }else if( c<0x10000 ){ |
| 2528 | blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f)); |
| 2529 | blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); |
| 2530 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2531 | }else{ |
| 2532 | blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07)); |
| 2533 | blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f)); |
| 2534 | blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); |
| 2535 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2536 | } |
| 2537 | } |
| 2538 | }else{ |
| 2539 | if( !seenText && !inTitle ) blob_append_char(pOut, '\n'); |
| 2540 | seenText = 1; |
| 2541 | nNL = nWS = 0; |
| 2542 | blob_append(pOut, zIn, n); |
| 2543 | } |
| 2544 | zIn += n; |
| 2545 | } |
| 2546 | if( nNL==0 ) blob_append_char(pOut, '\n'); |
| 2547 | } |
| 2548 | |
| 2549 | /* |
| 2550 | ** COMMAND: test-html-to-text |
| 2551 | ** |
| 2552 | ** Usage: %fossil test-html-to-text FILE ... |
| 2553 | ** |
| 2554 | ** Read all files named on the command-line. Convert the file |
| 2555 | ** content from HTML to text and write the results on standard |
| 2556 | ** output. |
| 2557 | ** |
| 2558 | ** This command is intended as a test and debug interface for |
| 2559 | ** the html_to_plaintext() routine. |
| 2560 | */ |
| 2561 | void test_html_to_text(void){ |
| 2562 | Blob in, out; |
| 2563 | int i; |
| 2564 | |
| 2565 | for(i=2; i<g.argc; i++){ |
| 2566 | blob_read_from_file(&in, g.argv[i], ExtFILE); |
| 2567 | blob_zero(&out); |
| 2568 | html_to_plaintext(blob_str(&in), &out); |
| 2569 | blob_reset(&in); |
| 2570 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2571 | blob_reset(&out); |
| 2572 | } |
| 2573 | } |
| 2574 |
| --- src/wikiformat.c | |
| +++ src/wikiformat.c | |
| @@ -23,23 +23,43 @@ | |
| 23 | |
| 24 | #if INTERFACE |
| 25 | /* |
| 26 | ** Allowed wiki transformation operations |
| 27 | */ |
| 28 | #define WIKI_HTMLONLY 0x0001 /* HTML markup only. No wiki */ |
| 29 | #define WIKI_INLINE 0x0002 /* Do not surround with <p>..</p> */ |
| 30 | /* avalable for reuse: 0x0004 --- formerly WIKI_NOBLOCK */ |
| 31 | #define WIKI_BUTTONS 0x0008 /* Allow sub-menu buttons */ |
| 32 | #define WIKI_NOBADLINKS 0x0010 /* Ignore broken hyperlinks */ |
| 33 | #define WIKI_LINKSONLY 0x0020 /* No markup. Only decorate links */ |
| 34 | #define WIKI_NEWLINE 0x0040 /* Honor \n - break lines at each \n */ |
| 35 | #define WIKI_MARKDOWNLINKS 0x0080 /* Resolve hyperlinks as in markdown */ |
| 36 | #define WIKI_SAFE 0x0100 /* Make the result safe for embedding */ |
| 37 | #define WIKI_TARGET_BLANK 0x0200 /* Hyperlinks go to a new window */ |
| 38 | #define WIKI_NOBRACKET 0x0400 /* Omit extra [..] around hyperlinks */ |
| 39 | #define WIKI_ADMIN 0x0800 /* Ignore g.perm.Hyperlink */ |
| 40 | #define WIKI_MARK 0x1000 /* Add <mark>..</mark> around problems */ |
| 41 | |
| 42 | /* |
| 43 | ** Return values from wiki_convert |
| 44 | */ |
| 45 | #define RENDER_LINK 0x0001 /* One or more hyperlinks rendered */ |
| 46 | #define RENDER_ENTITY 0x0002 /* One or more HTML entities (ex: <) */ |
| 47 | #define RENDER_TAG 0x0004 /* One or more HTML tags */ |
| 48 | #define RENDER_BLOCKTAG 0x0008 /* One or more HTML block tags (ex: <p>) */ |
| 49 | #define RENDER_BLOCK 0x0010 /* Block wiki (paragraphs, etc.) */ |
| 50 | #define RENDER_MARK 0x0020 /* Output contains <mark>..</mark> */ |
| 51 | #define RENDER_BADLINK 0x0100 /* Bad hyperlink syntax seen */ |
| 52 | #define RENDER_BADTARGET 0x0200 /* Bad hyperlink target */ |
| 53 | #define RENDER_BADTAG 0x0400 /* Bad HTML tag or tag syntax */ |
| 54 | #define RENDER_BADENTITY 0x0800 /* Bad HTML entity syntax */ |
| 55 | #define RENDER_BADHTML 0x1000 /* Bad HTML seen */ |
| 56 | #define RENDER_ERROR 0x8000 /* Some other kind of error */ |
| 57 | /* Composite values: */ |
| 58 | #define RENDER_ANYERROR 0x9f00 /* Mask for any kind of error */ |
| 59 | |
| 60 | #endif /* INTERFACE */ |
| 61 | |
| 62 | |
| 63 | /* |
| 64 | ** These are the only markup attributes allowed. |
| 65 | */ |
| @@ -445,20 +465,20 @@ | |
| 465 | #define AT_NEWLINE 0x0010000 /* At start of a line */ |
| 466 | #define AT_PARAGRAPH 0x0020000 /* At start of a paragraph */ |
| 467 | #define ALLOW_WIKI 0x0040000 /* Allow wiki markup */ |
| 468 | #define ALLOW_LINKS 0x0080000 /* Allow [...] hyperlinks */ |
| 469 | #define FONT_MARKUP_ONLY 0x0100000 /* Only allow MUTYPE_FONT markup */ |
| 470 | #define IN_LIST 0x0200000 /* Within wiki <ul> or <ol> */ |
| 471 | |
| 472 | /* |
| 473 | ** Current state of the rendering engine |
| 474 | */ |
| 475 | typedef struct Renderer Renderer; |
| 476 | struct Renderer { |
| 477 | Blob *pOut; /* Output appended to this blob */ |
| 478 | int state; /* Flag that govern rendering */ |
| 479 | int mRender; /* Mask of RENDER_* values to return */ |
| 480 | unsigned renderFlags; /* Flags from the client */ |
| 481 | int wikiList; /* Current wiki list type */ |
| 482 | int inVerbatim; /* True in <verbatim> mode */ |
| 483 | int preVerbState; /* Value of state prior to verbatim */ |
| 484 | int wantAutoParagraph; /* True if a <p> is desired */ |
| @@ -680,21 +700,26 @@ | |
| 700 | static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){ |
| 701 | int n; |
| 702 | if( z[0]=='<' ){ |
| 703 | n = html_tag_length(z); |
| 704 | if( n>0 ){ |
| 705 | p->mRender |= RENDER_TAG; |
| 706 | *pTokenType = TOKEN_MARKUP; |
| 707 | return n; |
| 708 | }else{ |
| 709 | p->mRender |= RENDER_BADTAG; |
| 710 | *pTokenType = TOKEN_CHARACTER; |
| 711 | return 1; |
| 712 | } |
| 713 | } |
| 714 | if( z[0]=='&' ){ |
| 715 | p->mRender |= RENDER_ENTITY; |
| 716 | if( (p->inVerbatim || !isElement(z)) ){ |
| 717 | *pTokenType = TOKEN_CHARACTER; |
| 718 | return 1; |
| 719 | } |
| 720 | } |
| 721 | if( (p->state & ALLOW_WIKI)!=0 ){ |
| 722 | if( z[0]=='\n' ){ |
| 723 | n = paragraphBreakLength(z); |
| 724 | if( n>0 ){ |
| 725 | *pTokenType = TOKEN_PARAGRAPH; |
| @@ -726,17 +751,31 @@ | |
| 751 | if( n>0 ){ |
| 752 | *pTokenType = TOKEN_INDENT; |
| 753 | return n; |
| 754 | } |
| 755 | } |
| 756 | if( z[0]=='[' ){ |
| 757 | if( (n = linkLength(z))>0 ){ |
| 758 | *pTokenType = TOKEN_LINK; |
| 759 | return n; |
| 760 | }else if( p->state & WIKI_MARK ){ |
| 761 | blob_append_string(p->pOut, "<mark>"); |
| 762 | p->mRender |= RENDER_BADLINK|RENDER_MARK; |
| 763 | }else{ |
| 764 | p->mRender |= RENDER_BADLINK; |
| 765 | } |
| 766 | } |
| 767 | }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){ |
| 768 | if( (n = linkLength(z))>0 ){ |
| 769 | *pTokenType = TOKEN_LINK; |
| 770 | return n; |
| 771 | }else if( p->state & WIKI_MARK ){ |
| 772 | blob_append_string(p->pOut, "<mark>"); |
| 773 | p->mRender |= RENDER_BADLINK|RENDER_MARK; |
| 774 | }else{ |
| 775 | p->mRender |= RENDER_BADLINK; |
| 776 | } |
| 777 | } |
| 778 | *pTokenType = TOKEN_TEXT; |
| 779 | return 1 + textLength(z+1, p->state); |
| 780 | } |
| 781 | |
| @@ -746,13 +785,20 @@ | |
| 785 | ** z points to the start of a token. Return the number of |
| 786 | ** characters in that token. Write the token type into *pTokenType. |
| 787 | */ |
| 788 | static int nextRawToken(const char *z, Renderer *p, int *pTokenType){ |
| 789 | int n; |
| 790 | if( z[0]=='[' ){ |
| 791 | if( (n = linkLength(z))>0 ){ |
| 792 | *pTokenType = TOKEN_LINK; |
| 793 | return n; |
| 794 | }else if( p->state & WIKI_MARK ){ |
| 795 | blob_append_string(p->pOut, "<mark>"); |
| 796 | p->mRender |= RENDER_BADLINK|RENDER_MARK; |
| 797 | }else{ |
| 798 | p->mRender |= RENDER_BADLINK; |
| 799 | } |
| 800 | } |
| 801 | *pTokenType = TOKEN_RAW; |
| 802 | return 1 + textLength(z+1, p->state); |
| 803 | } |
| 804 | |
| @@ -1249,12 +1295,16 @@ | |
| 1295 | ** [wiki:WikiPageName] |
| 1296 | ** |
| 1297 | ** [2010-02-27 07:13] |
| 1298 | ** |
| 1299 | ** [InterMap:Link] -> Interwiki link |
| 1300 | ** |
| 1301 | ** The return value is a mask of RENDER_* values indicating what happened. |
| 1302 | ** Probably the return value is 0 on success and RENDER_BADTARGET or |
| 1303 | ** RENDER_BADLINK if there are problems. |
| 1304 | */ |
| 1305 | int wiki_resolve_hyperlink( |
| 1306 | Blob *pOut, /* Write the HTML output here */ |
| 1307 | int mFlags, /* Rendering option flags */ |
| 1308 | const char *zTarget, /* Hyperlink target; text within [...] */ |
| 1309 | char *zClose, /* Write hyperlink closing text here */ |
| 1310 | int nClose, /* Bytes available in zClose[] */ |
| @@ -1264,10 +1314,11 @@ | |
| 1314 | const char *zTerm = "</a>"; |
| 1315 | const char *z; |
| 1316 | char *zExtra = 0; |
| 1317 | const char *zExtraNS = 0; |
| 1318 | char *zRemote = 0; |
| 1319 | int rc = 0; |
| 1320 | |
| 1321 | if( zTitle ){ |
| 1322 | zExtra = mprintf(" title='%h'", zTitle); |
| 1323 | zExtraNS = zExtra+1; |
| 1324 | }else if( mFlags & WIKI_TARGET_BLANK ){ |
| @@ -1317,14 +1368,19 @@ | |
| 1368 | } |
| 1369 | } |
| 1370 | }else if( !in_this_repo(zTarget) ){ |
| 1371 | if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){ |
| 1372 | zTerm = ""; |
| 1373 | }else if( (mFlags & WIKI_MARK)!=0 ){ |
| 1374 | blob_appendf(pOut, "<mark>%s", zLB); |
| 1375 | zTerm = "]</mark>"; |
| 1376 | rc |= RENDER_MARK; |
| 1377 | }else{ |
| 1378 | blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB); |
| 1379 | zTerm = "]</span>"; |
| 1380 | } |
| 1381 | rc |= RENDER_BADTARGET; |
| 1382 | }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){ |
| 1383 | blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB); |
| 1384 | zTerm = "]</a>"; |
| 1385 | }else{ |
| 1386 | zTerm = ""; |
| @@ -1341,33 +1397,40 @@ | |
| 1397 | }else{ |
| 1398 | blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra); |
| 1399 | } |
| 1400 | }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' |
| 1401 | && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ |
| 1402 | /* Dates or date-and-times in ISO8601 resolve to a link to the |
| 1403 | ** timeline for that date */ |
| 1404 | blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra); |
| 1405 | }else if( mFlags & WIKI_MARKDOWNLINKS ){ |
| 1406 | /* If none of the above, and if rendering links for markdown, then |
| 1407 | ** create a link to the literal text of the target */ |
| 1408 | blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra); |
| 1409 | }else if( mFlags & WIKI_MARK ){ |
| 1410 | blob_appendf(pOut, "<mark>["); |
| 1411 | zTerm = "]</mark>"; |
| 1412 | rc |= RENDER_BADTARGET|RENDER_MARK; |
| 1413 | }else if( zOrig && zTarget>=&zOrig[2] |
| 1414 | && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){ |
| 1415 | /* If the hyperlink markup is not preceded by whitespace, then it |
| 1416 | ** is probably a C-language subscript or similar, not really a |
| 1417 | ** hyperlink. Just ignore it. */ |
| 1418 | zTerm = ""; |
| 1419 | }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){ |
| 1420 | /* Also ignore the link if various flags are set */ |
| 1421 | zTerm = ""; |
| 1422 | rc |= RENDER_BADTARGET; |
| 1423 | }else{ |
| 1424 | blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget); |
| 1425 | zTerm = "</span>"; |
| 1426 | rc |= RENDER_BADTARGET; |
| 1427 | } |
| 1428 | if( zExtra ) fossil_free(zExtra); |
| 1429 | assert( (int)strlen(zTerm)<nClose ); |
| 1430 | sqlite3_snprintf(nClose, zClose, "%s", zTerm); |
| 1431 | return rc; |
| 1432 | } |
| 1433 | |
| 1434 | /* |
| 1435 | ** Check zTarget to see if it looks like a valid hyperlink target. |
| 1436 | ** Return true if it does seem valid and false if not. |
| @@ -1465,11 +1528,10 @@ | |
| 1528 | */ |
| 1529 | static void wiki_render(Renderer *p, char *z){ |
| 1530 | int tokenType; |
| 1531 | ParsedMarkup markup; |
| 1532 | int n; |
| 1533 | int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0; |
| 1534 | int linksOnly = (p->state & WIKI_LINKSONLY)!=0; |
| 1535 | char *zOrig = z; |
| 1536 | |
| 1537 | /* Make sure the attribute constants and names still align |
| @@ -1483,22 +1545,17 @@ | |
| 1545 | n = nextWikiToken(z, p, &tokenType); |
| 1546 | } |
| 1547 | p->state &= ~(AT_NEWLINE|AT_PARAGRAPH); |
| 1548 | switch( tokenType ){ |
| 1549 | case TOKEN_PARAGRAPH: { |
| 1550 | if( p->wikiList ){ |
| 1551 | popStackToTag(p, p->wikiList); |
| 1552 | p->wikiList = 0; |
| 1553 | } |
| 1554 | endAutoParagraph(p); |
| 1555 | blob_append_string(p->pOut, "\n\n"); |
| 1556 | p->wantAutoParagraph = 1; |
| 1557 | p->state |= AT_PARAGRAPH|AT_NEWLINE; |
| 1558 | break; |
| 1559 | } |
| 1560 | case TOKEN_NEWLINE: { |
| 1561 | if( p->renderFlags & WIKI_NEWLINE ){ |
| @@ -1508,86 +1565,91 @@ | |
| 1565 | } |
| 1566 | p->state |= AT_NEWLINE; |
| 1567 | break; |
| 1568 | } |
| 1569 | case TOKEN_BUL_LI: { |
| 1570 | p->mRender |= RENDER_BLOCK; |
| 1571 | if( p->wikiList!=MARKUP_UL ){ |
| 1572 | if( p->wikiList ){ |
| 1573 | popStackToTag(p, p->wikiList); |
| 1574 | } |
| 1575 | endAutoParagraph(p); |
| 1576 | pushStack(p, MARKUP_UL); |
| 1577 | blob_append_string(p->pOut, "<ul>"); |
| 1578 | p->wikiList = MARKUP_UL; |
| 1579 | } |
| 1580 | popStackToTag(p, MARKUP_LI); |
| 1581 | startAutoParagraph(p); |
| 1582 | pushStack(p, MARKUP_LI); |
| 1583 | blob_append_string(p->pOut, "<li>"); |
| 1584 | break; |
| 1585 | } |
| 1586 | case TOKEN_NUM_LI: { |
| 1587 | p->mRender |= RENDER_BLOCK; |
| 1588 | if( p->wikiList!=MARKUP_OL ){ |
| 1589 | if( p->wikiList ){ |
| 1590 | popStackToTag(p, p->wikiList); |
| 1591 | } |
| 1592 | endAutoParagraph(p); |
| 1593 | pushStack(p, MARKUP_OL); |
| 1594 | blob_append_string(p->pOut, "<ol>"); |
| 1595 | p->wikiList = MARKUP_OL; |
| 1596 | } |
| 1597 | popStackToTag(p, MARKUP_LI); |
| 1598 | startAutoParagraph(p); |
| 1599 | pushStack(p, MARKUP_LI); |
| 1600 | blob_append_string(p->pOut, "<li>"); |
| 1601 | break; |
| 1602 | } |
| 1603 | case TOKEN_ENUM: { |
| 1604 | p->mRender |= RENDER_BLOCK; |
| 1605 | if( p->wikiList!=MARKUP_OL ){ |
| 1606 | if( p->wikiList ){ |
| 1607 | popStackToTag(p, p->wikiList); |
| 1608 | } |
| 1609 | endAutoParagraph(p); |
| 1610 | pushStack(p, MARKUP_OL); |
| 1611 | blob_append_string(p->pOut, "<ol>"); |
| 1612 | p->wikiList = MARKUP_OL; |
| 1613 | } |
| 1614 | popStackToTag(p, MARKUP_LI); |
| 1615 | startAutoParagraph(p); |
| 1616 | pushStack(p, MARKUP_LI); |
| 1617 | blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z)); |
| 1618 | break; |
| 1619 | } |
| 1620 | case TOKEN_INDENT: { |
| 1621 | p->mRender |= RENDER_BLOCK; |
| 1622 | assert( p->wikiList==0 ); |
| 1623 | pushStack(p, MARKUP_BLOCKQUOTE); |
| 1624 | blob_append_string(p->pOut, "<blockquote>"); |
| 1625 | p->wantAutoParagraph = 0; |
| 1626 | p->wikiList = MARKUP_BLOCKQUOTE; |
| 1627 | break; |
| 1628 | } |
| 1629 | case TOKEN_CHARACTER: { |
| 1630 | startAutoParagraph(p); |
| 1631 | if( p->state & WIKI_MARK ){ |
| 1632 | blob_append_string(p->pOut, "<mark>"); |
| 1633 | p->mRender |= RENDER_MARK; |
| 1634 | } |
| 1635 | if( z[0]=='<' ){ |
| 1636 | p->mRender |= RENDER_BADTAG; |
| 1637 | blob_append_string(p->pOut, "<"); |
| 1638 | }else if( z[0]=='&' ){ |
| 1639 | p->mRender |= RENDER_BADENTITY; |
| 1640 | blob_append_string(p->pOut, "&"); |
| 1641 | } |
| 1642 | if( p->state & WIKI_MARK ){ |
| 1643 | if( fossil_isalnum(z[1]) || (z[1]=='/' && fossil_isalnum(z[2])) ){ |
| 1644 | int kk; |
| 1645 | for(kk=2; fossil_isalnum(z[kk]); kk++){} |
| 1646 | blob_append(p->pOut, &z[1], kk-1); |
| 1647 | n = kk; |
| 1648 | } |
| 1649 | blob_append_string(p->pOut, "</mark>"); |
| 1650 | } |
| 1651 | break; |
| 1652 | } |
| 1653 | case TOKEN_LINK: { |
| 1654 | char *zTarget; |
| 1655 | char *zDisplay = 0; |
| @@ -1596,10 +1658,11 @@ | |
| 1658 | char zClose[20]; |
| 1659 | char cS1 = 0; |
| 1660 | int iS1 = 0; |
| 1661 | |
| 1662 | startAutoParagraph(p); |
| 1663 | p->mRender |= RENDER_LINK; |
| 1664 | zTarget = &z[1]; |
| 1665 | for(i=1; z[i] && z[i]!=']'; i++){ |
| 1666 | if( z[i]=='|' && zDisplay==0 ){ |
| 1667 | zDisplay = &z[i+1]; |
| 1668 | for(j=i; j>0 && fossil_isspace(z[j-1]); j--){} |
| @@ -1612,11 +1675,11 @@ | |
| 1675 | if( zDisplay==0 ){ |
| 1676 | zDisplay = zTarget + interwiki_removable_prefix(zTarget); |
| 1677 | }else{ |
| 1678 | while( fossil_isspace(*zDisplay) ) zDisplay++; |
| 1679 | } |
| 1680 | p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state, |
| 1681 | zTarget, zClose, sizeof(zClose), zOrig, 0); |
| 1682 | if( linksOnly || zClose[0]==0 || p->inVerbatim ){ |
| 1683 | if( cS1 ) z[iS1] = cS1; |
| 1684 | if( zClose[0]!=']' ){ |
| 1685 | blob_appendf(p->pOut, "[%h]%s", zTarget, zClose); |
| @@ -1702,14 +1765,22 @@ | |
| 1765 | |
| 1766 | /* Render invalid markup literally. The markup appears in the |
| 1767 | ** final output as plain text. |
| 1768 | */ |
| 1769 | if( markup.iCode==MARKUP_INVALID ){ |
| 1770 | p->mRender |= RENDER_BADTAG; |
| 1771 | unparseMarkup(&markup); |
| 1772 | startAutoParagraph(p); |
| 1773 | if( p->state & WIKI_MARK ){ |
| 1774 | p->mRender |= RENDER_MARK; |
| 1775 | blob_append_string(p->pOut, "<mark>"); |
| 1776 | htmlize_to_blob(p->pOut, z, n); |
| 1777 | blob_append_string(p->pOut, "</mark>"); |
| 1778 | }else{ |
| 1779 | blob_append_string(p->pOut, "<"); |
| 1780 | htmlize_to_blob(p->pOut, z+1, n-1); |
| 1781 | } |
| 1782 | }else |
| 1783 | |
| 1784 | /* If the markup is not font-change markup ignore it if the |
| 1785 | ** font-change-only flag is set. |
| 1786 | */ |
| @@ -1723,16 +1794,10 @@ | |
| 1794 | }else{ |
| 1795 | p->state &= ~ALLOW_WIKI; |
| 1796 | } |
| 1797 | }else |
| 1798 | |
| 1799 | /* Generate end-tags */ |
| 1800 | if( markup.endTag ){ |
| 1801 | popStackToTag(p, markup.iCode); |
| 1802 | }else |
| 1803 | |
| @@ -1814,10 +1879,11 @@ | |
| 1879 | }else |
| 1880 | { |
| 1881 | if( markup.iType==MUTYPE_FONT ){ |
| 1882 | startAutoParagraph(p); |
| 1883 | }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){ |
| 1884 | p->mRender |= RENDER_BLOCKTAG; |
| 1885 | p->wantAutoParagraph = 0; |
| 1886 | } |
| 1887 | if( markup.iCode==MARKUP_HR |
| 1888 | || markup.iCode==MARKUP_H1 |
| 1889 | || markup.iCode==MARKUP_H2 |
| @@ -1844,12 +1910,14 @@ | |
| 1910 | ** Transform the text in the pIn blob. Write the results |
| 1911 | ** into the pOut blob. The pOut blob should already be |
| 1912 | ** initialized. The output is merely appended to pOut. |
| 1913 | ** If pOut is NULL, then the output is appended to the CGI |
| 1914 | ** reply. |
| 1915 | ** |
| 1916 | ** Return a mask of RENDER_ flags indicating what happened. |
| 1917 | */ |
| 1918 | int wiki_convert(Blob *pIn, Blob *pOut, int flags){ |
| 1919 | Renderer renderer; |
| 1920 | |
| 1921 | memset(&renderer, 0, sizeof(renderer)); |
| 1922 | renderer.renderFlags = flags; |
| 1923 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags; |
| @@ -1873,10 +1941,11 @@ | |
| 1941 | while( renderer.nStack ){ |
| 1942 | popStack(&renderer); |
| 1943 | } |
| 1944 | blob_append_char(renderer.pOut, '\n'); |
| 1945 | free(renderer.aStack); |
| 1946 | return renderer.mRender; |
| 1947 | } |
| 1948 | |
| 1949 | /* |
| 1950 | ** COMMAND: test-wiki-render |
| 1951 | ** |
| @@ -1886,45 +1955,84 @@ | |
| 1955 | ** the resulting HTML on standard output. |
| 1956 | ** |
| 1957 | ** Options: |
| 1958 | ** --buttons Set the WIKI_BUTTONS flag |
| 1959 | ** --dark-pikchr Render pikchrs in dark mode |
| 1960 | ** --flow Render as text using comment_format |
| 1961 | ** --htmlonly Set the WIKI_HTMLONLY flag |
| 1962 | ** --inline Set the WIKI_INLINE flag |
| 1963 | ** --linksonly Set the WIKI_LINKSONLY flag |
| 1964 | ** -m TEXT Use TEXT in place of the content of FILE |
| 1965 | ** --mark Add <mark>...</mark> around problems |
| 1966 | ** --nobadlinks Set the WIKI_NOBADLINKS flag |
| 1967 | ** --text Run the output through html_to_plaintext() |
| 1968 | ** --type Break down the return code from wiki_convert() |
| 1969 | */ |
| 1970 | void test_wiki_render(void){ |
| 1971 | Blob in, out; |
| 1972 | int flags = 0; |
| 1973 | int bText; |
| 1974 | int bFlow = 0; |
| 1975 | int showType = 0; |
| 1976 | int mType; |
| 1977 | const char *zIn; |
| 1978 | if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS; |
| 1979 | if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY; |
| 1980 | if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY; |
| 1981 | if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS; |
| 1982 | if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE; |
| 1983 | if( find_option("mark",0,0)!=0 ) flags |= WIKI_MARK; |
| 1984 | if( find_option("dark-pikchr",0,0)!=0 ){ |
| 1985 | pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE ); |
| 1986 | } |
| 1987 | bText = find_option("text",0,0)!=0; |
| 1988 | bFlow = find_option("flow",0,0)!=0; |
| 1989 | showType = find_option("type",0,0)!=0; |
| 1990 | zIn = find_option("msg","m",1); |
| 1991 | db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
| 1992 | verify_all_options(); |
| 1993 | if( (zIn==0 && g.argc!=3) || (zIn!=0 && g.argc!=2) ) usage("FILE"); |
| 1994 | blob_zero(&out); |
| 1995 | if( zIn ){ |
| 1996 | blob_init(&in, zIn, -1); |
| 1997 | }else{ |
| 1998 | blob_read_from_file(&in, g.argv[2], ExtFILE); |
| 1999 | } |
| 2000 | mType = wiki_convert(&in, &out, flags); |
| 2001 | if( bText ){ |
| 2002 | Blob txt; |
| 2003 | int htot = HTOT_TRIM; |
| 2004 | if( terminal_is_vt100() ) htot |= HTOT_VT100; |
| 2005 | if( bFlow ) htot |= HTOT_FLOW; |
| 2006 | blob_init(&txt, 0, 0); |
| 2007 | html_to_plaintext(blob_str(&out),&txt, htot); |
| 2008 | blob_reset(&out); |
| 2009 | out = txt; |
| 2010 | } |
| 2011 | if( bFlow ){ |
| 2012 | fossil_print(" "); |
| 2013 | comment_print(blob_str(&out), 0, 3, terminal_get_width(80)-3, |
| 2014 | get_comment_format()); |
| 2015 | }else{ |
| 2016 | blob_write_to_file(&out, "-"); |
| 2017 | } |
| 2018 | if( showType ){ |
| 2019 | fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*'); |
| 2020 | if( mType & RENDER_LINK ) fossil_print(" LINK"); |
| 2021 | if( mType & RENDER_ENTITY ) fossil_print(" ENTITY"); |
| 2022 | if( mType & RENDER_TAG ) fossil_print(" TAG"); |
| 2023 | if( mType & RENDER_BLOCKTAG ) fossil_print(" BLOCKTAG"); |
| 2024 | if( mType & RENDER_BLOCK ) fossil_print(" BLOCK"); |
| 2025 | if( mType & RENDER_MARK ) fossil_print(" MARK"); |
| 2026 | if( mType & RENDER_BADLINK ) fossil_print(" BADLINK"); |
| 2027 | if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET"); |
| 2028 | if( mType & RENDER_BADTAG ) fossil_print(" BADTAG"); |
| 2029 | if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY"); |
| 2030 | if( mType & RENDER_BADHTML ) fossil_print(" BADHTML"); |
| 2031 | if( mType & RENDER_ERROR ) fossil_print(" ERROR"); |
| 2032 | fossil_print("\n"); |
| 2033 | } |
| 2034 | } |
| 2035 | |
| 2036 | /* |
| 2037 | ** COMMAND: test-markdown-render |
| 2038 | ** |
| @@ -1960,11 +2068,11 @@ | |
| 2068 | safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED ); |
| 2069 | safe_html(&out); |
| 2070 | if( bText ){ |
| 2071 | Blob txt; |
| 2072 | blob_init(&txt, 0, 0); |
| 2073 | html_to_plaintext(blob_str(&out), &txt, HTOT_VT100); |
| 2074 | blob_reset(&out); |
| 2075 | out = txt; |
| 2076 | } |
| 2077 | blob_write_to_file(&out, "-"); |
| 2078 | blob_reset(&in); |
| @@ -2024,33 +2132,30 @@ | |
| 2132 | ** [target|...] |
| 2133 | ** |
| 2134 | ** Where "target" can be either an artifact ID prefix or a wiki page |
| 2135 | ** name. For each such hyperlink found, add an entry to the |
| 2136 | ** backlink table. |
| 2137 | ** |
| 2138 | ** The return value is a mask of RENDER_ flags. |
| 2139 | */ |
| 2140 | int wiki_extract_links( |
| 2141 | char *z, /* The wiki text from which to extract links */ |
| 2142 | Backlink *pBklnk, /* Backlink extraction context */ |
| 2143 | int flags /* wiki parsing flags */ |
| 2144 | ){ |
| 2145 | Renderer renderer; |
| 2146 | int tokenType; |
| 2147 | ParsedMarkup markup; |
| 2148 | int n; |
| 2149 | int wikiHtmlOnly = 0; |
| 2150 | |
| 2151 | memset(&renderer, 0, sizeof(renderer)); |
| 2152 | renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH; |
| 2153 | if( wikiUsesHtml() ){ |
| 2154 | renderer.state |= WIKI_HTMLONLY; |
| 2155 | wikiHtmlOnly = 1; |
| 2156 | } |
| 2157 | |
| 2158 | while( z[0] ){ |
| 2159 | if( wikiHtmlOnly ){ |
| 2160 | n = nextRawToken(z, &renderer, &tokenType); |
| 2161 | }else{ |
| @@ -2127,16 +2232,10 @@ | |
| 2232 | }else{ |
| 2233 | renderer.state &= ~ALLOW_WIKI; |
| 2234 | } |
| 2235 | }else |
| 2236 | |
| 2237 | /* Generate end-tags */ |
| 2238 | if( markup.endTag ){ |
| 2239 | popStackToTag(&renderer, markup.iCode); |
| 2240 | }else |
| 2241 | |
| @@ -2175,10 +2274,11 @@ | |
| 2274 | } |
| 2275 | } |
| 2276 | z += n; |
| 2277 | } |
| 2278 | free(renderer.aStack); |
| 2279 | return renderer.mRender; |
| 2280 | } |
| 2281 | |
| 2282 | /* |
| 2283 | ** Return the length, in bytes, of the HTML token that z is pointing to. |
| 2284 | */ |
| @@ -2427,33 +2527,61 @@ | |
| 2527 | blob_reset(&in); |
| 2528 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2529 | blob_reset(&out); |
| 2530 | } |
| 2531 | } |
| 2532 | |
| 2533 | #if INTERFACE |
| 2534 | /* |
| 2535 | ** Allowed flag options for html_to_plaintext(). |
| 2536 | */ |
| 2537 | #define HTOT_VT100 0x01 /* <mark> becomes ^[[91m */ |
| 2538 | #define HTOT_FLOW 0x02 /* Collapse internal whitespace to a single space */ |
| 2539 | #define HTOT_TRIM 0x04 /* Trim off leading and trailing whitespace */ |
| 2540 | |
| 2541 | #endif /* INTERFACE */ |
| 2542 | |
| 2543 | /* |
| 2544 | ** Add <MARK> or </MARK> to the output, or similar VT-100 escape |
| 2545 | ** codes. |
| 2546 | */ |
| 2547 | static void addMark(Blob *pOut, int mFlags, int isClose){ |
| 2548 | static const char *az[4] = { "<MARK>", "</MARK>", "\033[91m", "\033[0m" }; |
| 2549 | int i = 0; |
| 2550 | if( isClose ) i++; |
| 2551 | if( mFlags & HTOT_VT100 ) i += 2; |
| 2552 | blob_append(pOut, az[i], -1); |
| 2553 | } |
| 2554 | |
| 2555 | /* |
| 2556 | ** Remove all HTML markup from the input text. The output written into |
| 2557 | ** pOut is pure text. |
| 2558 | ** |
| 2559 | ** Put the title on the first line, if there is any <title> markup. |
| 2560 | ** If there is no <title>, then create a blank first line. |
| 2561 | */ |
| 2562 | void html_to_plaintext(const char *zIn, Blob *pOut, int mFlags){ |
| 2563 | int n; |
| 2564 | int i, j; |
| 2565 | int bFlow = 0; /* Transform internal WS into a single space */ |
| 2566 | int prevWS = 1; /* Previous output was whitespace or start of msg */ |
| 2567 | int nMark = 0; /* True if inside of <mark>..</mark> */ |
| 2568 | |
| 2569 | for(i=0; fossil_isspace(zIn[i]); i++){} |
| 2570 | if( i>0 && (mFlags & HTOT_TRIM)==0 ){ |
| 2571 | blob_append(pOut, zIn, i); |
| 2572 | } |
| 2573 | zIn += i; |
| 2574 | if( mFlags & HTOT_FLOW ) bFlow = 1; |
| 2575 | while( zIn[0] ){ |
| 2576 | n = html_token_length(zIn); |
| 2577 | if( zIn[0]=='<' && n>1 ){ |
| 2578 | int isCloseTag; |
| 2579 | int eTag; |
| 2580 | int eType; |
| 2581 | char zTag[32]; |
| 2582 | prevWS = 0; |
| 2583 | isCloseTag = zIn[1]=='/'; |
| 2584 | for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){ |
| 2585 | zTag[i] = fossil_tolower(zIn[j]); |
| 2586 | } |
| 2587 | zTag[i] = 0; |
| @@ -2467,33 +2595,44 @@ | |
| 2595 | zIn += n; |
| 2596 | } |
| 2597 | if( zIn[0]=='<' ) zIn += n; |
| 2598 | continue; |
| 2599 | } |
| 2600 | if( eTag==MARKUP_INVALID && strcmp(zTag,"mark")==0 ){ |
| 2601 | if( isCloseTag && nMark ){ |
| 2602 | addMark(pOut, mFlags, 1); |
| 2603 | nMark = 0; |
| 2604 | }else if( !isCloseTag && !nMark ){ |
| 2605 | addMark(pOut, mFlags, 0); |
| 2606 | nMark = 1; |
| 2607 | } |
| 2608 | zIn += n; |
| 2609 | continue; |
| 2610 | } |
| 2611 | if( eTag==MARKUP_TITLE ){ |
| 2612 | if( isCloseTag && (mFlags & HTOT_FLOW)==0 ){ |
| 2613 | bFlow = 0; |
| 2614 | }else{ |
| 2615 | bFlow = 1; |
| 2616 | } |
| 2617 | } |
| 2618 | if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){ |
| 2619 | blob_append_char(pOut, '\n'); |
| 2620 | } |
| 2621 | }else if( fossil_isspace(zIn[0]) ){ |
| 2622 | if( bFlow==0 ){ |
| 2623 | if( zIn[n]==0 && (mFlags & HTOT_TRIM) ) break; |
| 2624 | blob_append(pOut, zIn, n); |
| 2625 | }else if( !prevWS ){ |
| 2626 | prevWS = 1; |
| 2627 | blob_append_char(pOut, ' '); |
| 2628 | zIn += n; |
| 2629 | n = 0; |
| 2630 | } |
| 2631 | }else if( zIn[0]=='&' ){ |
| 2632 | u32 c = '?'; |
| 2633 | prevWS = 0; |
| 2634 | if( zIn[1]=='#' ){ |
| 2635 | c = atoi(&zIn[2]); |
| 2636 | if( c==0 ) c = '?'; |
| 2637 | }else{ |
| 2638 | static const struct { int n; u32 c; char *z; } aEntity[] = { |
| @@ -2509,65 +2648,63 @@ | |
| 2648 | c = aEntity[jj].c; |
| 2649 | break; |
| 2650 | } |
| 2651 | } |
| 2652 | } |
| 2653 | if( c<0x00080 ){ |
| 2654 | blob_append_char(pOut, c & 0xff); |
| 2655 | }else if( c<0x00800 ){ |
| 2656 | blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f)); |
| 2657 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2658 | }else if( c<0x10000 ){ |
| 2659 | blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f)); |
| 2660 | blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); |
| 2661 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2662 | }else{ |
| 2663 | blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07)); |
| 2664 | blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f)); |
| 2665 | blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f)); |
| 2666 | blob_append_char(pOut, 0x80 + (u8)(c&0x3f)); |
| 2667 | } |
| 2668 | }else{ |
| 2669 | prevWS = 0; |
| 2670 | blob_append(pOut, zIn, n); |
| 2671 | } |
| 2672 | zIn += n; |
| 2673 | } |
| 2674 | if( nMark ){ |
| 2675 | addMark(pOut, mFlags, 1); |
| 2676 | } |
| 2677 | } |
| 2678 | |
| 2679 | /* |
| 2680 | ** COMMAND: test-html-to-text |
| 2681 | ** |
| 2682 | ** Usage: %fossil test-html-to-text [OPTIONS] FILE ... |
| 2683 | ** |
| 2684 | ** Read all files named on the command-line. Convert the file |
| 2685 | ** content from HTML to text and write the results on standard |
| 2686 | ** output. |
| 2687 | ** |
| 2688 | ** This command is intended as a test and debug interface for |
| 2689 | ** the html_to_plaintext() routine. |
| 2690 | ** |
| 2691 | ** Options: |
| 2692 | ** |
| 2693 | ** --vt100 Translate <mark> and </mark> into ANSI/VT100 |
| 2694 | ** escapes to highlight the contained text. |
| 2695 | */ |
| 2696 | void test_html_to_text(void){ |
| 2697 | Blob in, out; |
| 2698 | int i; |
| 2699 | int mFlags = 0; |
| 2700 | if( find_option("vt100",0,0)!=0 ) mFlags |= HTOT_VT100; |
| 2701 | |
| 2702 | for(i=2; i<g.argc; i++){ |
| 2703 | blob_read_from_file(&in, g.argv[i], ExtFILE); |
| 2704 | blob_zero(&out); |
| 2705 | html_to_plaintext(blob_str(&in), &out, mFlags); |
| 2706 | blob_reset(&in); |
| 2707 | fossil_puts(blob_buffer(&out), 0, blob_size(&out)); |
| 2708 | blob_reset(&out); |
| 2709 | } |
| 2710 | } |
| 2711 |
+5
-1
| --- www/cgi.wiki | ||
| +++ www/cgi.wiki | ||
| @@ -73,12 +73,16 @@ | ||
| 73 | 73 | of available Fossil repositories. |
| 74 | 74 | |
| 75 | 75 | The "skin" of the reply is determined by the first |
| 76 | 76 | repository in the list that has a non-zero |
| 77 | 77 | [/help?cmd=repolist-skin|repolist-skin] setting. |
| 78 | + | |
| 78 | 79 | If no repository has such a non-zero repolist-skin setting, then |
| 79 | -the repository list is generic HTML without any decoration. | |
| 80 | +the repository list is generic HTML without any decoration, with | |
| 81 | +the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> | |
| 82 | +environment variable. The variable can be defined in the CGI | |
| 83 | +control file using the [#setenv|<tt>setenv:</tt>] statement. | |
| 80 | 84 | |
| 81 | 85 | The repolist-generated page recurses into subdirectories and will list |
| 82 | 86 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 83 | 87 | |
| 84 | 88 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 85 | 89 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -73,12 +73,16 @@ | |
| 73 | of available Fossil repositories. |
| 74 | |
| 75 | The "skin" of the reply is determined by the first |
| 76 | repository in the list that has a non-zero |
| 77 | [/help?cmd=repolist-skin|repolist-skin] setting. |
| 78 | If no repository has such a non-zero repolist-skin setting, then |
| 79 | the repository list is generic HTML without any decoration. |
| 80 | |
| 81 | The repolist-generated page recurses into subdirectories and will list |
| 82 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 83 | |
| 84 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 85 |
| --- www/cgi.wiki | |
| +++ www/cgi.wiki | |
| @@ -73,12 +73,16 @@ | |
| 73 | of available Fossil repositories. |
| 74 | |
| 75 | The "skin" of the reply is determined by the first |
| 76 | repository in the list that has a non-zero |
| 77 | [/help?cmd=repolist-skin|repolist-skin] setting. |
| 78 | |
| 79 | If no repository has such a non-zero repolist-skin setting, then |
| 80 | the repository list is generic HTML without any decoration, with |
| 81 | the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt> |
| 82 | environment variable. The variable can be defined in the CGI |
| 83 | control file using the [#setenv|<tt>setenv:</tt>] statement. |
| 84 | |
| 85 | The repolist-generated page recurses into subdirectories and will list |
| 86 | all <tt>*.fossil</tt> files found, with the following exceptions: |
| 87 | |
| 88 | * Filenames starting with a period are treated as "hidden" and skipped. |
| 89 |
+41
-14
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -14,32 +14,43 @@ | ||
| 14 | 14 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | 15 | diffs of multiple files. |
| 16 | 16 | </ol> |
| 17 | 17 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 18 | 18 | about pending changes in a working check-out |
| 19 | - * The [/help?cmd=ui|fossil ui] command defaults to using the | |
| 20 | - [/help?cmd=/ckout|/ckout page] as its start page. Or, if the | |
| 21 | - "--from PATH" option is present, the default start page becomes | |
| 22 | - "/ckout?exbase=PATH". | |
| 19 | + * Enhancements to the [/help?cmd=ui|fossil ui] command: | |
| 20 | + <ol type="a"> | |
| 21 | + <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its | |
| 22 | + start page. Or, if the new "--from PATH" option is present, the | |
| 23 | + default start page becomes "/ckout?exbase=PATH". | |
| 24 | + <li> The new "--extpage FILENAME" option opens the named file as if it | |
| 25 | + where in a [./serverext.wiki|CGI extension]. Example usage: the | |
| 26 | + person editing this change log has | |
| 27 | + "fossil ui --extpage www/changes.wiki" running and hence can | |
| 28 | + press "Reload" on the web browser to view edits. | |
| 29 | + </ol> | |
| 23 | 30 | * Enhancements to [/help?cmd=merge|fossil merge]: |
| 24 | 31 | <ol type="a"> |
| 25 | - <li> Added the [/help?cmd=merge-info|fossil merge-info] command and especially | |
| 26 | - the --tk option to that command, to provide analysis of the most recent | |
| 27 | - merge or update operation. | |
| 32 | + <li> Added the [/help?cmd=merge-info|fossil merge-info] command and | |
| 33 | + especially the --tk option to that command, to provide analysis | |
| 34 | + of the most recent merge or update operation. | |
| 28 | 35 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 29 | 36 | text that shows Fossil's suggested resolution to the conflict. |
| 30 | 37 | </ol> |
| 31 | 38 | * Enhancements to [/help?cmd=commit|fossil commit]: |
| 32 | 39 | <ol type="a"> |
| 33 | - <li> If Fossil sees potential formatting mistakes (bad hyperlinks) | |
| 40 | + <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) | |
| 34 | 41 | in the check-in comment, it will alert the developer and give |
| 35 | 42 | him or her the opportunity to edit the comment before continuing. |
| 43 | + This feature is controllable by the | |
| 44 | + [/help?cmd=verify-comments|verify-comments setting]. | |
| 36 | 45 | <li> The new "--if-changes" option causes the commit to become |
| 37 | 46 | a quiet no-op if there are no pending changes. |
| 38 | 47 | <li> Added the ability to sign check-ins with SSH keys. |
| 39 | 48 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 40 | 49 | branch has been changed. |
| 50 | + <li> The interactive checkin comment prompt shows the formatting rules | |
| 51 | + set for that repository. | |
| 41 | 52 | </ol> |
| 42 | 53 | * Deprecate the --comfmtflags and --comment-format global options and |
| 43 | 54 | no longer list them in the built-in help, but keep them working for |
| 44 | 55 | backwards compatibility. |
| 45 | 56 | Alternative TTY comment formatting can still be specified using the |
| @@ -73,19 +84,33 @@ | ||
| 73 | 84 | collapses long runs of check-ins on the same branch into just |
| 74 | 85 | end-points. |
| 75 | 86 | <li> The p= and d= parameters an reference different check-ins, which |
| 76 | 87 | case the timeline shows those check-ins that are both ancestors |
| 77 | 88 | of p= and descendants of d=. |
| 89 | + <li> The saturation and intensity of user-specified checkin and branch | |
| 90 | + colors are automatically adjusted to keep the colors | |
| 91 | + compatible with the current skin, unless the | |
| 92 | + [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. The | |
| 93 | + /test-bgcolor page was added to | |
| 94 | + test and visualize how these adjustments. | |
| 78 | 95 | </ol> |
| 96 | + * The [/help?cmd=/docfile|/docfile webpage] was added. It works like | |
| 97 | + /doc but keeps the title of markdown documents with the document rather | |
| 98 | + that moving it up to the page title. | |
| 79 | 99 | * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 80 | 100 | and debugging |
| 81 | - * Fix a bug in [/help?cmd=patch|fossil patch create] that causes | |
| 82 | - [/help?cmd=revert|fossil revert] operations that happened on individual | |
| 83 | - files after a [/help?cmd=merge|fossil merge] to be omitted from the | |
| 84 | - patch. | |
| 85 | - * Added the [/help?cmd=patch|patch alias] command for managing aliases | |
| 86 | - for remote checkout names. | |
| 101 | + * Added the "artifact_to_json(NAME)" SQL function that returns a JSON | |
| 102 | + decoding of the artifact described by NAME. | |
| 103 | + * Improvements to the [/help?cmd=patch|fossil patch] command: | |
| 104 | + <ol type="a"> | |
| 105 | + <li> Fix a bug in "fossil patch create" that causes | |
| 106 | + [/help?cmd=revert|fossil revert] operations that happened | |
| 107 | + on individualfiles after a [/help?cmd=merge|fossil merge] | |
| 108 | + to be omitted from the patch. | |
| 109 | + <li> Added the [/help?cmd=patch|patch alias] command for managing | |
| 110 | + aliases for remote checkout names. | |
| 111 | + </ol> | |
| 87 | 112 | * Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 88 | 113 | <ol type="a"> |
| 89 | 114 | <li> Add the ability to search the help text, either in the UI |
| 90 | 115 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 91 | 116 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| @@ -95,10 +120,12 @@ | ||
| 95 | 120 | <li> The -u (--usage) option shows only the command-line syntax |
| 96 | 121 | <li> The -o (--options) option shows only the command-line options |
| 97 | 122 | </ol> |
| 98 | 123 | * Added the ability to attach wiki pages to a ticket for extended |
| 99 | 124 | descriptions. |
| 125 | + * Added the "hash" query parameter to the | |
| 126 | + [/help?cmd=/whatis|/whatis webpage]. | |
| 100 | 127 | * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription] |
| 101 | 128 | which alerts subscribers when an admin creates a new user or |
| 102 | 129 | adds new permissions to one. |
| 103 | 130 | * Diverse minor fixes and additions. |
| 104 | 131 | |
| 105 | 132 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -14,32 +14,43 @@ | |
| 14 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | diffs of multiple files. |
| 16 | </ol> |
| 17 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 18 | about pending changes in a working check-out |
| 19 | * The [/help?cmd=ui|fossil ui] command defaults to using the |
| 20 | [/help?cmd=/ckout|/ckout page] as its start page. Or, if the |
| 21 | "--from PATH" option is present, the default start page becomes |
| 22 | "/ckout?exbase=PATH". |
| 23 | * Enhancements to [/help?cmd=merge|fossil merge]: |
| 24 | <ol type="a"> |
| 25 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and especially |
| 26 | the --tk option to that command, to provide analysis of the most recent |
| 27 | merge or update operation. |
| 28 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 29 | text that shows Fossil's suggested resolution to the conflict. |
| 30 | </ol> |
| 31 | * Enhancements to [/help?cmd=commit|fossil commit]: |
| 32 | <ol type="a"> |
| 33 | <li> If Fossil sees potential formatting mistakes (bad hyperlinks) |
| 34 | in the check-in comment, it will alert the developer and give |
| 35 | him or her the opportunity to edit the comment before continuing. |
| 36 | <li> The new "--if-changes" option causes the commit to become |
| 37 | a quiet no-op if there are no pending changes. |
| 38 | <li> Added the ability to sign check-ins with SSH keys. |
| 39 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 40 | branch has been changed. |
| 41 | </ol> |
| 42 | * Deprecate the --comfmtflags and --comment-format global options and |
| 43 | no longer list them in the built-in help, but keep them working for |
| 44 | backwards compatibility. |
| 45 | Alternative TTY comment formatting can still be specified using the |
| @@ -73,19 +84,33 @@ | |
| 73 | collapses long runs of check-ins on the same branch into just |
| 74 | end-points. |
| 75 | <li> The p= and d= parameters an reference different check-ins, which |
| 76 | case the timeline shows those check-ins that are both ancestors |
| 77 | of p= and descendants of d=. |
| 78 | </ol> |
| 79 | * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 80 | and debugging |
| 81 | * Fix a bug in [/help?cmd=patch|fossil patch create] that causes |
| 82 | [/help?cmd=revert|fossil revert] operations that happened on individual |
| 83 | files after a [/help?cmd=merge|fossil merge] to be omitted from the |
| 84 | patch. |
| 85 | * Added the [/help?cmd=patch|patch alias] command for managing aliases |
| 86 | for remote checkout names. |
| 87 | * Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 88 | <ol type="a"> |
| 89 | <li> Add the ability to search the help text, either in the UI |
| 90 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 91 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| @@ -95,10 +120,12 @@ | |
| 95 | <li> The -u (--usage) option shows only the command-line syntax |
| 96 | <li> The -o (--options) option shows only the command-line options |
| 97 | </ol> |
| 98 | * Added the ability to attach wiki pages to a ticket for extended |
| 99 | descriptions. |
| 100 | * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription] |
| 101 | which alerts subscribers when an admin creates a new user or |
| 102 | adds new permissions to one. |
| 103 | * Diverse minor fixes and additions. |
| 104 | |
| 105 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -14,32 +14,43 @@ | |
| 14 | <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show |
| 15 | diffs of multiple files. |
| 16 | </ol> |
| 17 | * Added the [/help?cmd=/ckout|/ckout web page] to provide information |
| 18 | about pending changes in a working check-out |
| 19 | * Enhancements to the [/help?cmd=ui|fossil ui] command: |
| 20 | <ol type="a"> |
| 21 | <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its |
| 22 | start page. Or, if the new "--from PATH" option is present, the |
| 23 | default start page becomes "/ckout?exbase=PATH". |
| 24 | <li> The new "--extpage FILENAME" option opens the named file as if it |
| 25 | where in a [./serverext.wiki|CGI extension]. Example usage: the |
| 26 | person editing this change log has |
| 27 | "fossil ui --extpage www/changes.wiki" running and hence can |
| 28 | press "Reload" on the web browser to view edits. |
| 29 | </ol> |
| 30 | * Enhancements to [/help?cmd=merge|fossil merge]: |
| 31 | <ol type="a"> |
| 32 | <li> Added the [/help?cmd=merge-info|fossil merge-info] command and |
| 33 | especially the --tk option to that command, to provide analysis |
| 34 | of the most recent merge or update operation. |
| 35 | <li> When a merge conflict occurs, a new section is added to the conflict |
| 36 | text that shows Fossil's suggested resolution to the conflict. |
| 37 | </ol> |
| 38 | * Enhancements to [/help?cmd=commit|fossil commit]: |
| 39 | <ol type="a"> |
| 40 | <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks) |
| 41 | in the check-in comment, it will alert the developer and give |
| 42 | him or her the opportunity to edit the comment before continuing. |
| 43 | This feature is controllable by the |
| 44 | [/help?cmd=verify-comments|verify-comments setting]. |
| 45 | <li> The new "--if-changes" option causes the commit to become |
| 46 | a quiet no-op if there are no pending changes. |
| 47 | <li> Added the ability to sign check-ins with SSH keys. |
| 48 | <li> Issue a warning if a user tries to commit on a check-in where the |
| 49 | branch has been changed. |
| 50 | <li> The interactive checkin comment prompt shows the formatting rules |
| 51 | set for that repository. |
| 52 | </ol> |
| 53 | * Deprecate the --comfmtflags and --comment-format global options and |
| 54 | no longer list them in the built-in help, but keep them working for |
| 55 | backwards compatibility. |
| 56 | Alternative TTY comment formatting can still be specified using the |
| @@ -73,19 +84,33 @@ | |
| 84 | collapses long runs of check-ins on the same branch into just |
| 85 | end-points. |
| 86 | <li> The p= and d= parameters an reference different check-ins, which |
| 87 | case the timeline shows those check-ins that are both ancestors |
| 88 | of p= and descendants of d=. |
| 89 | <li> The saturation and intensity of user-specified checkin and branch |
| 90 | colors are automatically adjusted to keep the colors |
| 91 | compatible with the current skin, unless the |
| 92 | [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on. The |
| 93 | /test-bgcolor page was added to |
| 94 | test and visualize how these adjustments. |
| 95 | </ol> |
| 96 | * The [/help?cmd=/docfile|/docfile webpage] was added. It works like |
| 97 | /doc but keeps the title of markdown documents with the document rather |
| 98 | that moving it up to the page title. |
| 99 | * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis |
| 100 | and debugging |
| 101 | * Added the "artifact_to_json(NAME)" SQL function that returns a JSON |
| 102 | decoding of the artifact described by NAME. |
| 103 | * Improvements to the [/help?cmd=patch|fossil patch] command: |
| 104 | <ol type="a"> |
| 105 | <li> Fix a bug in "fossil patch create" that causes |
| 106 | [/help?cmd=revert|fossil revert] operations that happened |
| 107 | on individualfiles after a [/help?cmd=merge|fossil merge] |
| 108 | to be omitted from the patch. |
| 109 | <li> Added the [/help?cmd=patch|patch alias] command for managing |
| 110 | aliases for remote checkout names. |
| 111 | </ol> |
| 112 | * Enhancements to on-line help and the [/help?cmd=help|fossil help] command: |
| 113 | <ol type="a"> |
| 114 | <li> Add the ability to search the help text, either in the UI |
| 115 | (on the [/help?cmd=/search|/search page]) or from the command-line |
| 116 | (using the "[/help?cmd=search|fossil search -h PATTERN]" command.) |
| @@ -95,10 +120,12 @@ | |
| 120 | <li> The -u (--usage) option shows only the command-line syntax |
| 121 | <li> The -o (--options) option shows only the command-line options |
| 122 | </ol> |
| 123 | * Added the ability to attach wiki pages to a ticket for extended |
| 124 | descriptions. |
| 125 | * Added the "hash" query parameter to the |
| 126 | [/help?cmd=/whatis|/whatis webpage]. |
| 127 | * Add a "user elevation" [/doc/trunk/www/alerts.md|subscription] |
| 128 | which alerts subscribers when an admin creates a new user or |
| 129 | adds new permissions to one. |
| 130 | * Diverse minor fixes and additions. |
| 131 | |
| 132 |
+6
| --- www/env-opts.md | ||
| +++ www/env-opts.md | ||
| @@ -148,10 +148,15 @@ | ||
| 148 | 148 | |
| 149 | 149 | |
| 150 | 150 | `FOSSIL_HOME`: Location of [configuration database][configdb]. |
| 151 | 151 | See the [configuration database location][configloc] description |
| 152 | 152 | for additional information. |
| 153 | + | |
| 154 | +`FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page | |
| 155 | +loaded by the `fossil all ui` or `fossil ui /` commands. Only used if | |
| 156 | +none of the listed repositories has the `repolist_skin` property set. | |
| 157 | +Can be set from the [CGI control file][cgictlfile]. | |
| 153 | 158 | |
| 154 | 159 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 155 | 160 | SEE as text to be hashed into the actual encryption key. This has no |
| 156 | 161 | effect if Fossil was not compiled with SEE support enabled. |
| 157 | 162 | |
| @@ -493,5 +498,6 @@ | ||
| 493 | 498 | On Windows platforms, it assumes that `start` is the command to open |
| 494 | 499 | a URL in the user's configured default browser. |
| 495 | 500 | |
| 496 | 501 | [configdb]: ./tech_overview.wiki#configdb |
| 497 | 502 | [configloc]: ./tech_overview.wiki#configloc |
| 503 | +[cgictlfile]: ./cgi.wiki | |
| 498 | 504 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -148,10 +148,15 @@ | |
| 148 | |
| 149 | |
| 150 | `FOSSIL_HOME`: Location of [configuration database][configdb]. |
| 151 | See the [configuration database location][configloc] description |
| 152 | for additional information. |
| 153 | |
| 154 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 155 | SEE as text to be hashed into the actual encryption key. This has no |
| 156 | effect if Fossil was not compiled with SEE support enabled. |
| 157 | |
| @@ -493,5 +498,6 @@ | |
| 493 | On Windows platforms, it assumes that `start` is the command to open |
| 494 | a URL in the user's configured default browser. |
| 495 | |
| 496 | [configdb]: ./tech_overview.wiki#configdb |
| 497 | [configloc]: ./tech_overview.wiki#configloc |
| 498 |
| --- www/env-opts.md | |
| +++ www/env-opts.md | |
| @@ -148,10 +148,15 @@ | |
| 148 | |
| 149 | |
| 150 | `FOSSIL_HOME`: Location of [configuration database][configdb]. |
| 151 | See the [configuration database location][configloc] description |
| 152 | for additional information. |
| 153 | |
| 154 | `FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page |
| 155 | loaded by the `fossil all ui` or `fossil ui /` commands. Only used if |
| 156 | none of the listed repositories has the `repolist_skin` property set. |
| 157 | Can be set from the [CGI control file][cgictlfile]. |
| 158 | |
| 159 | `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for |
| 160 | SEE as text to be hashed into the actual encryption key. This has no |
| 161 | effect if Fossil was not compiled with SEE support enabled. |
| 162 | |
| @@ -493,5 +498,6 @@ | |
| 498 | On Windows platforms, it assumes that `start` is the command to open |
| 499 | a URL in the user's configured default browser. |
| 500 | |
| 501 | [configdb]: ./tech_overview.wiki#configdb |
| 502 | [configloc]: ./tech_overview.wiki#configloc |
| 503 | [cgictlfile]: ./cgi.wiki |
| 504 |
+8
-4
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -61,11 +61,11 @@ | ||
| 61 | 61 | artifacts: |
| 62 | 62 | |
| 63 | 63 | <ul> |
| 64 | 64 | <li> [#manifest | Manifests] </li> |
| 65 | 65 | <li> [#cluster | Clusters] </li> |
| 66 | -<li> [#ctrl | Control Artifacts] </li> | |
| 66 | +<li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li> | |
| 67 | 67 | <li> [#wikichng | Wiki Pages] </li> |
| 68 | 68 | <li> [#tktchng | Ticket Changes] </li> |
| 69 | 69 | <li> [#attachment | Attachments] </li> |
| 70 | 70 | <li> [#event | TechNotes] </li> |
| 71 | 71 | <li> [#forum | Forum Posts] </li> |
| @@ -276,22 +276,26 @@ | ||
| 276 | 276 | prior cards in the cluster. The <b>Z</b> card is required. |
| 277 | 277 | |
| 278 | 278 | An example cluster from Fossil can be seen |
| 279 | 279 | [/artifact/d03dbdd73a2a8 | here]. |
| 280 | 280 | |
| 281 | -<h3 id="ctrl">2.3 Control Artifacts</h3> | |
| 281 | +<h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3> | |
| 282 | 282 | |
| 283 | 283 | Control artifacts are used to assign properties to other artifacts |
| 284 | -within the repository. | |
| 285 | -Allowed cards in a control artifact are as follows: | |
| 284 | +within the repository. Allowed cards in a control artifact are as | |
| 285 | +follows: | |
| 286 | 286 | |
| 287 | 287 | <div class="indent"> |
| 288 | 288 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 289 | 289 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br /> |
| 290 | 290 | <b>U</b> <i>user-name</i><br /> |
| 291 | 291 | <b>Z</b> <i>checksum</i><br /> |
| 292 | 292 | </div> |
| 293 | + | |
| 294 | +Control articles are also referred to as Tag artifacts, but tags can | |
| 295 | +also be applied via other artifact types, as described in | |
| 296 | +[#summary|the Card Summary table]. | |
| 293 | 297 | |
| 294 | 298 | A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and |
| 295 | 299 | one or more <b>T</b> cards. No other cards or other text is |
| 296 | 300 | allowed in a control artifact. Control artifacts might be PGP |
| 297 | 301 | clearsigned. |
| 298 | 302 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -61,11 +61,11 @@ | |
| 61 | artifacts: |
| 62 | |
| 63 | <ul> |
| 64 | <li> [#manifest | Manifests] </li> |
| 65 | <li> [#cluster | Clusters] </li> |
| 66 | <li> [#ctrl | Control Artifacts] </li> |
| 67 | <li> [#wikichng | Wiki Pages] </li> |
| 68 | <li> [#tktchng | Ticket Changes] </li> |
| 69 | <li> [#attachment | Attachments] </li> |
| 70 | <li> [#event | TechNotes] </li> |
| 71 | <li> [#forum | Forum Posts] </li> |
| @@ -276,22 +276,26 @@ | |
| 276 | prior cards in the cluster. The <b>Z</b> card is required. |
| 277 | |
| 278 | An example cluster from Fossil can be seen |
| 279 | [/artifact/d03dbdd73a2a8 | here]. |
| 280 | |
| 281 | <h3 id="ctrl">2.3 Control Artifacts</h3> |
| 282 | |
| 283 | Control artifacts are used to assign properties to other artifacts |
| 284 | within the repository. |
| 285 | Allowed cards in a control artifact are as follows: |
| 286 | |
| 287 | <div class="indent"> |
| 288 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 289 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br /> |
| 290 | <b>U</b> <i>user-name</i><br /> |
| 291 | <b>Z</b> <i>checksum</i><br /> |
| 292 | </div> |
| 293 | |
| 294 | A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and |
| 295 | one or more <b>T</b> cards. No other cards or other text is |
| 296 | allowed in a control artifact. Control artifacts might be PGP |
| 297 | clearsigned. |
| 298 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -61,11 +61,11 @@ | |
| 61 | artifacts: |
| 62 | |
| 63 | <ul> |
| 64 | <li> [#manifest | Manifests] </li> |
| 65 | <li> [#cluster | Clusters] </li> |
| 66 | <li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li> |
| 67 | <li> [#wikichng | Wiki Pages] </li> |
| 68 | <li> [#tktchng | Ticket Changes] </li> |
| 69 | <li> [#attachment | Attachments] </li> |
| 70 | <li> [#event | TechNotes] </li> |
| 71 | <li> [#forum | Forum Posts] </li> |
| @@ -276,22 +276,26 @@ | |
| 276 | prior cards in the cluster. The <b>Z</b> card is required. |
| 277 | |
| 278 | An example cluster from Fossil can be seen |
| 279 | [/artifact/d03dbdd73a2a8 | here]. |
| 280 | |
| 281 | <h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3> |
| 282 | |
| 283 | Control artifacts are used to assign properties to other artifacts |
| 284 | within the repository. Allowed cards in a control artifact are as |
| 285 | follows: |
| 286 | |
| 287 | <div class="indent"> |
| 288 | <b>D</b> <i>time-and-date-stamp</i><br /> |
| 289 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br /> |
| 290 | <b>U</b> <i>user-name</i><br /> |
| 291 | <b>Z</b> <i>checksum</i><br /> |
| 292 | </div> |
| 293 | |
| 294 | Control articles are also referred to as Tag artifacts, but tags can |
| 295 | also be applied via other artifact types, as described in |
| 296 | [#summary|the Card Summary table]. |
| 297 | |
| 298 | A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and |
| 299 | one or more <b>T</b> cards. No other cards or other text is |
| 300 | allowed in a control artifact. Control artifacts might be PGP |
| 301 | clearsigned. |
| 302 |
+33
-5
| --- www/serverext.wiki | ||
| +++ www/serverext.wiki | ||
| @@ -32,11 +32,11 @@ | ||
| 32 | 32 | If the Fossil server is itself run as |
| 33 | 33 | [./server/any/cgi.md|CGI], then add a line to the |
| 34 | 34 | [./cgi.wiki#extroot|CGI script file] that says: |
| 35 | 35 | |
| 36 | 36 | <pre> |
| 37 | -extroot: <i>DIRECTORY</i> | |
| 37 | + extroot: <i>DIRECTORY</i> | |
| 38 | 38 | </pre> |
| 39 | 39 | |
| 40 | 40 | Or, if the Fossil server is being run using the |
| 41 | 41 | "[./server/any/none.md|fossil server]" or |
| 42 | 42 | "[./server/any/none.md|fossil ui]" or |
| @@ -45,18 +45,21 @@ | ||
| 45 | 45 | |
| 46 | 46 | The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI. |
| 47 | 47 | Files in the DOCUMENT_ROOT are accessed via URLs like this: |
| 48 | 48 | |
| 49 | 49 | <pre> |
| 50 | -https://example-project.org/ext/<i>FILENAME</i> | |
| 50 | + https://example-project.org/ext/<i>FILENAME</i> | |
| 51 | 51 | </pre> |
| 52 | 52 | |
| 53 | 53 | In other words, access files in DOCUMENT_ROOT by appending the filename |
| 54 | 54 | relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext] |
| 55 | 55 | page of the Fossil server. |
| 56 | -Files that are readable but not executable are returned as static | |
| 57 | -content. Files that are executable are run as CGI. | |
| 56 | + | |
| 57 | + * Files that are readable but not executable are returned as static | |
| 58 | + content. | |
| 59 | + | |
| 60 | + * Files that are executable are run as CGI. | |
| 58 | 61 | |
| 59 | 62 | <h3>2.1 Example #1</h3> |
| 60 | 63 | |
| 61 | 64 | The source code repository for SQLite is a Fossil server that is run |
| 62 | 65 | as CGI. The URL for the source code repository is [https://sqlite.org/src]. |
| @@ -117,16 +120,41 @@ | ||
| 117 | 120 | of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based |
| 118 | 121 | technologies like Wapp.) The fileup1 script is a demo program that lets |
| 119 | 122 | the user upload a file using a form, and then displays that file in the reply. |
| 120 | 123 | There is a link on the page that causes the fileup1 script to return a copy |
| 121 | 124 | of its own source-code, so you can see how it works. |
| 125 | + | |
| 126 | +<h3>2.3 Example #3</h3> | |
| 127 | + | |
| 128 | +For Fossil versions dated 2025-03-23 and later, the "--extpage FILENAME" | |
| 129 | +option to the [/help?cmd=ui|fossil ui] command is a short cut that treats | |
| 130 | +FILENAME as a CGI extension. When the ui command starts up a new web browser | |
| 131 | +pages, it points that page to the FILENAME extension. So if FILENAME is | |
| 132 | +a static content file (such as an HTML file or | |
| 133 | +[/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the | |
| 134 | +rendered content of the file is displayed. Meanwhile, the user can be | |
| 135 | +editing the source text for that document in a separate window, and | |
| 136 | +periodically pressing "Reload" on the web browser to instantly view the | |
| 137 | +rendered results. | |
| 138 | + | |
| 139 | +For example, the author of this documentation page is running | |
| 140 | +"<tt>fossil ui --extpage www/serverext.wiki</tt>" while editing this | |
| 141 | +very paragraph, and presses Reload from time to time to view his | |
| 142 | +edits. | |
| 143 | + | |
| 144 | +A same idea applies when developing new CGI applications using a script | |
| 145 | +language (for example using [https://wapp.tcl.tk|Wapp]). Run the | |
| 146 | +command "<tt>fossil ui --extpage SCRIPT</tt>" where SCRIPT is the name | |
| 147 | +of the application script, while editing that script in a separate | |
| 148 | +window, then press Reload periodically on the web browser to test the | |
| 149 | +script. | |
| 122 | 150 | |
| 123 | 151 | <h2 id="cgi-inputs">3.0 CGI Inputs</h2> |
| 124 | 152 | |
| 125 | 153 | The /ext extension mechanism is an ordinary CGI interface. Parameters |
| 126 | 154 | are passed to the CGI program using environment variables. The following |
| 127 | -standard CGI environment variables are supported: | |
| 155 | +standard CGI environment variables are supplied: | |
| 128 | 156 | |
| 129 | 157 | * AUTH_TYPE |
| 130 | 158 | * AUTH_CONTENT |
| 131 | 159 | * CONTENT_LENGTH |
| 132 | 160 | * CONTENT_TYPE |
| 133 | 161 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -32,11 +32,11 @@ | |
| 32 | If the Fossil server is itself run as |
| 33 | [./server/any/cgi.md|CGI], then add a line to the |
| 34 | [./cgi.wiki#extroot|CGI script file] that says: |
| 35 | |
| 36 | <pre> |
| 37 | extroot: <i>DIRECTORY</i> |
| 38 | </pre> |
| 39 | |
| 40 | Or, if the Fossil server is being run using the |
| 41 | "[./server/any/none.md|fossil server]" or |
| 42 | "[./server/any/none.md|fossil ui]" or |
| @@ -45,18 +45,21 @@ | |
| 45 | |
| 46 | The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI. |
| 47 | Files in the DOCUMENT_ROOT are accessed via URLs like this: |
| 48 | |
| 49 | <pre> |
| 50 | https://example-project.org/ext/<i>FILENAME</i> |
| 51 | </pre> |
| 52 | |
| 53 | In other words, access files in DOCUMENT_ROOT by appending the filename |
| 54 | relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext] |
| 55 | page of the Fossil server. |
| 56 | Files that are readable but not executable are returned as static |
| 57 | content. Files that are executable are run as CGI. |
| 58 | |
| 59 | <h3>2.1 Example #1</h3> |
| 60 | |
| 61 | The source code repository for SQLite is a Fossil server that is run |
| 62 | as CGI. The URL for the source code repository is [https://sqlite.org/src]. |
| @@ -117,16 +120,41 @@ | |
| 117 | of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based |
| 118 | technologies like Wapp.) The fileup1 script is a demo program that lets |
| 119 | the user upload a file using a form, and then displays that file in the reply. |
| 120 | There is a link on the page that causes the fileup1 script to return a copy |
| 121 | of its own source-code, so you can see how it works. |
| 122 | |
| 123 | <h2 id="cgi-inputs">3.0 CGI Inputs</h2> |
| 124 | |
| 125 | The /ext extension mechanism is an ordinary CGI interface. Parameters |
| 126 | are passed to the CGI program using environment variables. The following |
| 127 | standard CGI environment variables are supported: |
| 128 | |
| 129 | * AUTH_TYPE |
| 130 | * AUTH_CONTENT |
| 131 | * CONTENT_LENGTH |
| 132 | * CONTENT_TYPE |
| 133 |
| --- www/serverext.wiki | |
| +++ www/serverext.wiki | |
| @@ -32,11 +32,11 @@ | |
| 32 | If the Fossil server is itself run as |
| 33 | [./server/any/cgi.md|CGI], then add a line to the |
| 34 | [./cgi.wiki#extroot|CGI script file] that says: |
| 35 | |
| 36 | <pre> |
| 37 | extroot: <i>DIRECTORY</i> |
| 38 | </pre> |
| 39 | |
| 40 | Or, if the Fossil server is being run using the |
| 41 | "[./server/any/none.md|fossil server]" or |
| 42 | "[./server/any/none.md|fossil ui]" or |
| @@ -45,18 +45,21 @@ | |
| 45 | |
| 46 | The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI. |
| 47 | Files in the DOCUMENT_ROOT are accessed via URLs like this: |
| 48 | |
| 49 | <pre> |
| 50 | https://example-project.org/ext/<i>FILENAME</i> |
| 51 | </pre> |
| 52 | |
| 53 | In other words, access files in DOCUMENT_ROOT by appending the filename |
| 54 | relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext] |
| 55 | page of the Fossil server. |
| 56 | |
| 57 | * Files that are readable but not executable are returned as static |
| 58 | content. |
| 59 | |
| 60 | * Files that are executable are run as CGI. |
| 61 | |
| 62 | <h3>2.1 Example #1</h3> |
| 63 | |
| 64 | The source code repository for SQLite is a Fossil server that is run |
| 65 | as CGI. The URL for the source code repository is [https://sqlite.org/src]. |
| @@ -117,16 +120,41 @@ | |
| 120 | of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based |
| 121 | technologies like Wapp.) The fileup1 script is a demo program that lets |
| 122 | the user upload a file using a form, and then displays that file in the reply. |
| 123 | There is a link on the page that causes the fileup1 script to return a copy |
| 124 | of its own source-code, so you can see how it works. |
| 125 | |
| 126 | <h3>2.3 Example #3</h3> |
| 127 | |
| 128 | For Fossil versions dated 2025-03-23 and later, the "--extpage FILENAME" |
| 129 | option to the [/help?cmd=ui|fossil ui] command is a short cut that treats |
| 130 | FILENAME as a CGI extension. When the ui command starts up a new web browser |
| 131 | pages, it points that page to the FILENAME extension. So if FILENAME is |
| 132 | a static content file (such as an HTML file or |
| 133 | [/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the |
| 134 | rendered content of the file is displayed. Meanwhile, the user can be |
| 135 | editing the source text for that document in a separate window, and |
| 136 | periodically pressing "Reload" on the web browser to instantly view the |
| 137 | rendered results. |
| 138 | |
| 139 | For example, the author of this documentation page is running |
| 140 | "<tt>fossil ui --extpage www/serverext.wiki</tt>" while editing this |
| 141 | very paragraph, and presses Reload from time to time to view his |
| 142 | edits. |
| 143 | |
| 144 | A same idea applies when developing new CGI applications using a script |
| 145 | language (for example using [https://wapp.tcl.tk|Wapp]). Run the |
| 146 | command "<tt>fossil ui --extpage SCRIPT</tt>" where SCRIPT is the name |
| 147 | of the application script, while editing that script in a separate |
| 148 | window, then press Reload periodically on the web browser to test the |
| 149 | script. |
| 150 | |
| 151 | <h2 id="cgi-inputs">3.0 CGI Inputs</h2> |
| 152 | |
| 153 | The /ext extension mechanism is an ordinary CGI interface. Parameters |
| 154 | are passed to the CGI program using environment variables. The following |
| 155 | standard CGI environment variables are supplied: |
| 156 | |
| 157 | * AUTH_TYPE |
| 158 | * AUTH_CONTENT |
| 159 | * CONTENT_LENGTH |
| 160 | * CONTENT_TYPE |
| 161 |