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) &rarr; %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&amp;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&amp;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&amp;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&amp;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&amp;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&amp;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&amp;aid=%s&amp;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&amp;aid=%s&amp;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&amp;aid=%s&amp;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)&amp;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)&amp;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 ){
         @ &nbsp;&nbsp;
-        @ <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 ){
       @ &nbsp;&nbsp;
-      @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;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&nbsp;unified&nbsp;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&nbsp;side-by-side&nbsp;diffs</a>
       }
     }else{
       if( showDiff ){
-        @ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide&nbsp;diffs</a>
+        @ %z(xhref("class='button'","%R/ci/%T",zName))hide&nbsp;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&nbsp;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&nbsp;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&nbsp;unified&nbsp;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&nbsp;side-by-side&nbsp;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&nbsp;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&amp;to=UUID&amp;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&amp;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&amp;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)&amp;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&amp;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&amp;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, "&nbsp;&nbsp;&nbsp;");
     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&amp;copy=1\" rel=\"nofollow\">copy</a>] ", rn);
+      blob_appendf(&ril, "[%zcopy</a>] ",
+                   href("%R/rptedit?rn=%d&copy=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&amp;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">&nbsp;</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&amp;%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)&amp;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)&amp;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) &nbsp;
-          @ <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) &rarr; %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) &rarr; %h(zFilename)
           }else{
             @ <li> %h(zFilename) &nbsp;
           }
-          @ <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)&amp;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&amp;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)&amp;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)&amp;file=%t(zFile)&amp;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&amp;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&amp;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&amp;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"/>
     @  &nbsp; <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&amp;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)&amp;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)&amp;file=%t(zFile)&amp;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)&amp;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 = "&#x27FE;</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);