Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -75,13 +75,13 @@ zFilename = &zFilename[i+1]; i = -1; } } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ - zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); + zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); }else{ - zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); + zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } @ @ <p><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a> @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br /> if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -321,18 +321,18 @@ style_submenu_element("All", "All", "brlist?all"); } login_anonymous_available(); style_sidebox_begin("Nomenclature:", "33%"); @ <ol> - @ <li> An <div class="sideboxDescribed"><a href="brlist"> + @ <li> An <div class="sideboxDescribed">%z(href("brlist")) @ open branch</a></div> is a branch that has one or - @ more <a href="leaves">open leaves.</a> + @ more %z(href("leaves"))open leaves.</a> @ The presence of open leaves presumably means @ that the branch is still being extended with new check-ins.</li> - @ <li> A <div class="sideboxDescribed"><a href="brlist?closed"> + @ <li> A <div class="sideboxDescribed">%z(href("brlist?closed")) @ closed branch</a></div> is a branch with only - @ <div class="sideboxDescribed"><a href="leaves?closed"> + @ <div class="sideboxDescribed">%z(href("leaves?closed")) @ closed leaves</a></div>. @ Closed branches are fixed and do not change (unless they are first @ reopened)</li> @ </ol> style_sidebox_end(); @@ -356,14 +356,12 @@ } if( colorTest ){ const char *zColor = hash_color(zBr); @ <li><span style="background-color: %s(zColor)"> @ %h(zBr) → %s(zColor)</span></li> - }else if( g.perm.History ){ - @ <li><a href="%s(g.zTop)/timeline?r=%T(zBr)")>%h(zBr)</a></li> }else{ - @ <li><b>%h(zBr)</b></li> + @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li> } } if( cnt ){ @ </ul> } @@ -382,11 +380,11 @@ ** the timeline of a "brlist" page. Add some additional hyperlinks ** to the end of the line. */ static void brtimeline_extra(int rid){ Stmt q; - if( !g.perm.History ) return; + if( !g.perm.Hyperlink ) return; db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagxref.rid=%d" " AND tagxref.tagid=tag.tagid" " AND tagxref.tagtype>0" @@ -393,11 +391,11 @@ " AND tag.tagname GLOB 'sym-*'", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ <a href="%s(g.zTop)/timeline?r=%T(zTagName)">[timeline]</a> + @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> } db_finalize(&q); } /* Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -77,17 +77,19 @@ int i, j; char *zSep = ""; for(i=0; zPath[i]; i=j){ for(j=i; zPath[j] && zPath[j]!='/'; j++){} - if( zPath[j] && g.perm.History ){ + if( zPath[j] && g.perm.Hyperlink ){ if( zCI ){ - blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&name=%#T\">%#h</a>", - zSep, g.zTop, zCI, j, zPath, j-i, &zPath[i]); + char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath); + blob_appendf(pOut, "%s%z%#h</a>", + zSep, zLink, j-i, &zPath[i]); }else{ - blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", - zSep, g.zTop, j, zPath, j-i, &zPath[i]); + char *zLink = href("%R/dir?name=%#T", j, zPath); + blob_appendf(pOut, "%s%z%#h</a>", + zSep, zLink, j-i, &zPath[i]); } }else{ blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); } zSep = "/"; @@ -118,11 +120,11 @@ Blob dirname; Manifest *pM = 0; const char *zSubdirLink; login_check_credentials(); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } style_header("File List"); sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); @@ -155,39 +157,38 @@ } if( zCI ){ char zShort[20]; memcpy(zShort, zUuid, 10); zShort[10] = 0; - @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] + @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>] @ %s(blob_str(&dirname))</h2> - zSubdirLink = mprintf("%s/dir?ci=%S&name=%T", g.zTop, zUuid, zPrefix); + zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix); if( zD ){ - style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid); - style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); + style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid); + style_submenu_element("All", "All", "%R/dir?name=%t", zD); }else{ - style_submenu_element("All", "All", "%s/dir", g.zTop); + style_submenu_element("All", "All", "%R/dir"); } }else{ int hasTrunk; @ <h2>The union of all files from all check-ins @ %s(blob_str(&dirname))</h2> hasTrunk = db_exists( "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'", TAG_BRANCH); - zSubdirLink = mprintf("%s/dir?name=%T", g.zTop, zPrefix); + zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); if( zD ){ - style_submenu_element("Top", "Top", "%s/dir", g.zTop); - style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", - g.zTop, zD); + style_submenu_element("Top", "Top", "%R/dir"); + style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); if( hasTrunk ){ - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", - g.zTop,zD); + style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", + zD); } }else{ - style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zTop); + style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); if( hasTrunk ){ - style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zTop); + style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); } } } /* Compute the temporary table "localfiles" containing the names @@ -278,19 +279,19 @@ } i++; zFN = db_column_text(&q, 0); if( zFN[0]=='/' ){ zFN++; - @ <li><a href="%s(zSubdirLink)%T(zFN)">%h(zFN)/</a></li> + @ <li>%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> }else if( zCI ){ const char *zUuid = db_column_text(&q, 1); - @ <li><a href="%s(g.zTop)/artifact/%s(zUuid)">%h(zFN)</a></li> + @ <li>%z(href("%R/artifact/%s",zUuid))%h(zFN)</a></li> }else{ - @ <li><a href="%s(g.zTop)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN) + @ <li>%z(href("%R/finfo?name=%T%T",zPrefix,zFN))%h(zFN) @ </a></li> } } db_finalize(&q); manifest_destroy(pM); @ </ul></td></tr></table> style_footer(); } Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -1778,12 +1778,12 @@ const char *zUuid = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); if( webLabel ){ zLabel = mprintf( - "<a href='%s/info/%s' target='infowindow'>%.10s</a> %s %13.13s", - g.zTop, zUuid, zUuid, zDate, zUser + "<a href='%R/info/%s' target='infowindow'>%.10s</a> %s %13.13s", + zUuid, zUuid, zDate, zUser ); }else{ zLabel = mprintf("%.10s %s %13.13s", zUuid, zDate, zUser); } p->nVers++; @@ -1821,11 +1821,11 @@ if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){ fossil_redirect_home(); } style_header("File Annotation"); if( P("filevers") ) annFlags |= ANN_FILE_VERS; - annotate_file(&ann, fnid, mid, g.perm.History, iLimit, annFlags); + annotate_file(&ann, fnid, mid, g.perm.Hyperlink, iLimit, annFlags); if( P("log") ){ int i; @ <h2>Versions analyzed:</h2> @ <ol> for(i=0; i<ann.nVers; i++){ Index: src/diffcmd.c ================================================================== --- src/diffcmd.c +++ src/diffcmd.c @@ -541,11 +541,11 @@ } } /* ** WEBPAGE: vpatch -** URL vpatch?from=UUID&to=UUID +** URL vpatch?from=UUID&to=UUID */ void vpatch_page(void){ const char *zFrom = P("from"); const char *zTo = P("to"); login_check_credentials(); Index: src/event.c ================================================================== --- src/event.c +++ src/event.c @@ -31,20 +31,13 @@ /* ** Output a hyperlink to an event given its tagid. */ void hyperlink_to_event_tagid(int tagid){ char *zEventId; - char zShort[12]; - zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", tagid); - sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zEventId); - if( g.perm.History ){ - @ [<a href="%s(g.zTop)/event?name=%s(zEventId)">%s(zShort)</a>] - }else{ - @ [%s(zShort)] - } + @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>] free(zEventId); } /* ** WEBPAGE: event @@ -130,47 +123,47 @@ g.zTop, zEventId); } zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); style_submenu_element("Context", "Context", "%s/timeline?c=%T", g.zTop, zETime); - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( showDetail ){ - style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", + style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", g.zTop, zEventId, zUuid); if( nextRid ){ char *zNext; zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); style_submenu_element("Next", "Next", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zNext); free(zNext); } if( prevRid ){ char *zPrev; zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); style_submenu_element("Prev", "Prev", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zPrev); free(zPrev); } }else{ style_submenu_element("Detail", "Detail", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zUuid); } } - if( showDetail && g.perm.History ){ + if( showDetail && g.perm.Hyperlink ){ int i; const char *zClr = 0; Blob comment; zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); - @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at - @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>] + @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at + @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>] @ entered by user <b>%h(pEvent->zUser)</b> on - @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p> + @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p> @ <blockquote> for(i=0; i<pEvent->nTag; i++){ if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ zClr = pEvent->aTag[i].zValue; } Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -305,39 +305,34 @@ @ </td></tr> } memcpy(zTime, &zDate[11], 5); zTime[5] = 0; @ <tr><td class="timelineTime"> - @ <a href="%s(g.zTop)/timeline?c=%t(zDate)">%s(zTime)</a></td> + @ %z(href("%R/timeline?c=%t",zDate))%s(zTime)</a></td> @ <td class="timelineGraph"><div id="m%d(gidx)"></div></td> if( zBgClr && zBgClr[0] ){ @ <td class="timelineTableCell" style="background-color: %h(zBgClr);"> }else{ @ <td class="timelineTableCell"> } sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); if( zUuid ){ - if( g.perm.History ){ - @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%S(zUuid)]</a> - }else{ - @ [%S(zUuid)] - } - @ part of check-in + @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a> part of check-in }else{ @ <b>Deleted</b> by check-in } hyperlink_to_uuid(zShortCkin); @ %h(zCom) (user: hyperlink_to_user(zUser, zDate, ""); @ branch: %h(zBr)) - if( g.perm.History && zUuid ){ + if( g.perm.Hyperlink && zUuid ){ const char *z = zFilename; if( fpid ){ - @ <a href="%s(g.zTop)/fdiff?v1=%s(zPUuid)&v2=%s(zUuid)">[diff]</a> + @ %z(href("%R/fdiff?v1=%s&v2=%s",zPUuid,zUuid))[diff]</a> } - @ <a href="%s(g.zTop)/annotate?checkin=%S(zCkin)&filename=%h(z)"> + @ %z(href("%R/annotate?checkin=%S&filename=%h",zCkin,z)) @ [annotate]</a> } @ </td></tr> } db_finalize(&q); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -261,11 +261,11 @@ @ propagates to descendants } #if 0 if( zValue && fossil_strcmp(zTagname,"branch")==0 ){ @ - @ <a href="%s(g.zTop)/timeline?r=%T(zValue)">branch timeline</a> + @ %z(href("%R/timeline?r=%T",zValue))branch timeline</a> } #endif } if( zSrcUuid && zSrcUuid[0] ){ if( tagtype==0 ){ @@ -333,11 +333,11 @@ const char *zNew, /* blob.uuid after change. NULL for deletes */ const char *zOldName, /* Prior name. NULL if no name change. */ int diffFlags, /* Flags for text_diff(). Zero to omit diffs */ int mperm /* executable or symlink permission for zNew */ ){ - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ if( zNew==0 ){ @ <p>Deleted %h(zName)</p> }else if( zOld==0 ){ @ <p>Added %h(zName)</p> }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ @@ -354,35 +354,35 @@ @ </pre> } }else{ if( zOld && zNew ){ if( fossil_strcmp(zOld, zNew)!=0 ){ - @ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> - @ from <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a> - @ to <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)].</a> + @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a> + @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a> + @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)].</a> }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ @ <p>Name change from - @ from <a href="%s(g.zTop)/finfo?name=%T(zOldName)">%h(zOldName)</a> - @ to <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>. + @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a> + @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>. }else{ @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for - @ <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> + @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a> } }else if( zOld ){ - @ <p>Deleted <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> - @ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a> + @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a> + @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a> }else{ - @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> - @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a> + @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a> + @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a> } if( diffFlags ){ @ <pre style="white-space:pre;"> append_diff(zOld, zNew, diffFlags); @ </pre> }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @ - @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&v2=%S(zNew)">[diff]</a> + @ %z(href("%R/fdiff?v1=%S&v2=%S",zOld,zNew))[diff]</a> } @ </p> } } @@ -535,47 +535,47 @@ @ <tr><th>Received From:</th> @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr> } db_finalize(&q); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ const char *zProjName = db_get("project-name", "unnamed"); @ <tr><th>Timelines:</th><td> - @ <a href="%s(g.zTop)/timeline?f=%S(zUuid)">family</a> + @ %z(href("%R/timeline?f=%S",zUuid))family</a> if( zParent ){ - @ | <a href="%s(g.zTop)/timeline?p=%S(zUuid)">ancestors</a> + @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a> } if( !isLeaf ){ - @ | <a href="%s(g.zTop)/timeline?d=%S(zUuid)">descendants</a> + @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a> } if( zParent && !isLeaf ){ - @ | <a href="%s(g.zTop)/timeline?dp=%S(zUuid)">both</a> + @ | %z(href("%R/timeline?dp=%S",zUuid))both</a> } db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag " " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ | <a href="%s(g.zTop)/timeline?r=%T(zTagName)">%h(zTagName)</a> + @ | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName)</a> } db_finalize(&q); @ </td></tr> @ <tr><th>Other Links:</th> @ <td> - @ <a href="%s(g.zTop)/dir?ci=%S(zUuid)">files</a> + @ %z(href("%R/dir?ci=%S",zUuid))files</a> if( g.perm.Zip ){ - char *zUrl = mprintf("%s/tarball/%s-%S.tar.gz?uuid=%s", - g.zTop, zProjName, zUuid, zUuid); - @ | <a href="%s(zUrl)">Tarball</a> - @ | <a href="%s(g.zTop)/zip/%s(zProjName)-%S(zUuid).zip?uuid=%s(zUuid)"> + char *zUrl = mprintf("%R/tarball/%s-%S.tar.gz?uuid=%s", + zProjName, zUuid, zUuid); + @ | %z(href("%s",zUrl))Tarball</a> + @ | %z(href("%R/zip/%s-%S.zip?uuid=%s",zProjName,zUuid,zUuid)) @ ZIP archive</a> fossil_free(zUrl); } - @ | <a href="%s(g.zTop)/artifact/%S(zUuid)">manifest</a> + @ | %z(href("%R/artifact/%S",zUuid))manifest</a> if( g.perm.Write ){ - @ | <a href="%s(g.zTop)/ci_edit?r=%S(zUuid)">edit</a> + @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a> } @ </td> @ </tr> } @ </table> @@ -590,43 +590,43 @@ @ <div class="sectionmenu"> showDiff = g.zPath[0]!='c'; if( db_get_boolean("show-version-diffs", 0)==0 ){ showDiff = !showDiff; if( showDiff ){ - @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)"> + @ %z(xhref("class='button'","%R/vinfo/%T",zName))) @ hide diffs</a> if( sideBySide ){ - @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0"> + @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) @ unified diffs</a> }else{ - @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1"> + @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) @ side-by-side diffs</a> } }else{ - @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0"> + @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName)) @ show unified diffs</a> - @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1"> + @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName)) @ show side-by-side diffs</a> } }else{ if( showDiff ){ - @ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide diffs</a> + @ %z(xhref("class='button'","%R/ci/%T",zName))hide diffs</a> if( sideBySide ){ - @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=0"> + @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName)) @ unified diffs</a> }else{ - @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=1"> + @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName)) @ side-by-side diffs</a> } }else{ - @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=0"> + @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName)) @ show unified diffs</a> - @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=1"> + @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName)) @ show side-by-side diffs</a> } } - @ <a class="button" href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)"> + @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid)) @ patch</a></div> db_prepare(&q, "SELECT name," " mperm," " (SELECT uuid FROM blob WHERE rid=mlink.pid)," @@ -698,15 +698,15 @@ if( g.perm.Setup ){ @ <tr><th>Record ID:</th><td>%d(rid)</td></tr> } @ <tr><th>Original User:</th><td> hyperlink_to_user(zUser, zDate, "</td></tr>"); - if( g.perm.History ){ + if( g.perm.Hyperlink ){ @ <tr><th>Commands:</th> @ <td> - @ <a href="%s(g.zTop)/whistory?name=%t(zName)">history</a> - @ | <a href="%s(g.zTop)/artifact/%S(zUuid)">raw-text</a> + @ &z(href("%R/whistory?name=%t",zName))history</a> + @ | %z(href("%R/artifact/%S",zUuid))raw-text</a> @ </td> @ </tr> } @ </table></p> }else{ @@ -792,11 +792,11 @@ } /* ** WEBPAGE: vdiff -** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN +** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN ** ** Show all differences between two checkins. */ void vdiff_page(void){ int ridFrom, ridTo; @@ -936,34 +936,26 @@ }else if( mPerm==PERM_EXE ){ @ <li>Executable file }else{ @ <li>File } - if( g.perm.History ){ - @ <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a> - }else{ - @ %h(zName) - } + @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a> @ <ul> prevName = fossil_strdup(zName); } @ <li> hyperlink_to_date(zDate,""); @ - part of checkin hyperlink_to_uuid(zVers); if( zBr && zBr[0] ){ - if( g.perm.History ){ - @ on branch <a href="%s(g.zTop)/timeline?r=%T(zBr)">%h(zBr)</a> - }else{ - @ on branch %h(zBr) - } + @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a> } @ - %w(zCom) (user: hyperlink_to_user(zUser,zDate,""); @ ) - if( g.perm.History ){ - @ <a href="%s(g.zTop)/annotate?checkin=%S(zVers)&filename=%T(zName)"> + if( g.perm.Hyperlink ){ + @ %z(href("%R/annotate?checkin=%S&filename=%T",zVers,zName)) @ [annotate]</a> } cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zName, -1); @@ -989,16 +981,11 @@ if( cnt>0 ){ @ Also wiki page }else{ @ Wiki page } - if( g.perm.History ){ - @ [<a href="%s(g.zTop)/wiki?name=%t(zPagename)">%h(zPagename)</a>] - }else{ - @ [%h(zPagename)] - } - @ by + @ [%z(href("%R/wiki?name=%t",zPagename))%h(zPagename)</a>] by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate,"."); nWiki++; cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ @@ -1065,18 +1052,18 @@ @ Also attachment "%h(zFilename)" to }else{ @ Attachment "%h(zFilename)" to } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ - if( g.perm.History && g.perm.RdTkt ){ - @ ticket [<a href="%s(g.zTop)/tktview?name=%S(zTarget)">%S(zTarget)</a>] + if( g.perm.Hyperlink && g.perm.RdTkt ){ + @ ticket [%z(href("%R/tktview?name=%S",zTarget))%S(zTarget)</a>] }else{ @ ticket [%S(zTarget)] } }else{ - if( g.perm.History && g.perm.RdWiki ){ - @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>] + if( g.perm.Hyperlink && g.perm.RdWiki ){ + @ wiki page [%z(href("%R/wiki?name=%t",zTarget)))%h(zTarget)</a>] }else{ @ wiki page [%h(zTarget)] } } @ added by @@ -1091,12 +1078,12 @@ if( cnt==0 ){ @ Control artifact. if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_appendf(pDownloadName, "%.10s.txt", zUuid); } - }else if( linkToView && g.perm.History ){ - @ <a href="%s(g.zTop)/artifact/%S(zUuid)">[view]</a> + }else if( linkToView && g.perm.Hyperlink ){ + @ %z(href("%R/artifact/%S",zUuid))[view]</a> } } /* @@ -1160,18 +1147,17 @@ g.zTop, P("v1"), P("v2")); } if( P("smhdr")!=0 ){ @ <h2>Differences From Artifact - @ <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a> To - @ <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>.</h2> + @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To + @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2> }else{ @ <h2>Differences From - @ Artifact <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a>:</h2> + @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2> object_description(v1, 0, 0); - @ <h2>To Artifact - @ <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>:</h2> + @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2> object_description(v2, 0, 0); } @ <hr /> @ <div class="%s(zStyle)"> @ %s(blob_str(&diff)) @@ -1272,11 +1258,11 @@ if( !g.perm.Read ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1419,11 +1405,11 @@ if( !g.perm.Read ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1478,11 +1464,11 @@ @ <pre> @ %h(z) @ </pre> } }else if( strncmp(zMime, "image/", 6)==0 ){ - @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img> + @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img> }else{ @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i> } @ </blockquote> } @@ -1507,11 +1493,11 @@ rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Admin ){ if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1522,18 +1508,17 @@ } style_header("Ticket Change Details"); zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); memcpy(zTktName, pTktChng->zTicketUuid, 10); zTktName[10] = 0; - if( g.perm.History ){ + if( g.perm.Hyperlink ){ @ <h2>Changes to ticket - @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2> + @ %z(href("%R/tktview/%s",pTktChng->zTicketUuid)))%s(zTktName)</a></h2> @ @ <p>By %h(pTktChng->zUser) on %s(zDate). See also: - @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and - @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket - @ history</a></p> + @ %z(href("%R/artifact/%T",zUuid))artifact content</a>, and + @ %z(href("%R/tkthistory/%s",pTktChng->zTicketUuid))ticket history</a></p> }else{ @ <h2>Changes to ticket %s(zTktName)</h2> @ @ <p>By %h(pTktChng->zUser) on %s(zDate). @ </p> @@ -1965,11 +1950,11 @@ @ </blockquote> @ <hr /> blob_reset(&suffix); } @ <p>Make changes to attributes of check-in - @ [<a href="ci?name=%s(zUuid)">%s(zUuid)</a>]:</p> + @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p> @ <form action="%s(g.zTop)/ci_edit" method="post"><div> login_insert_csrf_secret(); @ <input type="hidden" name="r" value="%S(zUuid)" /> @ <table border="0" cellspacing="10"> Index: src/json_dir.c ================================================================== --- src/json_dir.c +++ src/json_dir.c @@ -66,11 +66,11 @@ char * zUuid = NULL; char const * zCI = NULL; Manifest * pM = NULL; Stmt q = empty_Stmt; int rid = 0; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'h' permissions."); return NULL; } zCI = json_find_option_cstr("checkin",NULL,"ci" ); Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ src/json_timeline.c @@ -53,11 +53,11 @@ #if 0 /* The original timeline code does not require 'h' access, but it arguably should. For JSON mode i think one could argue that History permissions are required. */ - if(! g.perm.History && !g.perm.Read ){ + if(! g.perm.Hyperlink && !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access."); return NULL; } #endif return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); @@ -426,11 +426,11 @@ int check = 0; char showFiles = -1/*magic number*/; Stmt q = empty_Stmt; char warnRowToJsonFailed = 0; Blob sql = empty_blob; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ /* Reminder to self: HTML impl requires 'o' (Read) rights. */ json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'h' access." ); return NULL; Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ src/json_wiki.c @@ -492,11 +492,11 @@ Manifest * pW1 = NULL, *pW2 = NULL; Blob w1 = empty_blob, w2 = empty_blob, d = empty_blob; char const * zErrTag = NULL; int diffFlags; char * zUuid = NULL; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'h' permissions."); return NULL; } Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -562,11 +562,11 @@ redirect_to_g(); } } style_header("Login/Logout"); @ %s(zErrMsg) - if( zGoto ){ + if( zGoto && P("anon")==0 ){ @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p> } @ <form action="login" method="post"> if( zGoto ){ @ <input type="hidden" name="g" value="%h(zGoto)" /> @@ -911,13 +911,15 @@ } /* Set the capabilities */ login_replace_capabilities(zCap, 0); login_set_anon_nobody_capabilities(); - if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1) + if( zCap[0] && !g.perm.Hyperlink + && db_get_boolean("auto-enable-hyperlinks",1) && isHuman(P("HTTP_USER_AGENT")) ){ - g.perm.History = 1; + g.perm.Hyperlink = 1; + g.javascriptHyperlink = 1; } /* If the public-pages glob pattern is defined and REQUEST_URI matches ** one of the globs in public-pages, then also add in all default-perms ** permissions. @@ -973,21 +975,21 @@ } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ case 's': g.perm.Setup = 1; /* Fall thru into Admin */ case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip = - g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = - g.perm.ApndWiki = g.perm.History = g.perm.Clone = - g.perm.NewTkt = g.perm.Password = g.perm.RdAddr = - g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1; - /* Fall thru into Read/Write */ + g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = + g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone = + g.perm.NewTkt = g.perm.Password = g.perm.RdAddr = + g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1; + /* Fall thru into Read/Write */ case 'i': g.perm.Read = g.perm.Write = 1; break; case 'o': g.perm.Read = 1; break; case 'z': g.perm.Zip = 1; break; case 'd': g.perm.Delete = 1; break; - case 'h': g.perm.History = 1; break; + case 'h': g.perm.Hyperlink = 1; break; case 'g': g.perm.Clone = 1; break; case 'p': g.perm.Password = 1; break; case 'j': g.perm.RdWiki = 1; break; case 'k': g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1; break; @@ -1053,15 +1055,14 @@ case 'c': rc = g.perm.ApndTkt; break; case 'd': rc = g.perm.Delete; break; case 'e': rc = g.perm.RdAddr; break; case 'f': rc = g.perm.NewWiki; break; case 'g': rc = g.perm.Clone; break; - case 'h': rc = g.perm.History; break; + case 'h': rc = g.perm.Hyperlink; break; case 'i': rc = g.perm.Write; break; case 'j': rc = g.perm.RdWiki; break; case 'k': rc = g.perm.WrWiki; break; - /* case 'l': */ case 'm': rc = g.perm.ApndWiki; break; case 'n': rc = g.perm.NewTkt; break; case 'o': rc = g.perm.Read; break; case 'p': rc = g.perm.Password; break; /* case 'q': */ @@ -1129,23 +1130,23 @@ assert(0); } } /* -** Call this routine if the user lacks okHistory permission. If -** the anonymous user has okHistory permission, then paint a mesage +** Call this routine if the user lacks g.perm.Hyperlink permission. If +** the anonymous user has Hyperlink permission, then paint a mesage ** to inform the user that much more information is available by ** logging in as anonymous. */ void login_anonymous_available(void){ - if( !g.perm.History && + if( !g.perm.Hyperlink && db_exists("SELECT 1 FROM user" " WHERE login='anonymous'" " AND cap LIKE '%%h%%'") ){ const char *zUrl = PD("REQUEST_URI", "index"); @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br /> - @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> + @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a> @ to enable hyperlinks.</p> } } /* Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -60,11 +60,11 @@ char Delete; /* d: delete wiki or tickets */ char Password; /* p: change password */ char Query; /* q: create new reports */ char Write; /* i: xfer inbound. checkin */ char Read; /* o: xfer outbound. checkout */ - char History; /* h: access historical information. */ + char Hyperlink; /* h: enable the display of hyperlinks */ char Clone; /* g: clone */ char RdWiki; /* j: view wiki via web */ char NewWiki; /* f: create new wiki via web */ char ApndWiki; /* m: append to wiki via web */ char WrWiki; /* k: edit wiki via web */ @@ -135,11 +135,12 @@ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int clockSkewSeen; /* True if clocks on client and server out of sync */ - int isHTTP; /* True if running in server/CGI modes, else assume CLI. */ + char isHTTP; /* True if erver/CGI modes, else assume CLI. */ + char javascriptHyperlink; /* If true, set href= using script, not HTML */ int urlIsFile; /* True if a "file:" url */ int urlIsHttps; /* True if a "https:" url */ int urlIsSsh; /* True if an "ssh:" url */ char *urlName; /* Hostname for http: or filename for file: */ Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -47,10 +47,11 @@ #define etFOSSILIZE 19 /* The fossil header encoding format. */ #define etPATH 20 /* Path type */ #define etWIKISTR 21 /* Wiki text rendered from a char*: %w */ #define etWIKIBLOB 22 /* Wiki text rendered from a Blob*: %W */ #define etSTRINGID 23 /* String with length limit for a UUID prefix: %S */ +#define etROOT 24 /* String value of g.zTop: % */ /* ** An "etByte" is an 8-bit unsigned value. */ @@ -93,10 +94,11 @@ { 'b', 0, 2, etBLOB, 0, 0 }, { 'B', 0, 2, etBLOBSQL, 0, 0 }, { 'w', 0, 2, etWIKISTR, 0, 0 }, { 'W', 0, 2, etWIKIBLOB, 0, 0 }, { 'h', 0, 4, etHTMLIZE, 0, 0 }, + { 'R', 0, 0, etROOT, 0, 0 }, { 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */ { 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */ { 'F', 0, 4, etFOSSILIZE, 0, 0 }, { 'S', 0, 4, etSTRINGID, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, @@ -570,10 +572,15 @@ bufpt[i]=e[i]; } } bufpt[length]='\0'; break; + } + case etROOT: { + bufpt = g.zTop; + length = (int)strlen(bufpt); + break; } case etSTRINGID: { precision = 16; /* Fall through */ } Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -56,26 +56,29 @@ cnt++; blob_appendf(&ril, "<li>"); if( zTitle[0] == '_' ){ blob_appendf(&ril, "%s", zTitle); } else { - blob_appendf(&ril, "<a href=\"rptview?rn=%d\" rel=\"nofollow\">%h</a>", rn, zTitle); + blob_appendf(&ril, "%z%h</a>", href("%R/rptview?rn=%d", rn), zTitle); } blob_appendf(&ril, " "); if( g.perm.Write && zOwner && zOwner[0] ){ blob_appendf(&ril, "(by <i>%h</i></i>) ", zOwner); } if( g.perm.TktFmt ){ - blob_appendf(&ril, "[<a href=\"rptedit?rn=%d&copy=1\" rel=\"nofollow\">copy</a>] ", rn); + blob_appendf(&ril, "[%zcopy</a>] ", + href("%R/rptedit?rn=%d©=1", rn)); } if( g.perm.Admin || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0) ){ - blob_appendf(&ril, "[<a href=\"rptedit?rn=%d\" rel=\"nofollow\">edit</a>] ", rn); + blob_appendf(&ril, "[%zedit</a>]", + href("%R/rptedit?rn=%d", rn)); } if( g.perm.TktFmt ){ - blob_appendf(&ril, "[<a href=\"rptsql?rn=%d\" rel=\"nofollow\">sql</a>] ", rn); + blob_appendf(&ril, "[%zsql</a>]", + href("%R/rptsql?rn=%d", rn)); } blob_appendf(&ril, "</li>\n"); } Th_Store("report_items", blob_str(&ril)); @@ -416,11 +419,11 @@ } } if( zOwner==0 ) zOwner = g.zLogin; style_submenu_element("Cancel", "Cancel", "reportlist"); if( rn>0 ){ - style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); + style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); } style_header(rn>0 ? "Edit Report Format":"Create New Report Format"); if( zErr ){ @ <blockquote class="reportError">%h(zErr)</blockquote> } @@ -720,11 +723,11 @@ if( i==pState->iBg ) continue; zData = azArg[i]; if( zData==0 ) zData = ""; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ if( zTid && g.perm.Write ){ - @ <td valign="top"><a href="tktedit/%h(zTid)">edit</a></td> + @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td> zTid = 0; } if( zData[0] ){ Blob content; @ </tr><tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)> @@ -732,25 +735,21 @@ wiki_convert(&content, 0, 0); blob_reset(&content); } }else if( azName[i][0]=='#' ){ zTid = zData; - if( g.perm.History ){ - @ <td valign="top"><a href="tktview?name=%h(zData)">%h(zData)</a></td> - }else{ - @ <td valign="top">%h(zData)</td> - } + @ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td> }else if( zData[0]==0 ){ @ <td valign="top"> </td> }else{ @ <td valign="top"> @ %h(zData) @ </td> } } if( zTid && g.perm.Write ){ - @ <td valign="top"><a href="tktedit/%h(zTid)">edit</a></td> + @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td> } @ </tr> return 0; } @@ -949,11 +948,11 @@ if( !tabs ){ struct GenerateHTML sState; db_multi_exec("PRAGMA empty_result_callbacks=ON"); style_submenu_element("Raw", "Raw", - "rptview?tablist=1&%h", PD("QUERY_STRING","")); + "rptview?tablist=1&%h", PD("QUERY_STRING","")); if( g.perm.Admin || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn); } if( g.perm.TktFmt ){ Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -524,11 +524,11 @@ @ <input type="checkbox" name="ad"%s(oad) />%s(B('d'))Delete<br /> @ <input type="checkbox" name="ae"%s(oae) />%s(B('e'))Email<br /> @ <input type="checkbox" name="ap"%s(oap) />%s(B('p'))Password<br /> @ <input type="checkbox" name="ai"%s(oai) />%s(B('i'))Check-In<br /> @ <input type="checkbox" name="ao"%s(oao) />%s(B('o'))Check-Out<br /> - @ <input type="checkbox" name="ah"%s(oah) />%s(B('h'))History<br /> + @ <input type="checkbox" name="ah"%s(oah) />%s(B('h'))Hyperlinks<br /> @ <input type="checkbox" name="au"%s(oau) />%s(B('u'))Reader<br /> @ <input type="checkbox" name="av"%s(oav) />%s(B('v'))Developer<br /> @ <input type="checkbox" name="ag"%s(oag) />%s(B('g'))Clone<br /> @ <input type="checkbox" name="aj"%s(oaj) />%s(B('j'))Read Wiki<br /> @ <input type="checkbox" name="af"%s(oaf) />%s(B('f'))New Wiki<br /> @@ -625,24 +625,24 @@ @ is first posted. The <span class="usertype">Setup</span> user can @ delete anything at any time. @ </p></li> @ @ <li><p> - @ The <span class="capability">History</span> privilege allows a user + @ The <span class="capability">Hyperlinks</span> privilege allows a user @ to see most hyperlinks. This is recommended ON for most logged-in users @ but OFF for user "nobody" to avoid problems with spiders trying to walk - @ every historical version of every baseline and file. + @ every diff and annotation of every historical check-in and file. @ </p></li> @ @ <li><p> @ The <span class="capability">Zip</span> privilege allows a user to @ see the "download as ZIP" @ hyperlink and permits access to the <tt>/zip</tt> page. This allows @ users to download ZIP archives without granting other rights like @ <span class="capability">Read</span> or - @ <span class="capability">History</span>. This privilege is recommended for - @ user <span class="usertype">nobody</span> so that automatic package + @ <span class="capability">Hyperlink</span>. The "z" privilege is recommended + @ for user <span class="usertype">nobody</span> so that automatic package @ downloaders can obtain the sources without going through the login @ procedure. @ </p></li> @ @ <li><p> @@ -704,12 +704,12 @@ @ To disable universal access to the repository, make sure no user named @ <span class="usertype">nobody</span> exists or that the @ <span class="usertype">nobody</span> user has no capabilities @ enabled. The password for <span class="usertype">nobody</span> is ignore. @ To avoid problems with spiders overloading the server, it is recommended - @ that the <span class="capability">h</span> (History) capability be turned - @ off for the <span class="usertype">nobody</span> user. + @ that the <span class="capability">h</span> (Hyperlinks) capability be + @ turned off for the <span class="usertype">nobody</span> user. @ </p></li> @ @ <li><p> @ Login is required for user <span class="usertype">anonymous</span> but the @ password is displayed on the login screen beside the password entry box @@ -891,19 +891,27 @@ @ than this, then the client will issue multiple HTTP requests. @ Values below 1 million are not recommended. 5 million is a @ reasonable number.</p> @ <hr /> - onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent", - "auto-enable-hyperlinks", "autohyperlink", 1); + onoff_attribute( + "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript", + "auto-enable-hyperlinks", "autohyperlink", 1); @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users - @ including user "nobody", as long as the User-Agent string in the HTTP header - @ indicates that the request is coming from an actual human being and not a - @ a robot or script. Note: Bots can specify whatever User-Agent string they - @ that want. So a bot that wants to impersonate a human can easily do so. - @ Hence, this technique does not necessarily exclude malicious bots. - @ </p> + @ including user "nobody", as long as (1) the User-Agent string in the + @ HTTP header indicates that the request is coming from an actual human + @ being and not a a robot or spider and (2) the user agent is able to + @ run Javascript in order to set the href= attribute of hyperlinks. Bots + @ and spiders can specify whatever User-Agent string they that want and + @ they can run javascript just like browsers. But most bots don't go to + @ that much trouble so this is normally an effective defense.</p> + @ + @ <p>You do not normally want a bot to walk your entire repository because + @ if it does, your server will end up computing diffs and annotations for + @ every historical version of every file and creating ZIPs and tarballs of + @ every historical check-in, which can use a lot of CPU and bandwidth + @ even for relatively small projects.</p> @ <hr /> entry_attribute("Public pages", 30, "public-pages", "pubpage", ""); @ <p>A comma-separated list of glob patterns for pages that are accessible Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -45,10 +45,94 @@ /* ** remember, if a sidebox was used */ static int sideboxUsed = 0; + +/* +** List of hyperlinks that need to be resolved by javascript in +** the footer. +*/ +char **aHref = 0; +int nHref = 0; +int nHrefAlloc = 0; + +/* +** Generate and return a anchor tag like this: +** +** <a href="URL"> +** or <a id="ID"> +** +** The form of the anchor tag is determined by the g.javascriptHyperlink +** variable. The href="URL" form is used if g.javascriptHyperlink is false. +** If g.javascriptHyperlink is true then the +** id="ID" form is used and javascript is generated in the footer to cause +** href values to be inserted after the page has loaded. If +** g.perm.History is false, then the <a id="ID"> form is still +** generated but the javascript is not generated so the links never +** activate. +** +** Filling in the href="URL" using javascript is a defense against bots. +** +** The name of this routine is deliberately kept short so that can be +** easily used within @-lines. Example: +** +** @ %z(href("%R/artifact/%s",zUuid))%h(zFN)</a> +** +** Note %z format. The string returned by this function is always +** obtained from fossil_malloc() so rendering it with %z will reclaim +** that memory space. +** +** There are two versions of this routine: href() does a plain hyperlink +** and xhref() adds extra attribute text. +*/ +char *xhref(const char *zExtra, const char *zFormat, ...){ + char *zUrl; + va_list ap; + va_start(ap, zFormat); + zUrl = vmprintf(zFormat, ap); + va_end(ap); + if( g.perm.Hyperlink && !g.javascriptHyperlink ){ + return mprintf("<a %s href=\"%z\">", zExtra, zUrl); + } + if( nHref>=nHrefAlloc ){ + nHrefAlloc = nHrefAlloc*2 + 10; + aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0])); + } + aHref[nHref++] = zUrl; + return mprintf("<a %s id=%d>", zExtra, nHref); +} +char *href(const char *zFormat, ...){ + char *zUrl; + va_list ap; + va_start(ap, zFormat); + zUrl = vmprintf(zFormat, ap); + va_end(ap); + if( g.perm.Hyperlink && !g.javascriptHyperlink ){ + return mprintf("<a href=\"%z\">", zUrl); + } + if( nHref>=nHrefAlloc ){ + nHrefAlloc = nHrefAlloc*2 + 10; + aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0])); + } + aHref[nHref++] = zUrl; + return mprintf("<a id=%d>", nHref); +} + +/* +** Generate javascript that will set the href= attribute on all anchors. +*/ +void style_resolve_href(void){ + int i; + if( !g.perm.Hyperlink || !g.javascriptHyperlink || nHref==0 ) return; + @ <script> + for(i=0; i<nHref; i++){ + @ document.getElementById(%d(i+1)).href="%s(aHref[i])"; + } + @ </script> +} + /* ** Add a new element to the submenu */ void style_submenu_element( const char *zLabel, @@ -164,10 +248,13 @@ if( g.thTrace ){ cgi_append_content("<span class=\"thTrace\"><hr />\n", -1); cgi_append_content(blob_str(&g.thLog), blob_size(&g.thLog)); cgi_append_content("</span>\n", -1); } + + /* Set the href= field on hyperlinks */ + style_resolve_href(); } /* ** Begin a side-box on the right-hand side of a page. The title and ** the width of the box are given as arguments. The width is usually Index: src/tag.c ================================================================== --- src/tag.c +++ src/tag.c @@ -548,12 +548,12 @@ " ORDER BY tagname" ); @ <ul> while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); - if( g.perm.History ){ - @ <li><a class="tagLink" href="%s(g.zTop)/timeline?t=%T(zName)"> + if( g.perm.Hyperlink ){ + @ <li>%z(xhref("class='taglink'","%R/timeline?t=%T",zName)) @ %h(zName)</a></li> }else{ @ <li><span class="tagDsp">%h(zName)</span></li> } } Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -47,37 +47,37 @@ ** Generate a hyperlink to a version. */ void hyperlink_to_uuid(const char *zUuid){ char z[UUID_SIZE+1]; shorten_uuid(z, zUuid); - if( g.perm.History ){ - @ <a class="timelineHistLink" href="%s(g.zTop)/info/%s(z)">[%s(z)]</a> + if( g.perm.Hyperlink ){ + @ %z(xhref("class='timelineHistLink'","%R/info/%s",z))[%s(z)]</a> }else{ @ <span class="timelineHistDsp">[%s(z)]</span> } } /* ** Generate a hyperlink to a diff between two versions. */ void hyperlink_to_diff(const char *zV1, const char *zV2){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zV2==0 ){ - @ <a href="%s(g.zTop)/diff?v2=%s(zV1)">[diff]</a> + @ %z(href("%R/diff?v2=%s",zV1))[diff]</a> }else{ - @ <a href="%s(g.zTop)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a> + @ %z(href("%R/diff?v1=%s&v2=%s",zV1,zV2))[diff]</a> } } } /* ** Generate a hyperlink to a date & time. */ void hyperlink_to_date(const char *zDate, const char *zSuffix){ if( zSuffix==0 ) zSuffix = ""; - if( g.perm.History ){ - @ <a href="%s(g.zTop)/timeline?c=%T(zDate)">%s(zDate)</a>%s(zSuffix) + if( g.perm.Hyperlink ){ + @ %z(href("%R/timeline?c=%T",zDate))%s(zDate)</a>%s(zSuffix) }else{ @ %s(zDate)%s(zSuffix) } } @@ -86,15 +86,15 @@ ** events by that user. If the date+time is specified, then the timeline ** is centered on that date+time. */ void hyperlink_to_user(const char *zU, const char *zD, const char *zSuf){ if( zSuf==0 ) zSuf = ""; - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zD && zD[0] ){ - @ <a href="%s(g.zTop)/timeline?c=%T(zD)&u=%T(zU)">%h(zU)</a>%s(zSuf) + @ %z(href("%R/timeline?c=%T&u=%T",zD,zU))%h(zU)</a>%s(zSuf) }else{ - @ <a href="%s(g.zTop)/timeline?u=%T(zU)">%h(zU)</a>%s(zSuf) + @ %z(href("%R/timeline?u=%T",zU))%h(zU)</a>%s(zSuf) } }else{ @ %s(zU) } } @@ -353,39 +353,37 @@ /* Generate the "user: USERNAME" at the end of the comment, together ** with a hyperlink to another timeline for that user. */ if( zTagList && zTagList[0]==0 ) zTagList = 0; - if( g.perm.History && fossil_strcmp(zUser, zThisUser)!=0 ){ - char *zLink = mprintf("%s/timeline?u=%h&c=%t&nd", - g.zTop, zUser, zDate); - @ (user: <a href="%s(zLink)">%h(zUser)</a>%s(zTagList?",":"\051") - fossil_free(zLink); + if( g.perm.Hyperlink && fossil_strcmp(zUser, zThisUser)!=0 ){ + char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zUser, zDate); + @ (user: %z(href("%z",zLink))%h(zUser)</a>%s(zTagList?",":"\051") }else{ @ (user: %h(zUser)%s(zTagList?",":"\051") } /* Generate a "detail" link for tags. */ - if( zType[0]=='g' && g.perm.History ){ - @ [<a href="%s(g.zTop)/info/%S(zUuid)">details</a>] + if( zType[0]=='g' && g.perm.Hyperlink ){ + @ [%z(href("%R/info/%S",zUuid))details</a>] } /* Generate the "tags: TAGLIST" at the end of the comment, together ** with hyperlinks to the tag list. */ if( zTagList ){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ int i; const char *z = zTagList; Blob links; blob_zero(&links); while( z && z[0] ){ for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ blob_appendf(&links, - "<a href=\"%s/timeline?r=%#t&nd&c=%s\">%#h</a>%.2s", - g.zTop, i, z, zDate, i, z, &z[i] + "%z%#h</a>%.2s", + href("%R/timeline?r=%#t&nd&c=%s",i,z,zDate), i,z, &z[i] ); }else{ blob_appendf(&links, "%#h", i+2, z); } if( z[i]==0 ) break; @@ -403,11 +401,11 @@ if( xExtra ){ xExtra(rid); } /* Generate the file-change list if requested */ - if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.History ){ + if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.Hyperlink ){ int inUl = 0; if( !fchngQueryInit ){ db_prepare(&fchngQuery, "SELECT (pid==0) AS isnew," " (fid==0) AS isdel," @@ -433,26 +431,26 @@ @ <ul class="filelist"> inUl = 1; } if( isNew ){ @ <li> %h(zFilename) (new file) - @ <a href="%s(g.zTop)/artifact/%S(zNew)" - @ target="diffwindow">[view]</a></li> + @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew)) + @ [view]</a></li> }else if( isDel ){ @ <li> %h(zFilename) (deleted)</li> }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){ @ <li> %h(zOldName) → %h(zFilename) - @ <a href="%s(g.zTop)/artifact/%S(zNew)" - @ target="diffwindow">[view]</a></li> + @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew)) + @ [view]</a></li> }else{ if( zOldName!=0 ){ @ <li> %h(zOldName) → %h(zFilename) }else{ @ <li> %h(zFilename) } - @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&v2=%S(zNew)" - @ target="diffwindow">[diff]</a></li> + @ %z(xhref("target='diffwindow'","%R/fdiff?v1=%S&v2=%S",zOld,zNew)) + @ [diff]</a></li> } } db_reset(&fchngQuery); if( inUl ){ @ </ul> @@ -974,21 +972,13 @@ p = p->u.pTo; } blob_append(&sql, ")", -1); path_reset(); blob_append(&desc, "All nodes on the path from ", -1); - if( g.perm.History ){ - blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>", g.zTop,zFrom,zFrom); - }else{ - blob_appendf(&desc, "[%h]", zFrom); - } + blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom); blob_append(&desc, " and ", -1); - if( g.perm.History ){ - blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>.", g.zTop, zTo, zTo); - }else{ - blob_appendf(&desc, "[%h].", zTo); - } + blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo); tmFlags |= TIMELINE_DISJOINT; db_multi_exec("%s", blob_str(&sql)); }else if( (p_rid || d_rid) && g.perm.Read ){ /* If p= or d= is present, ignore all other parameters other than n= */ char *zUuid; @@ -1021,16 +1011,12 @@ blob_appendf(&desc, "%d ancestors", np); db_multi_exec("%s", blob_str(&sql)); } if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); } - if( g.perm.History ){ - blob_appendf(&desc, " of <a href='%s/info/%s'>[%.10s]</a>", - g.zTop, zUuid, zUuid); - }else{ - blob_appendf(&desc, " of check-in [%.10s]", zUuid); - } + blob_appendf(&desc, " of %z[%.10s]</a>", + href("%R/info/%s", zUuid), zUuid); }else if( f_rid && g.perm.Read ){ /* If f= is present, ignore all other parameters other than n= */ char *zUuid; db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" @@ -1042,16 +1028,11 @@ blob_appendf(&sql, " AND event.objid IN ok"); db_multi_exec("%s", blob_str(&sql)); if( useDividers ) timeline_add_dividers(0, f_rid); blob_appendf(&desc, "Parents and children of check-in "); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); - if( g.perm.History ){ - blob_appendf(&desc, "<a href='%s/info/%s'>[%.10s]</a>", - g.zTop, zUuid, zUuid); - }else{ - blob_appendf(&desc, "[%.10s]", zUuid); - } + blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid); }else{ /* Otherwise, a timeline based on a span of time */ int n; const char *zEType = "timeline item"; char *zDate; @@ -1219,11 +1200,11 @@ blob_appendf(&desc, " occurring around %h.<br />", zCirca); } if( zSearch ){ blob_appendf(&desc, " matching \"%h\"", zSearch); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zAfter || n==nEntry ){ zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); timeline_submenu(&url, "Older", "b", zDate, "a"); free(zDate); } @@ -1623,11 +1604,11 @@ */ void test_timewarp_page(void){ Stmt q; login_check_credentials(); - if( !g.perm.Read || !g.perm.History ){ login_needed(); return; } + if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; } style_header("Instances of timewarp"); @ <ul> db_prepare(&q, "SELECT blob.uuid " " FROM plink p, plink c, blob" @@ -1635,10 +1616,10 @@ " AND blob.rid=c.cid" ); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); @ <li> - @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&d=%S(zUuid)">%S(zUuid)</a> + @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&d=%S(zUuid)">%S(zUuid)</a> } db_finalize(&q); style_footer(); } Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -305,11 +305,11 @@ if( !g.perm.RdTkt ){ login_needed(); return; } if( g.perm.WrTkt || g.perm.ApndTkt ){ style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", g.zTop, PD("name","")); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ style_submenu_element("History", "History Of This Ticket", "%s/tkthistory/%T", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline Of This Ticket", "%s/tkttimeline/%T", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins Of This Ticket", @@ -319,11 +319,11 @@ style_submenu_element("New Ticket", "Create a new ticket", "%s/tktnew", g.zTop); } if( g.perm.ApndTkt && g.perm.Attach ){ style_submenu_element("Attach", "Add An Attachment", - "%s/attachadd?tkt=%T&from=%s/tktview/%t", + "%s/attachadd?tkt=%T&from=%s/tktview/%t", g.zTop, zUuid, g.zTop, zUuid); } style_header("View Ticket"); if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); ticket_init(); @@ -353,20 +353,20 @@ @ <hr /><h2>Attachments:</h2> @ <ul> } cnt++; @ <li> - if( g.perm.Read && g.perm.History ){ - @ <a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&file=%t(zFile)"> + if( g.perm.Read && g.perm.Hyperlink ){ + @ %z(href("%R/attachview?tkt=%s&file=%t",zFullName,zFile)) @ %h(zFile)</a> }else{ @ %h(zFile) } @ added by %h(zUser) on hyperlink_to_date(zDate, "."); if( g.perm.WrTkt && g.perm.Attach ){ - @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&file=%t(zFile)&from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>] + @ [%z(href("%R/attachdelete?tkt=%s&file=%t&from=%R/tktview%%3fname=%s",zFullName,zFile,zFullName))delete</a>] } @ </li> } if( cnt ){ @ </ul> @@ -647,11 +647,11 @@ return 0; } /* ** WEBPAGE: tkttimeline -** URL: /tkttimeline?name=TICKETUUID&y=TYPE +** URL: /tkttimeline?name=TICKETUUID&y=TYPE ** ** Show the change history for a single ticket in timeline format. */ void tkttimeline_page(void){ Stmt q; @@ -662,16 +662,16 @@ int tagid; char zGlobPattern[50]; const char *zType; login_check_credentials(); - if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zType = PD("y","a"); if( zType[0]!='c' ){ style_submenu_element("Check-ins", "Check-ins", - "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); + "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); }else{ style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%T", g.zTop, zUuid); } style_submenu_element("History", "History", @@ -736,17 +736,17 @@ char *zTitle; const char *zUuid; int tagid; login_check_credentials(); - if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zTitle = mprintf("History Of Ticket %h", zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins", - "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); + "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid); style_header(zTitle); free(zTitle); @@ -786,20 +786,20 @@ @ <p>Delete attachment "%h(zFile)" }else{ @ @ <p>Add attachment "%h(zFile)" } - @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] + @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] @ (rid %d(rid)) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, ".</p>"); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET); if( pTicket ){ @ @ <p>Ticket change - @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] + @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>] @ (rid %d(rid)) by hyperlink_to_user(pTicket->zUser,zDate," on"); hyperlink_to_date(zDate, ":"); @ </p> ticket_output_change_artifact(pTicket); Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -104,11 +104,11 @@ return; } style_header("Home"); @ <p>This is a stub home-page for the project. @ To fill in this page, first go to - @ <a href="%s(g.zTop)/setup_config">setup/config</a> + @ %z(href("%R/setup_config"))setup/config</a> @ and establish a "Project Name". Then create a @ wiki page with that name. The content of that wiki page @ will be displayed in place of this message.</p> style_footer(); } @@ -142,27 +142,25 @@ if( zPageName==0 ){ style_header("Wiki"); @ <ul> { char *zHomePageName = db_get("project-name",0); if( zHomePageName ){ - @ <li> <a href="%s(g.zTop)/wiki?name=%t(zHomePageName)"> + @ <li> %z(href("%R/wiki?name=%t",zHomePageName)) @ %h(zHomePageName)</a> wiki home page.</li> } } - @ <li> <a href="%s(g.zTop)/timeline?y=w">Recent changes</a> to wiki - @ pages. </li> - @ <li> <a href="%s(g.zTop)/wiki_rules">Formatting rules</a> for - @ wiki.</li> - @ <li> Use the <a href="%s(g.zTop)/wiki?name=Sandbox">Sandbox</a> + @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> + @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li> + @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a> @ to experiment.</li> if( g.perm.NewWiki ){ - @ <li> Create a <a href="%s(g.zTop)/wikinew">new wiki page</a>.</li> + @ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> if( g.perm.Write ){ - @ <li> Create a <a href="%s(g.zTop)/eventedit">new event</a>.</li> + @ <li> Create a %z(href("%R/eventedit"))new event</a>.</li> } } - @ <li> <a href="%s(g.zTop)/wcontent">List of All Wiki Pages</a> + @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> @ available on this server.</li> @ <li> <form method="get" action="%s(g.zTop)/wfind"><div> @ Search wiki titles: <input type="text" name="title"/> @ <input type="submit" /></div></form> @ </li> @@ -192,18 +190,18 @@ style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", g.zTop, zPageName); } if( rid && g.perm.ApndWiki && g.perm.Attach ){ style_submenu_element("Attach", "Add An Attachment", - "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", + "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", g.zTop, zPageName, g.zTop, zPageName); } if( rid && g.perm.ApndWiki ){ style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", g.zTop, zPageName); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ style_submenu_element("History", "History", "%s/whistory?name=%T", g.zTop, zPageName); } } style_header(zPageName); @@ -225,20 +223,20 @@ @ <hr /><h2>Attachments:</h2> @ <ul> } cnt++; @ <li> - if( g.perm.History && g.perm.Read ){ - @ <a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)"> + if( g.perm.Hyperlink && g.perm.Read ){ + @ %z(href("%R/attachview?page=%T&file=%t",zPageName,zFile)) @ %h(zFile)</a> }else{ @ %h(zFile) } @ added by %h(zUser) on hyperlink_to_date(zDate, "."); if( g.perm.WrWiki && g.perm.Attach ){ - @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>] + @ [%z(href("%R/attachdelete?page=%t&file=%t&from=%R/wiki%%3fname=%f",zPageName,zFile,zPageName))delete</a>] } @ </li> } if( cnt ){ @ </ul> @@ -544,11 +542,11 @@ ** Function called to output extra text at the end of each line in ** a wiki history listing. */ static void wiki_history_extra(int rid){ if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){ - @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a> + @ %z(href("%R/wdiff?name=%t&a=%d",zWikiPageName,rid))[diff]</a> } } /* ** WEBPAGE: whistory @@ -560,11 +558,11 @@ Stmt q; char *zTitle; char *zSQL; const char *zPageName; login_check_credentials(); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } zPageName = PD("name",""); zTitle = mprintf("History Of %s", zPageName); style_header(zTitle); free(zTitle); @@ -597,11 +595,11 @@ Blob w1, w2, d; int diffFlags; login_check_credentials(); rid1 = atoi(PD("a","0")); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } if( rid1==0 ) fossil_redirect_home(); rid2 = atoi(PD("b","0")); zPageName = PD("name",""); zTitle = mprintf("Changes To %s", zPageName); style_header(zTitle); @@ -674,13 +672,13 @@ wiki_prepare_page_list(&q); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); if( size>0 ){ - @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li> + @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li> }else if( showAll ){ - @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li> + @ <li>%z(href("%R/wiki?name=%T",zName))<s>%h(zName)</s></a></li> } } db_finalize(&q); @ </ul> style_footer(); @@ -704,11 +702,11 @@ "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" " ORDER BY lower(tagname) /*sort*/" , zTitle); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); - @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li> + @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li> } db_finalize(&q); @ </ul> style_footer(); } Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -1043,17 +1043,13 @@ || strncmp(zTarget, "mailto:", 7)==0 ){ blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); /* zTerm = "⟾</a>"; // doesn't work on windows */ }else if( zTarget[0]=='/' ){ - if( 1 /* g.perm.History */ ){ - blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); - }else{ - zTerm = ""; - } + blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); }else if( zTarget[0]=='.' || zTarget[0]=='#' ){ - if( 1 /* g.perm.History */ ){ + if( 1 ){ blob_appendf(p->pOut, "<a href=\"%h\">", zTarget); }else{ zTerm = ""; } }else if( is_valid_uuid(zTarget) ){ @@ -1061,36 +1057,34 @@ if( is_ticket(zTarget, &isClosed) ){ /* Special display processing for tickets. Display the hyperlink ** as crossed out if the ticket is closed. */ if( isClosed ){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ blob_appendf(p->pOut, - "<a href=\"%s/info/%s\"><span class=\"wikiTagCancelled\">[", - g.zTop, zTarget + "%z<span class=\"wikiTagCancelled\">[", + href("%R/info/%s",zTarget) ); zTerm = "]</span></a>"; }else{ blob_appendf(p->pOut,"<span class=\"wikiTagCancelled\">["); zTerm = "]</span>"; } }else{ - if( g.perm.History ){ - blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[", - g.zTop, zTarget - ); + if( g.perm.Hyperlink ){ + blob_appendf(p->pOut,"%z[", href("%R/info/%s", zTarget)); zTerm = "]</a>"; }else{ blob_appendf(p->pOut, "["); zTerm = "]"; } } }else if( !in_this_repo(zTarget) ){ blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget); zTerm = "]</span>"; - }else if( g.perm.History ){ - blob_appendf(p->pOut, "<a href=\"%s/info/%s\">[", g.zTop, zTarget); + }else if( g.perm.Hyperlink ){ + blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget)); zTerm = "]</a>"; } }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ blob_appendf(p->pOut, "<a href=\"%s/timeline?c=%T\">", g.zTop, zTarget);