Index: skins/default/header.txt ================================================================== --- skins/default/header.txt +++ skins/default/header.txt @@ -28,11 +28,11 @@ return $logourl } set logourl [getLogoUrl $baseurl] - $project_name + $<project_name>

$

$</span> Index: skins/eagle/header.txt ================================================================== --- skins/eagle/header.txt +++ skins/eagle/header.txt @@ -65,11 +65,11 @@ # Link logo to the top of the current repo set logourl $baseurl } </th1> <a href="$logourl"> - <img src="$logo_image_url" border="0" alt="$project_name"> + <img src="$logo_image_url" border="0" alt="$<project_name>"> </a> </div> <div class="title">$<title></div> <div class="status"><nobr><th1> if {[info exists login]} { Index: skins/original/header.txt ================================================================== --- skins/original/header.txt +++ skins/original/header.txt @@ -59,11 +59,11 @@ return $logourl } set logourl [getLogoUrl $baseurl] </th1> <a href="$logourl"> - <img src="$logo_image_url" border="0" alt="$project_name"> + <img src="$logo_image_url" border="0" alt="$<project_name>"> </a> </div> <div class="title">$<title></div> <div class="status"><nobr><th1> if {[info exists login]} { Index: skins/xekri/header.txt ================================================================== --- skins/xekri/header.txt +++ skins/xekri/header.txt @@ -65,11 +65,11 @@ # Link logo to the top of the current repo set logourl $baseurl } </th1> <a href="$logourl"> - <img src="$logo_image_url" border="0" alt="$project_name"> + <img src="$logo_image_url" border="0" alt="$<project_name>"> </a> </div> <div class="title">$<title></div> <div class="status"><nobr> <th1> Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -205,11 +205,11 @@ linkTip = rid != symbolic_name_to_rid("tip", "ci"); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); isBranchCI = branch_includes_uuid(zCI, zUuid); if( bDocDir ) zCI = mprintf("%S", zUuid); - Th_Store("current_checkin", zCI); + Th_StoreUnsafe("current_checkin", zCI); }else{ zCI = 0; } } @@ -771,11 +771,11 @@ rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); zNow = db_text("", "SELECT datetime(mtime,toLocal())" " FROM event WHERE objid=%d", rid); isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); isBranchCI = branch_includes_uuid(zCI, zUuid); - Th_Store("current_checkin", zCI); + Th_StoreUnsafe("current_checkin", zCI); }else{ zCI = 0; } } if( zCI==0 ){ Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -1052,11 +1052,11 @@ */ zMime = nMiss==0 ? P("mimetype") : 0; if( zMime==0 ){ zMime = mimetype_from_name(zName); } - Th_Store("doc_name", zName); + Th_StoreUnsafe("doc_name", zName); if( vid ){ Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" " FROM blob WHERE rid=%d", vid)); Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" " WHERE objid=%d AND type='ci'", vid)); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -951,11 +951,11 @@ const char *zOrigDate; int okWiki = 0; Blob wiki_read_links = BLOB_INITIALIZER; Blob wiki_add_links = BLOB_INITIALIZER; - Th_Store("current_checkin", zName); + Th_StoreUnsafe("current_checkin", zName); style_header("Check-in [%S]", zUuid); login_anonymous_available(); zEUser = db_text(0, "SELECT value FROM tagxref" " WHERE tagid=%d AND rid=%d AND tagtype>0", Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -3718,10 +3718,13 @@ ** case=3 Extra db_end_transaction() ** case=4 Error during SQL processing ** case=5 Call the segfault handler ** case=6 Call webpage_assert() ** case=7 Call webpage_error() +** case=8 Simulate a timeout +** case=9 Simulate a TH1 XSS vulnerability +** case=10 Simulate a TH1 SQL-injection vulnerability */ void test_warning_page(void){ int iCase = atoi(PD("case","0")); int i; login_check_credentials(); @@ -3730,17 +3733,15 @@ return; } style_set_current_feature("test"); style_header("Warning Test Page"); style_submenu_element("Error Log","%R/errorlog"); - if( iCase<1 || iCase>4 ){ - @ <p>Generate a message to the <a href="%R/errorlog">error log</a> - @ by clicking on one of the following cases: - }else{ - @ <p>This is the test page for case=%d(iCase). All possible cases: - } - for(i=1; i<=8; i++){ + @ <p>This page will generate various kinds of errors to test Fossil's + @ reaction. Depending on settings, a message might be written + @ into the <a href="%R/errorlog">error log</a>. Click on + @ one of the following hyperlinks to generate a simulated error: + for(i=1; i<=10; i++){ @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a> } @ </p> @ <p><ol> @ <li value='1'> Call fossil_warning() @@ -3769,20 +3770,39 @@ } @ <li value='6'> call webpage_assert(0) if( iCase==6 ){ webpage_assert( 5==7 ); } - @ <li value='7'> call webpage_error()" + @ <li value='7'> call webpage_error() if( iCase==7 ){ cgi_reset_content(); webpage_error("Case 7 from /test-warning"); } - @ <li value='8'> simulated timeout" + @ <li value='8'> simulated timeout if( iCase==8 ){ fossil_set_timeout(1); cgi_reset_content(); sqlite3_sleep(1100); + } + @ <li value='9'> simulated TH1 XSS vulnerability + @ <li value='10'> simulated TH1 SQL-injection vulnerability + if( iCase==9 || iCase==10 ){ + const char *zR; + int n, rc; + static const char *zTH1[] = { + /* case 9 */ "html [taint {<b>XSS</b>}]", + /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n" + " html \"<b>[htmlize $msg]</b>\"\n" + "}" + }; + rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1); + zR = Th_GetResult(g.interp, &n); + if( rc==TH_OK ){ + @ <pre class="th1result">%h(zR)</pre> + }else{ + @ <pre class="th1error">%h(zR)</pre> + } } @ </ol> @ <p>End of test</p> style_finish_page(); } Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -1121,11 +1121,11 @@ }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){ fprintf(out, "%s=%s\n", azEnv[i], z); } } } - fclose(out); + if( out!=stderr ) fclose(out); } /* ** The following variable becomes true while processing a fatal error ** or a panic. If additional "recursive-fatal" errors occur while Index: src/security_audit.c ================================================================== --- src/security_audit.c +++ src/security_audit.c @@ -810,27 +810,28 @@ ** WEBPAGE: errorlog ** ** Show the content of the error log. Only the administrator can view ** this page. ** -** y=0x01 Show only hack attempts -** y=0x02 Show only panics and assertion faults -** y=0x04 Show hung backoffice processes -** y=0x08 Show POST requests from a different origin -** y=0x10 Show SQLITE_AUTH and similar -** y=0x20 Show SMTP error reports -** y=0x40 Show other uncategorized messages +** y=0x001 Show only hack attempts +** y=0x002 Show only panics and assertion faults +** y=0x004 Show hung backoffice processes +** y=0x008 Show POST requests from a different origin +** y=0x010 Show SQLITE_AUTH and similar +** y=0x020 Show SMTP error reports +** y=0x040 Show TH1 vulnerability reports +** y=0x800 Show other uncategorized messages ** ** If y is omitted or is zero, a count of the various message types is ** shown. */ void errorlog_page(void){ i64 szFile; FILE *in; char *zLog; const char *zType = P("y"); - static const int eAllTypes = 0x7f; + static const int eAllTypes = 0x87f; long eType = 0; int bOutput = 0; int prevWasTime = 0; int nHack = 0; int nPanic = 0; @@ -837,10 +838,11 @@ int nOther = 0; int nHang = 0; int nXPost = 0; int nAuth = 0; int nSmtp = 0; + int nVuln = 0; char z[10000]; char zTime[10000]; login_check_credentials(); if( !g.perm.Admin ){ @@ -917,10 +919,13 @@ } if( eType & 0x20 ){ @ <li>SMTP malfunctions } if( eType & 0x40 ){ + @ <li>TH1 vulnerabilities + } + if( eType & 0x800 ){ @ <li>Other uncategorized messages } @ </ul> } @ <hr> @@ -953,12 +958,16 @@ || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0 ){ bOutput = (eType & 0x10)!=0; nAuth++; }else - { + if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){ bOutput = (eType & 0x40)!=0; + nVuln++; + }else + { + bOutput = (eType & 0x800)!=0; nOther++; } if( bOutput ){ @ %h(zTime)\ } @@ -978,17 +987,21 @@ fclose(in); if( eType ){ @ </pre> } if( eType==0 ){ - int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther; + int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther; int nTotal = nNonHack + nHack + nXPost; @ <p><table border="a" cellspacing="0" cellpadding="5"> if( nPanic>0 ){ @ <tr><td align="right">%d(nPanic)</td> @ <td><a href="./errorlog?y=2">Panics</a></td> } + if( nVuln>0 ){ + @ <tr><td align="right">%d(nVuln)</td> + @ <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td> + } if( nHack>0 ){ @ <tr><td align="right">%d(nHack)</td> @ <td><a href="./errorlog?y=1">Hack Attempts</a></td> } if( nHang>0 ){ @@ -1007,17 +1020,17 @@ @ <tr><td align="right">%d(nSmtp)</td> @ <td><a href="./errorlog?y=32">SMTP faults</a></td> } if( nOther>0 ){ @ <tr><td align="right">%d(nOther)</td> - @ <td><a href="./errorlog?y=64">Other</a></td> + @ <td><a href="./errorlog?y=2048">Other</a></td> } @ <tr><td align="right">%d(nTotal)</td> if( nTotal>0 ){ - @ <td><a href="./errorlog?y=255">All Messages</a></td> + @ <td><a href="./errorlog?y=4095">All Messages</a></td> }else{ @ <td>All Messages</td> } @ </table> } style_finish_page(); } Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -744,12 +744,13 @@ ** is evaluated before the header is rendered). */ Th_MaybeStore("default_csp", zDfltCsp); fossil_free(zDfltCsp); Th_Store("nonce", zNonce); - Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); - Th_Store("project_description", db_get("project-description","")); + Th_StoreUnsafe("project_name", + db_get("project-name","Unnamed Fossil Project")); + Th_StoreUnsafe("project_description", db_get("project-description","")); if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1)); Th_Store("baseurl", g.zBaseURL); Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL); Th_Store("home", g.zTop); Th_Store("index_page", db_get("index-page","/home")); Index: src/th.c ================================================================== --- src/th.c +++ src/th.c @@ -7,10 +7,16 @@ #include "config.h" #include "th.h" #include <string.h> #include <assert.h> +/* +** External routines +*/ +void fossil_panic(const char*,...); +void fossil_errorlog(const char*,...); + /* ** Values used for element values in the tcl_platform array. */ #if !defined(TH_ENGINE) @@ -197,10 +203,11 @@ */ struct Buffer { char *zBuf; int nBuf; int nBufAlloc; + int bTaint; }; typedef struct Buffer Buffer; static void thBufferInit(Buffer *); static void thBufferFree(Th_Interp *interp, Buffer *); @@ -209,10 +216,18 @@ ** be NULL as long as the number of bytes to copy is zero. */ static void th_memcpy(void *dest, const void *src, size_t n){ if( n>0 ) memcpy(dest,src,n); } + +/* +** An oversized string has been encountered. Do not try to recover. +** Panic the process. +*/ +void Th_OversizeString(void){ + fossil_panic("string too large. maximum size 286MB."); +} /* ** Append nAdd bytes of content copied from zAdd to the end of buffer ** pBuffer. If there is not enough space currently allocated, resize ** the allocation to make space. @@ -219,40 +234,46 @@ */ static void thBufferWriteResize( Th_Interp *interp, Buffer *pBuffer, const char *zAdd, - int nAdd + int nAddX ){ + int nAdd = TH1_LEN(nAddX); int nNew = (pBuffer->nBuf+nAdd)*2+32; #if defined(TH_MEMDEBUG) char *zNew = (char *)Th_Malloc(interp, nNew); + TH1_SIZECHECK(nNew); th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf); Th_Free(interp, pBuffer->zBuf); pBuffer->zBuf = zNew; #else int nOld = pBuffer->nBufAlloc; + TH1_SIZECHECK(nNew); pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew); memset(pBuffer->zBuf+nOld, 0, nNew-nOld); #endif pBuffer->nBufAlloc = nNew; th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd); pBuffer->nBuf += nAdd; + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); } static void thBufferWriteFast( Th_Interp *interp, Buffer *pBuffer, const char *zAdd, - int nAdd + int nAddX ){ + int nAdd = TH1_LEN(nAddX); if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){ - thBufferWriteResize(interp, pBuffer, zAdd, nAdd); + thBufferWriteResize(interp, pBuffer, zAdd, nAddX); }else{ if( pBuffer->zBuf ){ memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd); } pBuffer->nBuf += nAdd; + TH1_XFER_TAINT(pBuffer->bTaint, nAddX); } } #define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d) /* @@ -704,24 +725,25 @@ int nWord ){ int rc = TH_OK; Buffer output; int i; + int nn = TH1_LEN(nWord); thBufferInit(&output); - if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){ - thBufferWrite(interp, &output, &zWord[1], nWord-2); + if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){ + thBufferWrite(interp, &output, &zWord[1], nn-2); }else{ /* If the word is surrounded by double-quotes strip these away. */ - if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){ + if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){ zWord++; - nWord -= 2; + nn -= 2; } - for(i=0; rc==TH_OK && i<nWord; i++){ + for(i=0; rc==TH_OK && i<nn; i++){ int nGet; int (*xGet)(Th_Interp *, const char*, int, int *) = 0; int (*xSubst)(Th_Interp *, const char*, int) = 0; @@ -743,11 +765,11 @@ thBufferAddChar(interp, &output, zWord[i]); continue; /* Go to the next iteration of the for(...) loop */ } } - rc = xGet(interp, &zWord[i], nWord-i, &nGet); + rc = xGet(interp, &zWord[i], nn-i, &nGet); if( rc==TH_OK ){ rc = xSubst(interp, &zWord[i], nGet); } if( rc==TH_OK ){ const char *zRes; @@ -758,11 +780,11 @@ } } } if( rc==TH_OK ){ - Th_SetResult(interp, output.zBuf, output.nBuf); + Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint); } thBufferFree(interp, &output); return rc; } @@ -826,11 +848,11 @@ Buffer strbuf; Buffer lenbuf; int nCount = 0; const char *zInput = zList; - int nInput = nList; + int nInput = TH1_LEN(nList); thBufferInit(&strbuf); thBufferInit(&lenbuf); while( nInput>0 ){ @@ -837,19 +859,19 @@ const char *zWord; int nWord; thNextSpace(interp, zInput, nInput, &nWord); zInput += nWord; - nInput = nList-(zInput-zList); + nInput = TH1_LEN(nList)-(zInput-zList); if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0)) || TH_OK!=(rc = thSubstWord(interp, zInput, nWord)) ){ goto finish; } - zInput = &zInput[nWord]; - nInput = nList-(zInput-zList); + zInput = &zInput[TH1_LEN(nWord)]; + nInput = TH1_LEN(nList)-(zInput-zList); if( nWord>0 ){ zWord = Th_GetResult(interp, &nWord); thBufferWrite(interp, &strbuf, zWord, nWord); thBufferAddChar(interp, &strbuf, 0); thBufferWrite(interp, &lenbuf, &nWord, sizeof(int)); @@ -872,11 +894,11 @@ zElem = (char *)&anElem[nCount]; th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf); th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf); for(i=0; i<nCount;i++){ azElem[i] = zElem; - zElem += (anElem[i] + 1); + zElem += (TH1_LEN(anElem[i]) + 1); } *pazElem = azElem; *panElem = anElem; } if( pnCount ){ @@ -894,12 +916,17 @@ ** in the current stack frame. */ static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){ int rc = TH_OK; const char *zInput = zProgram; - int nInput = nProgram; + int nInput = TH1_LEN(nProgram); + if( TH1_TAINTED(nProgram) + && Th_ReportTaint(interp, "script", zProgram, nProgram) + ){ + return TH_ERROR; + } while( rc==TH_OK && nInput ){ Th_HashEntry *pEntry; int nSpace; const char *zFirst; @@ -949,13 +976,13 @@ if( rc!=TH_OK ) continue; if( argc>0 ){ /* Look up the command name in the command hash-table. */ - pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0); + pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0); if( !pEntry ){ - Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]); + Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0])); rc = TH_ERROR; } /* Call the command procedure. */ if( rc==TH_OK ){ @@ -1053,10 +1080,12 @@ }else{ int nInput = nProgram; if( nInput<0 ){ nInput = th_strlen(zProgram); + }else{ + nInput = TH1_LEN(nInput); } rc = thEvalLocal(interp, zProgram, nInput); } interp->pFrame = pSavedFrame; @@ -1095,10 +1124,12 @@ int isGlobal = 0; int i; if( nVarname<0 ){ nVarname = th_strlen(zVarname); + }else{ + nVarname = TH1_LEN(nVarname); } nOuter = nVarname; /* If the variable name starts with "::", then do the lookup is in the ** uppermost (global) frame. @@ -1271,31 +1302,10 @@ } return Th_SetResult(interp, pValue->zData, pValue->nData); } -/* -** If interp has a variable with the given name, its value is returned -** and its length is returned via *nOut if nOut is not NULL. If -** interp has no such var then NULL is returned without setting any -** error state and *nOut, if not NULL, is set to -1. The returned value -** is owned by the interpreter and may be invalidated the next time -** the interpreter is modified. -*/ -const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, - int *nOut){ - Th_Variable *pValue; - - pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0); - if( !pValue || !pValue->zData ){ - if( nOut!=0 ) *nOut = -1; - return NULL; - } - if( nOut!=0 ) *nOut = pValue->nData; - return pValue->zData; -} - /* ** Return true if variable (zVar, nVar) exists. */ int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){ Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0); @@ -1324,28 +1334,32 @@ int nVar, const char *zValue, int nValue ){ Th_Variable *pValue; + int nn; + nVar = TH1_LEN(nVar); pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0); if( !pValue ){ return TH_ERROR; } if( nValue<0 ){ - nValue = th_strlen(zValue); + nn = th_strlen(zValue); + }else{ + nn = TH1_LEN(nValue); } if( pValue->zData ){ Th_Free(interp, pValue->zData); pValue->zData = 0; } - assert(zValue || nValue==0); - pValue->zData = Th_Malloc(interp, nValue+1); - pValue->zData[nValue] = '\0'; - th_memcpy(pValue->zData, zValue, nValue); + assert(zValue || nn==0); + pValue->zData = Th_Malloc(interp, nn+1); + pValue->zData[nn] = '\0'; + th_memcpy(pValue->zData, zValue, nn); pValue->nData = nValue; return TH_OK; } @@ -1458,10 +1472,12 @@ */ char *th_strdup(Th_Interp *interp, const char *z, int n){ char *zRes; if( n<0 ){ n = th_strlen(z); + }else{ + n = TH1_LEN(n); } zRes = Th_Malloc(interp, n+1); th_memcpy(zRes, z, n); zRes[n] = '\0'; return zRes; @@ -1519,13 +1535,14 @@ n = th_strlen(z); } if( z && n>0 ){ char *zResult; - zResult = Th_Malloc(pInterp, n+1); - th_memcpy(zResult, z, n); - zResult[n] = '\0'; + int nn = TH1_LEN(n); + zResult = Th_Malloc(pInterp, nn+1); + th_memcpy(zResult, z, nn); + zResult[nn] = '\0'; pInterp->zResult = zResult; pInterp->nResult = n; } return TH_OK; @@ -1834,24 +1851,28 @@ int *pnStr, /* IN/OUT: Current length of *pzStr */ const char *zElem, /* Data to append */ int nElem /* Length of nElem */ ){ char *zNew; - int nNew; + long long int nNew; + int nn; if( nElem<0 ){ - nElem = th_strlen(zElem); + nn = th_strlen(zElem); + }else{ + nn = TH1_LEN(nElem); } - nNew = *pnStr + nElem; + nNew = TH1_LEN(*pnStr) + nn; + TH1_SIZECHECK(nNew); zNew = Th_Malloc(interp, nNew); th_memcpy(zNew, *pzStr, *pnStr); - th_memcpy(&zNew[*pnStr], zElem, nElem); + th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn); Th_Free(interp, *pzStr); *pzStr = zNew; - *pnStr = nNew; + *pnStr = (int)nNew; return TH_OK; } /* @@ -2456,10 +2477,12 @@ int nToken = 0; Expr **apToken = 0; if( nExpr<0 ){ nExpr = th_strlen(zExpr); + }else{ + nExpr = TH1_LEN(nExpr); } /* Parse the expression to a list of tokens. */ rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken); @@ -2567,10 +2590,12 @@ Th_HashEntry *pRet; Th_HashEntry **ppRet; if( nKey<0 ){ nKey = th_strlen(zKey); + }else{ + nKey = TH1_LEN(nKey); } for(i=0; i<nKey; i++){ iKey = (iKey<<3) ^ iKey ^ zKey[i]; } @@ -2800,10 +2825,12 @@ int base = 10; int (*isdigit)(char) = th_isdigit; if( n<0 ){ n = th_strlen(z); + }else{ + n = TH1_LEN(n); } if( n>1 && (z[0]=='-' || z[0]=='+') ){ i = 1; } @@ -2859,11 +2886,11 @@ const char *z, int n, double *pfOut ){ if( !sqlite3IsNumber((const char *)z, 0) ){ - Th_ErrorMessage(interp, "expected number, got: \"", z, n); + Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n)); return TH_ERROR; } sqlite3AtoF((const char *)z, pfOut); return TH_OK; Index: src/th.h ================================================================== --- src/th.h +++ src/th.h @@ -1,10 +1,56 @@ - /* This header file defines the external interface to the custom Scripting ** Language (TH) interpreter. TH is very similar to Tcl but is not an ** exact clone. +** +** TH1 was original developed to run SQLite tests on SymbianOS. This version +** of TH1 was repurposed as a scripted language for Fossil, and was heavily +** modified for that purpose, beginning in early 2008. +** +** More recently, TH1 has been enhanced to distinguish between regular text +** and "tainted" text. "Tainted" text is text that might have originated +** from an outside source and hence might not be trustworthy. To prevent +** cross-site scripting (XSS) and SQL-injections and similar attacks, +** tainted text should not be used for the following purposes: +** +** * executed as TH1 script or expression. +** * output as HTML or Javascript +** * used as part of an SQL query +** +** Tainted text can be converted into a safe form using commands like +** "htmlize". And some commands ("query" and "expr") know how to use +** potentially tainted variable values directly, and thus can bypass +** the restrictions above. +** +** Whether a string is clean or tainted is determined by its length integer. +** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length +** (about 268MB - more than sufficient for the purposes of Fossil). The top +** bit of the length integer is the sign bit, of course. The next three bits +** are reserved. One of those, the 0x10000000 bit, marks tainted strings. */ +#define TH1_MX_STRLEN 0x0fffffff /* Maximum length of a TH1-C string */ +#define TH1_TAINT_BIT 0x10000000 /* The taint bit */ +#define TH1_SIGN 0x80000000 + +/* Convert an integer into a string length. Negative values remain negative */ +#define TH1_LEN(X) ((TH1_SIGN|TH1_MX_STRLEN)&(X)) + +/* Return true if the string is tainted */ +#define TH1_TAINTED(X) (((X)&TH1_TAINT_BIT)!=0) + +/* Remove taint from a string */ +#define TH1_RM_TAINT(X) ((X)&~TH1_TAINT_BIT) + +/* Add taint to a string */ +#define TH1_ADD_TAINT(X) ((X)|TH1_TAINT_BIT) + +/* If B is tainted, make A tainted too */ +#define TH1_XFER_TAINT(A,B) (A)|=(TH1_TAINT_BIT&(B)) + +/* Check to see if a string is too big for TH1 */ +#define TH1_SIZECHECK(N) if((N)>TH1_MX_STRLEN){Th_OversizeString();} +void Th_OversizeString(void); /* ** Before creating an interpreter, the application must allocate and ** populate an instance of the following structure. It must remain valid ** for the lifetime of the interpreter. @@ -24,10 +70,16 @@ ** Create and delete interpreters. */ Th_Interp * Th_CreateInterp(Th_Vtab *); void Th_DeleteInterp(Th_Interp *); +/* +** Report taint in the string zStr,nStr. That string represents "zTitle" +** If non-zero is returned error out of the caller. +*/ +int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr); + /* ** Evaluate an TH program in the stack frame identified by parameter ** iFrame, according to the following rules: ** ** * If iFrame is 0, this means the current frame. @@ -56,23 +108,10 @@ int Th_GetVar(Th_Interp *, const char *, int); int Th_SetVar(Th_Interp *, const char *, int, const char *, int); int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); int Th_UnsetVar(Th_Interp *, const char *, int); -/* -** If interp has a variable with the given name, its value is returned -** and its length is returned via *nOut if nOut is not NULL. If -** interp has no such var then NULL is returned without setting any -** error state and *nOut, if not NULL, is set to 0. The returned value -** is owned by the interpreter and may be invalidated the next time -** the interpreter is modified. -** -** zVarName must be NUL-terminated. -*/ -const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName, - int *nOut); - typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); /* ** Register new commands. */ Index: src/th_lang.c ================================================================== --- src/th_lang.c +++ src/th_lang.c @@ -39,11 +39,11 @@ rc = Th_Eval(interp, 0, argv[1], -1); if( argc==3 ){ int nResult; const char *zResult = Th_GetResult(interp, &nResult); - Th_SetVar(interp, argv[2], argl[2], zResult, nResult); + Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult); } Th_SetResultInt(interp, rc); return TH_OK; } @@ -215,15 +215,18 @@ int *argl ){ char *zList = 0; int nList = 0; int i; + int bTaint = 0; for(i=1; i<argc; i++){ + TH1_XFER_TAINT(bTaint,argl[i]); Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); } + TH1_XFER_TAINT(nList, bTaint); Th_SetResult(interp, zList, nList); Th_Free(interp, zList); return TH_OK; } @@ -244,10 +247,11 @@ int *argl ){ char *zList = 0; int nList = 0; int i, rc; + int bTaint = 0; if( argc<2 ){ return Th_WrongNumArgs(interp, "lappend var ..."); } rc = Th_GetVar(interp, argv[1], argl[1]); @@ -254,13 +258,15 @@ if( rc==TH_OK ){ zList = Th_TakeResult(interp, &nList); } for(i=2; i<argc; i++){ + TH1_XFER_TAINT(bTaint, argl[i]); Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]); } + TH1_XFER_TAINT(nList, bTaint); Th_SetVar(interp, argv[1], argl[1], zList, nList); Th_SetResult(interp, zList, nList); Th_Free(interp, zList); return TH_OK; @@ -356,13 +362,14 @@ return Th_WrongNumArgs(interp, "lsearch list string"); } rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); if( rc==TH_OK ){ + int nn = TH1_LEN(argl[2]); Th_SetResultInt(interp, -1); for(i=0; i<nCount; i++){ - if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){ + if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){ Th_SetResultInt(interp, i); break; } } Th_Free(interp, azElem); @@ -561,20 +568,21 @@ int nUsage = 0; /* Number of bytes at zUsage */ if( argc!=4 ){ return Th_WrongNumArgs(interp, "proc name arglist code"); } - if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){ + if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), + &azParam, &anParam, &nParam) ){ return TH_ERROR; } /* Allocate the new ProcDefn structure. */ nByte = sizeof(ProcDefn) + /* ProcDefn structure */ (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ - argl[3] + /* zProgram */ - argl[2]; /* Space for copies of parameter names and default values */ + TH1_LEN(argl[3]) + /* zProgram */ + TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */ p = (ProcDefn *)Th_Malloc(interp, nByte); /* If the last parameter in the parameter list is "args", then set the ** ProcDefn.hasArgs flag. The "args" parameter does not require an ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. @@ -590,12 +598,12 @@ p->azParam = (char **)&p[1]; p->anParam = (int *)&p->azParam[nParam]; p->azDefault = (char **)&p->anParam[nParam]; p->anDefault = (int *)&p->azDefault[nParam]; p->zProgram = (char *)&p->anDefault[nParam]; - memcpy(p->zProgram, argv[3], argl[3]); - p->nProgram = argl[3]; + memcpy(p->zProgram, argv[3], TH1_LEN(argl[3])); + p->nProgram = TH1_LEN(argl[3]); zSpace = &p->zProgram[p->nProgram]; for(i=0; i<nParam; i++){ char **az; int *an; @@ -672,11 +680,12 @@ int *argl ){ if( argc!=3 ){ return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); } - return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]); + return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]), + argv[2], TH1_LEN(argl[2])); } /* ** TH Syntax: ** @@ -746,13 +755,13 @@ if( argc!=4 ){ return Th_WrongNumArgs(interp, "string compare str1 str2"); } zLeft = argv[2]; - nLeft = argl[2]; + nLeft = TH1_LEN(argl[2]); zRight = argv[3]; - nRight = argl[3]; + nRight = TH1_LEN(argl[3]); for(i=0; iRes==0 && i<nLeft && i<nRight; i++){ iRes = zLeft[i]-zRight[i]; } if( iRes==0 ){ @@ -779,12 +788,12 @@ if( argc!=4 ){ return Th_WrongNumArgs(interp, "string first needle haystack"); } - nNeedle = argl[2]; - nHaystack = argl[3]; + nNeedle = TH1_LEN(argl[2]); + nHaystack = TH1_LEN(argl[3]); if( nNeedle && nHaystack && nNeedle<=nHaystack ){ const char *zNeedle = argv[2]; const char *zHaystack = argv[3]; int i; @@ -812,19 +821,19 @@ if( argc!=4 ){ return Th_WrongNumArgs(interp, "string index string index"); } - if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){ - iIndex = argl[2]-1; + if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){ + iIndex = TH1_LEN(argl[2])-1; }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ Th_ErrorMessage( interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); return TH_ERROR; } - if( iIndex>=0 && iIndex<argl[2] ){ + if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){ return Th_SetResult(interp, &argv[2][iIndex], 1); }else{ return Th_SetResult(interp, 0, 0); } } @@ -838,41 +847,44 @@ Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ if( argc!=4 ){ return Th_WrongNumArgs(interp, "string is class string"); } - if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){ + if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){ int i; int iRes = 1; - for(i=0; i<argl[3]; i++){ + for(i=0; i<TH1_LEN(argl[3]); i++){ if( !th_isalnum(argv[3][i]) ){ iRes = 0; } } return Th_SetResultInt(interp, iRes); - }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){ + }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){ double fVal; if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){ return Th_SetResultInt(interp, 1); } return Th_SetResultInt(interp, 0); - }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){ + }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){ int iVal; if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){ return Th_SetResultInt(interp, 1); } return Th_SetResultInt(interp, 0); - }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){ + }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){ if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){ return Th_SetResultInt(interp, 1); } return Th_SetResultInt(interp, 0); + }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){ + return Th_SetResultInt(interp, TH1_TAINTED(argl[3])); }else{ Th_ErrorMessage(interp, - "Expected alnum, double, integer, or list, got:", argv[2], argl[2]); + "Expected alnum, double, integer, list, or tainted, got:", + argv[2], TH1_LEN(argl[2])); return TH_ERROR; } } /* @@ -889,12 +901,12 @@ if( argc!=4 ){ return Th_WrongNumArgs(interp, "string last needle haystack"); } - nNeedle = argl[2]; - nHaystack = argl[3]; + nNeedle = TH1_LEN(argl[2]); + nHaystack = TH1_LEN(argl[3]); if( nNeedle && nHaystack && nNeedle<=nHaystack ){ const char *zNeedle = argv[2]; const char *zHaystack = argv[3]; int i; @@ -919,11 +931,11 @@ Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ if( argc!=3 ){ return Th_WrongNumArgs(interp, "string length string"); } - return Th_SetResultInt(interp, argl[2]); + return Th_SetResultInt(interp, TH1_LEN(argl[2])); } /* ** TH Syntax: ** @@ -938,12 +950,12 @@ char *zPat, *zStr; int rc; if( argc!=4 ){ return Th_WrongNumArgs(interp, "string match pattern string"); } - zPat = fossil_strndup(argv[2],argl[2]); - zStr = fossil_strndup(argv[3],argl[3]); + zPat = fossil_strndup(argv[2],TH1_LEN(argl[2])); + zStr = fossil_strndup(argv[3],TH1_LEN(argl[3])); rc = sqlite3_strglob(zPat,zStr); fossil_free(zPat); fossil_free(zStr); return Th_SetResultInt(interp, !rc); } @@ -961,23 +973,23 @@ if( argc!=5 ){ return Th_WrongNumArgs(interp, "string range string first last"); } - if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){ - iEnd = argl[2]; + if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){ + iEnd = TH1_LEN(argl[2]); }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ Th_ErrorMessage( - interp, "Expected \"end\" or integer, got:", argv[4], argl[4]); + interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4])); return TH_ERROR; } if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ return TH_ERROR; } if( iStart<0 ) iStart = 0; - if( iEnd>=argl[2] ) iEnd = argl[2]-1; + if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1; if( iStart>iEnd ) iEnd = iStart-1; return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1); } @@ -989,27 +1001,33 @@ static int string_repeat_command( Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl ){ int n; int i; - int nByte; + int sz; + long long int nByte; char *zByte; if( argc!=4 ){ return Th_WrongNumArgs(interp, "string repeat string n"); } if( Th_ToInt(interp, argv[3], argl[3], &n) ){ return TH_ERROR; } - nByte = argl[2] * n; + nByte = n; + sz = TH1_LEN(argl[2]); + nByte *= sz; + TH1_SIZECHECK(nByte+1); zByte = Th_Malloc(interp, nByte+1); - for(i=0; i<nByte; i+=argl[2]){ - memcpy(&zByte[i], argv[2], argl[2]); + for(i=0; i<nByte; i+=sz){ + memcpy(&zByte[i], argv[2], sz); } - Th_SetResult(interp, zByte, nByte); + n = nByte; + TH1_XFER_TAINT(n, argl[2]); + Th_SetResult(interp, zByte, n); Th_Free(interp, zByte); return TH_OK; } /* @@ -1027,15 +1045,15 @@ if( argc!=3 ){ return Th_WrongNumArgs(interp, "string trim string"); } z = argv[2]; - n = argl[2]; - if( argl[1]<5 || argv[1][4]=='l' ){ + n = TH1_LEN(argl[2]); + if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){ while( n && th_isspace(z[0]) ){ z++; n--; } } - if( argl[1]<5 || argv[1][4]=='r' ){ + if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){ while( n && th_isspace(z[n-1]) ){ n--; } } Th_SetResult(interp, z, n); return TH_OK; } @@ -1051,11 +1069,11 @@ int rc; if( argc!=3 ){ return Th_WrongNumArgs(interp, "info exists var"); } - rc = Th_ExistsVar(interp, argv[2], argl[2]); + rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2])); Th_SetResultInt(interp, rc); return TH_OK; } /* @@ -1117,11 +1135,11 @@ int rc; if( argc!=3 ){ return Th_WrongNumArgs(interp, "array exists var"); } - rc = Th_ExistsArrayVar(interp, argv[2], argl[2]); + rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2])); Th_SetResultInt(interp, rc); return TH_OK; } /* @@ -1137,11 +1155,11 @@ int nElem = 0; if( argc!=3 ){ return Th_WrongNumArgs(interp, "array names varname"); } - rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem); + rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem); if( rc!=TH_OK ){ return rc; } Th_SetResult(interp, zElem, nElem); if( zElem ) Th_Free(interp, zElem); @@ -1161,11 +1179,11 @@ int *argl ){ if( argc!=2 ){ return Th_WrongNumArgs(interp, "unset var"); } - return Th_UnsetVar(interp, argv[1], argl[1]); + return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1])); } int Th_CallSubCommand( Th_Interp *interp, void *ctx, @@ -1176,19 +1194,22 @@ ){ if( argc>1 ){ int i; for(i=0; aSub[i].zName; i++){ const char *zName = aSub[i].zName; - if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){ + if( th_strlen(zName)==TH1_LEN(argl[1]) + && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){ return aSub[i].xProc(interp, ctx, argc, argv, argl); } } } if(argc<2){ - Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]); + Th_ErrorMessage(interp, "Expected sub-command for", + argv[0], TH1_LEN(argl[0])); }else{ - Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]); + Th_ErrorMessage(interp, "Expected sub-command, got:", + argv[1], TH1_LEN(argl[1])); } return TH_ERROR; } /* @@ -1319,11 +1340,11 @@ int iFrame = -1; if( argc!=2 && argc!=3 ){ return Th_WrongNumArgs(interp, "uplevel ?level? script..."); } - if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){ + if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){ return TH_ERROR; } return Th_Eval(interp, iFrame, argv[argc-1], -1); } @@ -1342,19 +1363,20 @@ int iVar = 1; int iFrame = -1; int rc = TH_OK; int i; - if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){ + if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){ iVar++; } if( argc==iVar || (argc-iVar)%2 ){ return Th_WrongNumArgs(interp, "upvar frame othervar myvar ?othervar myvar...?"); } for(i=iVar; rc==TH_OK && i<argc; i=i+2){ - rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]); + rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]), + iFrame, argv[i], TH1_LEN(argl[i])); } return rc; } /* Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -262,11 +262,11 @@ ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "httpize STRING"); } - zOut = httpize((char*)argv[1], argl[1]); + zOut = httpize((char*)argv[1], TH1_LEN(argl[1])); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } @@ -291,11 +291,12 @@ if( argc<2 || argc>3 ){ return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN"); } rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput); if( g.thTrace ){ - Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput); + Th_Trace("enable_output {%.*s} -> %d<br>\n", + TH1_LEN(argl[1]),argv[1],enableOutput); } return rc; } /* @@ -322,11 +323,11 @@ buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1; Th_SetResultInt(g.interp, buul); if(argc>1){ if( g.thTrace ){ Th_Trace("enable_htmlify {%.*s} -> %d<br>\n", - argl[1],argv[1],buul); + TH1_LEN(argl[1]),argv[1],buul); } rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul); if(!rc){ if(buul){ g.th1Flags &= ~TH_INIT_NO_ENCODE; @@ -381,19 +382,23 @@ ** g.th1Flags has the TH_INIT_NO_ENCODE flag. ** ** If pOut is NULL and the global pThOut is not then that blob ** is used for output. */ -static void sendText(Blob * pOut, const char *z, int n, int encode){ +static void sendText(Blob *pOut, const char *z, int n, int encode){ if(0==pOut && pThOut!=0){ pOut = pThOut; } if(TH_INIT_NO_ENCODE & g.th1Flags){ encode = 0; } if( enableOutput && n ){ - if( n<0 ) n = strlen(z); + if( n<0 ){ + n = strlen(z); + }else{ + n = TH1_LEN(n); + } if( encode ){ z = htmlize(z, n); n = strlen(z); } if(pOut!=0){ @@ -525,14 +530,23 @@ void *pConvert, int argc, const char **argv, int *argl ){ + int encode = *(unsigned int*)pConvert; + int n; if( argc!=2 ){ return Th_WrongNumArgs(interp, "puts STRING"); } - sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert); + n = argl[1]; + if( encode==0 && n>0 && TH1_TAINTED(n) ){ + if( Th_ReportTaint(interp, "output string", argv[1], n) ){ + return TH_ERROR; + } + n = TH1_LEN(n); + } + sendText(0,(char*)argv[1], n, encode); return TH_OK; } /* ** TH1 command: redirect URL ?withMethod? @@ -557,10 +571,15 @@ } if( argc==3 ){ if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){ return TH_ERROR; } + } + if( TH1_TAINTED(argl[1]) + && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1]) + ){ + return TH_ERROR; } if( withMethod ){ cgi_redirect_with_method(argv[1]); }else{ cgi_redirect(argv[1]); @@ -660,11 +679,11 @@ int nValue = 0; if( argc!=2 ){ return Th_WrongNumArgs(interp, "markdown STRING"); } blob_zero(&src); - blob_init(&src, (char*)argv[1], argl[1]); + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); blob_zero(&title); blob_zero(&body); markdown_to_html(&src, &title, &body); Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title)); Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body)); Th_SetResult(interp, zValue, nValue); @@ -690,11 +709,11 @@ if( argc!=2 ){ return Th_WrongNumArgs(interp, "wiki STRING"); } if( enableOutput ){ Blob src; - blob_init(&src, (char*)argv[1], argl[1]); + blob_init(&src, (char*)argv[1], TH1_LEN(argl[1])); wiki_convert(&src, 0, flags); blob_reset(&src); } return TH_OK; } @@ -735,11 +754,11 @@ ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "htmlize STRING"); } - zOut = htmlize((char*)argv[1], argl[1]); + zOut = htmlize((char*)argv[1], TH1_LEN(argl[1])); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } @@ -757,11 +776,11 @@ ){ char *zOut; if( argc!=2 ){ return Th_WrongNumArgs(interp, "encode64 STRING"); } - zOut = encode64((char*)argv[1], argl[1]); + zOut = encode64((char*)argv[1], TH1_LEN(argl[1])); Th_SetResult(interp, zOut, -1); free(zOut); return TH_OK; } @@ -778,11 +797,11 @@ int argc, const char **argv, int *argl ){ char *zOut; - if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){ + if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){ zOut = db_text("??", "SELECT datetime('now',toLocal())"); }else{ zOut = db_text("??", "SELECT datetime('now')"); } Th_SetResult(interp, zOut, -1); @@ -810,13 +829,13 @@ if( argc<2 ){ return Th_WrongNumArgs(interp, "hascap STRING ..."); } for(i=1; rc==1 && i<argc; i++){ if( g.thTrace ){ - Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]); + Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i])); } - rc = login_has_capability((char*)argv[i],argl[i],*(int*)p); + rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p); } if( g.thTrace ){ Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc); Th_Free(interp, zCapList); } @@ -858,11 +877,11 @@ int i; if( argc!=2 ){ return Th_WrongNumArgs(interp, "capexpr EXPR"); } - rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap); + rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap); if( rc ) return rc; rc = 0; for(i=0; i<nCap; i++){ if( azCap[i][0]=='!' ){ rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0); @@ -921,11 +940,12 @@ if( argc<2 ){ return Th_WrongNumArgs(interp, "hascap STRING ..."); } for(i=1; i<argc && rc; i++){ int match = 0; - for(j=0; j<argl[i]; j++){ + int nn = TH1_LEN(argl[i]); + for(j=0; j<nn; j++){ switch( argv[i][j] ){ case 'c': match |= searchCap & SRCH_CKIN; break; case 'd': match |= searchCap & SRCH_DOC; break; case 't': match |= searchCap & SRCH_TKT; break; case 'w': match |= searchCap & SRCH_WIKI; break; @@ -932,11 +952,11 @@ } } if( !match ) rc = 0; } if( g.thTrace ){ - Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc); + Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } @@ -1051,11 +1071,11 @@ #endif else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){ rc = 1; } if( g.thTrace ){ - Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc); + Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc); } Th_SetResultInt(interp, rc); return TH_OK; } @@ -1104,18 +1124,20 @@ const char **argv, int *argl ){ int rc = 0; int i; + int nn; if( argc!=2 ){ return Th_WrongNumArgs(interp, "anycap STRING"); } - for(i=0; rc==0 && i<argl[1]; i++){ + nn = TH1_LEN(argl[1]); + for(i=0; rc==0 && i<nn; i++){ rc = login_has_capability((char*)&argv[1][i],1,0); } if( g.thTrace ){ - Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc); + Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc); } Th_SetResultInt(interp, rc); return TH_OK; } @@ -1140,22 +1162,23 @@ return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); } if( enableOutput ){ int height; Blob name; - int nValue; + int nValue = 0; const char *zValue; char *z, *zH; int nElem; int *aszElem; char **azElem; int i; if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; - Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem); - blob_init(&name, (char*)argv[1], argl[1]); + Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem); + blob_init(&name, (char*)argv[1], TH1_LEN(argl[1])); zValue = Th_Fetch(blob_str(&name), &nValue); + nValue = TH1_LEN(nValue); zH = htmlize(blob_buffer(&name), blob_size(&name)); z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height); free(zH); sendText(0,z, -1, 0); free(z); @@ -1247,11 +1270,11 @@ return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); } if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; z = argv[1]; - size = argl[1]; + size = TH1_LEN(argl[1]); for(n=1, i=0; i<size; i++){ if( z[i]=='\n' ){ n++; if( n>=iMax ) break; } @@ -1407,11 +1430,12 @@ return TH_OK; }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){ Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1); return TH_OK; }else{ - Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]); + Th_ErrorMessage(interp, "unsupported global state:", + argv[1], TH1_LEN(argl[1])); return TH_ERROR; } } /* @@ -1848,10 +1872,47 @@ sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x); Th_SetResult(interp, zUTime, -1); return TH_OK; } +/* +** TH1 command: taint STRING +** +** Return a copy of STRING that is marked as tainted. +*/ +static int taintCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "STRING"); + } + Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1])); + return TH_OK; +} + +/* +** TH1 command: untaint STRING +** +** Return a copy of STRING that is marked as untainted. +*/ +static int untaintCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "STRING"); + } + Th_SetResult(interp, argv[1], TH1_LEN(argl[1])); + return TH_OK; +} /* ** TH1 command: randhex N ** ** Return N*2 random hexadecimal digits with N<50. If N is omitted, @@ -1923,11 +1984,13 @@ int res = TH_OK; int nVar; char *zErr = 0; int noComplain = 0; - if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){ + if( argc>3 && TH1_LEN(argl[1])==11 + && strncmp(argv[1], "-nocomplain", 11)==0 + ){ argc--; argv++; argl++; noComplain = 1; } @@ -1939,15 +2002,22 @@ Th_ErrorMessage(interp, "database is not open", 0, 0); return TH_ERROR; } zSql = argv[1]; nSql = argl[1]; + if( TH1_TAINTED(nSql) ){ + if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){ + return TH_ERROR; + } + nSql = TH1_LEN(nSql); + } + while( res==TH_OK && nSql>0 ){ zErr = 0; report_restrict_sql(&zErr); g.dbIgnoreErrors++; - rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail); + rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail); g.dbIgnoreErrors--; report_unrestrict_sql(); if( rc!=0 || zErr!=0 ){ if( noComplain ) return TH_OK; Th_ErrorMessage(interp, "SQL error: ", @@ -1964,31 +2034,31 @@ int szVar = zVar ? th_strlen(zVar) : 0; if( szVar>1 && zVar[0]=='$' && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){ int nVal; const char *zVal = Th_GetResult(interp, &nVal); - sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT); + sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT); } } while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){ int nCol = sqlite3_column_count(pStmt); for(i=0; i<nCol; i++){ const char *zCol = sqlite3_column_name(pStmt, i); int szCol = th_strlen(zCol); const char *zVal = (const char*)sqlite3_column_text(pStmt, i); int szVal = sqlite3_column_bytes(pStmt, i); - Th_SetVar(interp, zCol, szCol, zVal, szVal); + Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal)); } if( g.thTrace ){ - Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]); + Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]); } - res = Th_Eval(interp, 0, argv[2], argl[2]); + res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2])); if( g.thTrace ){ int nTrRes; char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); Th_Trace("[query_eval] => %h {%#h}<br>\n", - Th_ReturnCodeName(res, 0), nTrRes, zTrRes); + Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes); } if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK; } rc = sqlite3_finalize(pStmt); if( rc!=SQLITE_OK ){ @@ -2038,11 +2108,11 @@ Th_SetResult(interp, 0, 0); rc = TH_OK; } if( g.thTrace ){ Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "", - argl[nArg], argv[nArg], rc); + TH1_LEN(argl[nArg]), argv[nArg], rc); } return rc; } /* @@ -2121,11 +2191,11 @@ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); } zErr = re_compile(&pRe, argv[nArg], noCase); if( !zErr ){ Th_SetResultInt(interp, re_match(pRe, - (const unsigned char *)argv[nArg+1], argl[nArg+1])); + (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1]))); rc = TH_OK; }else{ Th_SetResult(interp, zErr, -1); rc = TH_ERROR; } @@ -2160,11 +2230,11 @@ UrlData urlData; if( argc<2 || argc>5 ){ return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS); } - if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){ + if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){ fAsynchronous = 1; nArg++; } if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++; if( nArg+1!=argc && nArg+2!=argc ){ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS); @@ -2189,11 +2259,11 @@ return TH_ERROR; } re_free(pRe); blob_zero(&payload); if( nArg+2==argc ){ - blob_append(&payload, argv[nArg+1], argl[nArg+1]); + blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1])); zType = "POST"; }else{ zType = "GET"; } if( fAsynchronous ){ @@ -2268,11 +2338,11 @@ if( argc!=2 ){ return Th_WrongNumArgs(interp, "captureTh1 STRING"); } pOrig = Th_SetOutputBlob(&out); zStr = argv[1]; - nStr = argl[1]; + nStr = TH1_LEN(argl[1]); rc = Th_Eval(g.interp, 0, zStr, nStr); Th_SetOutputBlob(pOrig); if(0==rc){ Th_SetResult(g.interp, blob_str(&out), blob_size(&out)); } @@ -2387,13 +2457,15 @@ {"setting", settingCmd, 0}, {"styleFooter", styleFooterCmd, 0}, {"styleHeader", styleHeaderCmd, 0}, {"styleScript", styleScriptCmd, 0}, {"submenu", submenuCmd, 0}, + {"taint", taintCmd, 0}, {"tclReady", tclReadyCmd, 0}, {"trace", traceCmd, 0}, {"stime", stimeCmd, 0}, + {"untaint", untaintCmd, 0}, {"unversioned", unversionedCmd, 0}, {"utime", utimeCmd, 0}, {"verifyCsrf", verifyCsrfCmd, 0}, {"verifyLogin", verifyLoginCmd, 0}, {"wiki", wikiCmd, (void*)&aFlags[0]}, @@ -2494,10 +2566,26 @@ Th_Trace("set %h {%h}<br>\n", zName, zValue); } Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue)); } } + +/* +** Store a string value in a variable in the interpreter +** with the "taint" marking, so that TH1 knows that this +** variable contains content under the control of the remote +** user and presents a risk of XSS or SQL-injection attacks. +*/ +void Th_StoreUnsafe(const char *zName, const char *zValue){ + Th_FossilInit(TH_INIT_DEFAULT); + if( zValue ){ + if( g.thTrace ){ + Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue); + } + Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue))); + } +} /* ** Appends an element to a TH1 list value. This function is called by the ** transfer subsystem; therefore, it must be very careful to avoid doing ** any unnecessary work. To that end, the TH1 subsystem will not be called @@ -2680,10 +2768,11 @@ char *zResult = (char*)Th_GetResult(g.interp, &nResult); /* ** Make sure that the TH1 script error was not caused by a "missing" ** command hook handler as that is not actually an error condition. */ + nResult = TH1_LEN(nResult); if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){ sendError(0,zResult, nResult, 0); }else{ /* ** There is no command hook handler "installed". This situation @@ -2767,10 +2856,11 @@ char *zResult = (char*)Th_GetResult(g.interp, &nResult); /* ** Make sure that the TH1 script error was not caused by a "missing" ** webpage hook handler as that is not actually an error condition. */ + nResult = TH1_LEN(nResult); if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){ sendError(0,zResult, nResult, 1); }else{ /* ** There is no webpage hook handler "installed". This situation @@ -2907,11 +2997,11 @@ rc = Th_Eval(g.interp, 0, (const char*)z, i); if( g.thTrace ){ int nTrRes; char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes); Th_Trace("[render_eval] => %h {%#h}<br>\n", - Th_ReturnCodeName(rc, 0), nTrRes, zTrRes); + Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes); } if( rc!=TH_OK ) break; z += i; if( z[0] ){ z += 6; } i = 0; @@ -2953,10 +3043,77 @@ ** as appropriate. We need to pass on g.th1Flags for the case of ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get ** inadvertently toggled off by a recursive call. */; } + +/* +** SETTING: vuln-report width=8 default=log +** +** This setting controls Fossil's behavior when it encounters a potential +** XSS or SQL-injection vulnerability due to misuse of TH1 configuration +** scripts. Choices are: +** +** off Do nothing. Ignore the vulnerability. +** +** log Write a report of the problem into the error log. +** +** block Like "log" but also prevent the offending TH1 command +** from running. +** +** fatal Render an error message page instead of the requested +** page. +*/ + +/* +** Report misuse of a tainted string in TH1. +** +** The behavior depends on the vuln-report setting. If "off", this routine +** is a no-op. Otherwise, right a message into the error log. If +** vuln-report is "log", that is all that happens. But for any other +** value of vuln-report, a fatal error is raised. +*/ +int Th_ReportTaint( + Th_Interp *interp, /* Report error here, if an error is reported */ + const char *zWhere, /* Where the tainted string appears */ + const char *zStr, /* The tainted string */ + int nStr /* Length of the tainted string */ +){ + char *zDisp; /* Dispensation */ + const char *zVulnType; /* Type of vulnerability */ + + zDisp = db_get("vuln-report","log"); + if( is_false(zDisp) ) return 0; + if( strstr(zWhere,"SQL")!=0 ){ + zVulnType = "SQL-injection"; + }else{ + zVulnType = "XSS"; + } + nStr = TH1_LEN(nStr); + fossil_errorlog("possible %s vulnerability due to tainted TH1 %s: \"%.*s\"", + zVulnType, zWhere, nStr, zStr); + if( strcmp(zDisp,"log")==0 ){ + return 0; + } + if( strcmp(zDisp,"block")==0 ){ + char *z = mprintf("tainted %s: \"", zWhere); + Th_ErrorMessage(interp, z, zStr, nStr); + fossil_free(z); + }else{ + char *z = mprintf("%#h", nStr, zStr); + cgi_reset_content(); + style_submenu_enable(0); + style_set_current_feature("error"); + style_header("Configuration Error"); + @ <p>Error in a TH1 configuration script: + @ tainted %h(zWhere): "%z(z)" + style_finish_page(); + cgi_reply(); + fossil_exit(1); + } + return 1; +} /* ** COMMAND: test-th-render ** ** Usage: %fossil test-th-render FILE @@ -2992,10 +3149,11 @@ if( find_option("set-user-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); login_set_capabilities(zCap ? zCap : "sx", 0); g.useLocalauth = 1; } + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); verify_all_options(); if( g.argc<3 ){ usage("FILE"); } blob_zero(&in); @@ -3044,10 +3202,11 @@ if( find_option("set-user-caps", 0, 0)!=0 ){ const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS"); login_set_capabilities(zCap ? zCap : "sx", 0); g.useLocalauth = 1; } + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); verify_all_options(); if( g.argc!=3 ){ usage("script"); } if(file_isfile(g.argv[2], ExtFILE)){ Index: src/th_tcl.c ================================================================== --- src/th_tcl.c +++ src/th_tcl.c @@ -41,16 +41,16 @@ #define USE_ARGV_TO_OBJV() \ int objc; \ Tcl_Obj **objv; \ int obji; -#define COPY_ARGV_TO_OBJV() \ - objc = argc-1; \ - objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ - for(obji=1; obji<argc; obji++){ \ - objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]); \ - Tcl_IncrRefCount(objv[obji-1]); \ +#define COPY_ARGV_TO_OBJV() \ + objc = argc-1; \ + objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ + for(obji=1; obji<argc; obji++){ \ + objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \ + Tcl_IncrRefCount(objv[obji-1]); \ } #define FREE_ARGV_TO_OBJV() \ for(obji=1; obji<argc; obji++){ \ Tcl_DecrRefCount(objv[obji-1]); \ @@ -449,11 +449,11 @@ } xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval; if( xNotifyProc ){ rc = xNotifyProc(bIsPost ? tclContext->pPostContext : tclContext->pPreContext, - interp, ctx, argc, argv, argl, rc); + interp, ctx, argc, argv, TH1_LEN(argl), rc); } return rc; } /* @@ -485,17 +485,17 @@ tclInterp = GET_CTX_TCL_INTERP(ctx); if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); return TH_ERROR; } - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc); if( rc!=TH_OK ){ return rc; } Tcl_Preserve((ClientData)tclInterp); if( argc==2 ){ - objPtr = Tcl_NewStringObj(argv[1], argl[1]); + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); Tcl_IncrRefCount(objPtr); rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); Tcl_DecrRefCount(objPtr); objPtr = 0; }else{ USE_ARGV_TO_OBJV(); @@ -507,11 +507,11 @@ FREE_ARGV_TO_OBJV(); } zResult = getTclResult(tclInterp, &nResult); Th_SetResult(interp, zResult, nResult); Tcl_Release((ClientData)tclInterp); - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl), getTh1ReturnCode(rc)); return rc; } /* @@ -545,17 +545,17 @@ tclInterp = GET_CTX_TCL_INTERP(ctx); if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); return TH_ERROR; } - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc); if( rc!=TH_OK ){ return rc; } Tcl_Preserve((ClientData)tclInterp); if( argc==2 ){ - objPtr = Tcl_NewStringObj(argv[1], argl[1]); + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); Tcl_IncrRefCount(objPtr); rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); Tcl_DecrRefCount(objPtr); objPtr = 0; }else{ USE_ARGV_TO_OBJV(); @@ -574,11 +574,11 @@ Th_SetResult(interp, zResult, nResult); if( rc==TCL_OK ){ Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; } Tcl_Release((ClientData)tclInterp); - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl), getTh1ReturnCode(rc)); return rc; } /* @@ -610,20 +610,20 @@ tclInterp = GET_CTX_TCL_INTERP(ctx); if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); return TH_ERROR; } - rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc); + rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, TH1_LEN(argl), rc); if( rc!=TH_OK ){ return rc; } Tcl_Preserve((ClientData)tclInterp); #if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV if( GET_CTX_TCL_USEOBJPROC(ctx) ){ Tcl_Command command; Tcl_CmdInfo cmdInfo; - Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]); + Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); Tcl_IncrRefCount(objPtr); command = Tcl_GetCommandFromObj(tclInterp, objPtr); if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); Tcl_DecrRefCount(objPtr); objPtr = 0; @@ -649,11 +649,11 @@ FREE_ARGV_TO_OBJV(); } zResult = getTclResult(tclInterp, &nResult); Th_SetResult(interp, zResult, nResult); Tcl_Release((ClientData)tclInterp); - rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl, + rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, TH1_LEN(argl), getTh1ReturnCode(rc)); return rc; } /* @@ -782,11 +782,11 @@ return TCL_ERROR; } arg = Tcl_GetStringFromObj(objv[1], &nArg); rc = Th_Eval(th1Interp, 0, arg, nArg); arg = Th_GetResult(th1Interp, &nArg); - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); return getTclReturnCode(rc); } /* ** Tcl command: th1Expr arg @@ -815,11 +815,11 @@ return TCL_ERROR; } arg = Tcl_GetStringFromObj(objv[1], &nArg); rc = Th_Expr(th1Interp, arg, nArg); arg = Th_GetResult(th1Interp, &nArg); - Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg)); + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); return getTclReturnCode(rc); } /* ** Array of Tcl integration commands. Used when adding or removing the Tcl Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -1888,11 +1888,11 @@ if( zTagName ){ zType = "ci"; if( matchStyle==MS_EXACT ){ /* For exact maching, inhibit links to the selected tag. */ zThisTag = zTagName; - Th_Store("current_checkin", zTagName); + Th_StoreUnsafe("current_checkin", zTagName); } /* Display a checkbox to enable/disable display of related check-ins. */ if( advancedMenu ){ style_submenu_checkbox("rel", "Related", 0, 0); Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -210,21 +210,21 @@ zVal = zRevealed = db_reveal(zVal); } if( (j = fieldId(zName))>=0 ){ aField[j].zValue = mprintf("%s", zVal); }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){ - Th_Store(zName, zVal); + Th_StoreUnsafe(zName, zVal); } free(zRevealed); } Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2))); Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3))); } db_finalize(&q); for(i=0; i<nField; i++){ if( Th_Fetch(aField[i].zName, &size)==0 ){ - Th_Store(aField[i].zName, aField[i].zValue); + Th_StoreUnsafe(aField[i].zName, aField[i].zValue); } } } /* @@ -233,11 +233,11 @@ static void initializeVariablesFromCGI(void){ int i; const char *z; for(i=0; (z = cgi_parameter_name(i))!=0; i++){ - Th_Store(z, P(z)); + Th_StoreUnsafe(z, P(z)); } } /* ** Information about a single J-card @@ -818,15 +818,15 @@ if( argc!=3 ){ return Th_WrongNumArgs(interp, "append_field FIELD STRING"); } if( g.thTrace ){ Th_Trace("append_field %#h {%#h}<br>\n", - argl[1], argv[1], argl[2], argv[2]); + TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]); } for(idx=0; idx<nField; idx++){ - if( memcmp(aField[idx].zName, argv[1], argl[1])==0 - && aField[idx].zName[argl[1]]==0 ){ + if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0 + && aField[idx].zName[TH1_LEN(argl[1])]==0 ){ break; } } if( idx>=nField ){ Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]); @@ -938,10 +938,11 @@ const char *zValue; int nValue; if( aField[i].zAppend ) continue; zValue = Th_Fetch(aField[i].zName, &nValue); if( zValue ){ + nValue = TH1_LEN(nValue); while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; } if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0) || memcmp(zValue, aField[i].zValue, nValue)!=0 ||(int)strlen(aField[i].zValue)!=nValue ){ @@ -1040,16 +1041,16 @@ if( uid ){ char * zEmail = db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d", uid); if( zEmail ){ - Th_Store("private_contact", zEmail); + Th_StoreUnsafe("private_contact", zEmail); fossil_free(zEmail); } } } - Th_Store("login", login_name()); + Th_StoreUnsafe("login", login_name()); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid, 0); if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ @@ -1120,11 +1121,11 @@ initializeVariablesFromDb(); if( g.zPath[0]=='d' ) showAllFields(); form_begin(0, "%R/%s", g.zPath); @ <input type="hidden" name="name" value="%s(zName)"> zScript = ticket_editpage_code(); - Th_Store("login", login_name()); + Th_StoreUnsafe("login", login_name()); Th_Store("date", db_text(0, "SELECT datetime('now')")); Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1); if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ Index: src/tktsetup.c ================================================================== --- src/tktsetup.c +++ src/tktsetup.c @@ -481,11 +481,11 @@ @ <th1> @ if {[info exists tkt_uuid]} { @ html "<td class='tktDspValue' colspan='3'>" @ copybtn hash-tk 0 $tkt_uuid 2 @ if {[hascap s]} { -@ html " ($tkt_id)" +@ puts " ($tkt_id)" @ } @ html "</td></tr>\n" @ } else { @ if {[hascap s]} { @ html "<td class='tktDspValue' colspan='3'>Deleted " @@ -522,24 +522,24 @@ @ $<resolution> @ </td></tr> @ <tr><td class="tktDspLabel">Last Modified:</td><td class="tktDspValue"> @ <th1> @ if {[info exists tkt_datetime]} { -@ html $tkt_datetime +@ puts $tkt_datetime @ } @ if {[info exists tkt_mage]} { -@ html "<br>$tkt_mage" +@ html "<br>[htmlize $tkt_mage] ago" @ } @ </th1> @ </td> @ <td class="tktDspLabel">Created:</td><td class="tktDspValue"> @ <th1> @ if {[info exists tkt_datetime_creation]} { -@ html $tkt_datetime_creation +@ puts $tkt_datetime_creation @ } @ if {[info exists tkt_cage]} { -@ html "<br>$tkt_cage" +@ html "<br>[htmlize $tkt_cage] ago" @ } @ </th1> @ </td></tr> @ <th1>enable_output [hascap e]</th1> @ <tr> @@ -614,19 +614,19 @@ @ html "User Comments:</td></tr>\n" @ html "<tr><td colspan='5' class='tktDspValue'>\n" @ set seenRow 1 @ } @ html "<span class='tktDspCommenter'>" -@ html "[htmlize $xlogin]" +@ puts $xlogin @ if {$xlogin ne $xusername && [string length $xusername]>0} { -@ html " (claiming to be [htmlize $xusername])" +@ puts " (claiming to be $xusername)" @ } -@ html " added on $xdate:" +@ puts " added on $xdate:" @ html "</span>\n" @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { @ set r [randhex] -@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" @ } elseif {$xmimetype eq "text/x-markdown"} { @ html [lindex [markdown $xcomment] 1] @@ -801,19 +801,19 @@ @ html "Previous User Comments:</td></tr>\n" @ html "<tr><td colspan='2' class='tktDspValue'>\n" @ set seenRow 1 @ } @ html "<span class='tktDspCommenter'>" -@ html "[htmlize $xlogin]" +@ puts $xlogin @ if {$xlogin ne $xusername && [string length $xusername]>0} { -@ html " (claiming to be [htmlize $xusername])" +@ puts " (claiming to be $xusername)" @ } -@ html " added on $xdate:" +@ puts " added on $xdate:" @ html "</span>\n" @ if {$alwaysPlaintext || $xmimetype eq "text/plain"} { @ set r [randhex] -@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"} +@ if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"} @ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n" @ } elseif {$xmimetype eq "text/x-fossil-wiki"} { @ wiki "<p>\n[string trimright $xcomment]\n</p>\n" @ } elseif {$xmimetype eq "text/x-markdown"} { @ html [lindex [markdown $xcomment] 1]