Changes On Branch spider-defense
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch spider-defense Excluding Merge-Ins

This is equivalent to a diff from f9711803e2 to d9c8a7dd73

2012-04-28
08:05
Move the enhanced spider-defense mechanism into the trunk. check-in: 433cde1ce8 user: drh tags: trunk
08:03
Refinements to the new hyperlink logic and spider defense. Closed-Leaf check-in: d9c8a7dd73 user: drh tags: spider-defense
07:15
Changes anchor tags (<a>) so that the href= attribute can be set by javascript rather than by HTML. This is to make it harder for spiders to follow the hyperlinks to every diff and annotation in the project history. It all seems to work, but it needs further testing and review before going live. check-in: 8ae52fc418 user: drh tags: spider-defense
03:32
Enhance the "translate" utility so that formatting characters can occur in between the "%" and "C" of a printf-style conversion on @-lines. check-in: f9711803e2 user: drh tags: trunk
2012-04-27
15:56
Always update the baseurl: entry in the config table if using a repository from a URL that has not previously been recorded. check-in: c7d6e334f8 user: drh tags: trunk

Changes to src/attach.c.

    73     73       for(i=0; zFilename[i]; i++){
    74     74         if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ 
    75     75           zFilename = &zFilename[i+1];
    76     76           i = -1;
    77     77         }
    78     78       }
    79     79       if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
    80         -      zUrlTail = mprintf("tkt=%s&amp;file=%t", zTarget, zFilename);
           80  +      zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
    81     81       }else{
    82         -      zUrlTail = mprintf("page=%t&amp;file=%t", zTarget, zFilename);
           82  +      zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    83     83       }
    84     84       @
    85     85       @ <p><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a>
    86     86       @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
    87     87       if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    88     88       if( zComment && zComment[0] ){
    89     89         @ %w(zComment)<br />

Changes to src/branch.c.

   319    319       style_submenu_element("Color-Test", "Color-Test", "brlist?colortest");
   320    320     }else{
   321    321       style_submenu_element("All", "All", "brlist?all");
   322    322     }
   323    323     login_anonymous_available();
   324    324     style_sidebox_begin("Nomenclature:", "33%");
   325    325     @ <ol>
   326         -  @ <li> An <div class="sideboxDescribed"><a href="brlist">
          326  +  @ <li> An <div class="sideboxDescribed">%z(href("brlist"))
   327    327     @ open branch</a></div> is a branch that has one or
   328         -  @ more <a href="leaves">open leaves.</a>
          328  +  @ more %z(href("leaves"))open leaves.</a>
   329    329     @ The presence of open leaves presumably means
   330    330     @ that the branch is still being extended with new check-ins.</li>
   331         -  @ <li> A <div class="sideboxDescribed"><a href="brlist?closed">
          331  +  @ <li> A <div class="sideboxDescribed">%z(href("brlist?closed"))
   332    332     @ closed branch</a></div> is a branch with only
   333         -  @ <div class="sideboxDescribed"><a href="leaves?closed">
          333  +  @ <div class="sideboxDescribed">%z(href("leaves?closed"))
   334    334     @ closed leaves</a></div>.
   335    335     @ Closed branches are fixed and do not change (unless they are first
   336    336     @ reopened)</li>
   337    337     @ </ol>
   338    338     style_sidebox_end();
   339    339   
   340    340     branch_prepare_list_query(&q, showAll?1:(showClosed?-1:0));
................................................................................
   354    354         @ <ul>
   355    355         cnt++;
   356    356       }
   357    357       if( colorTest ){
   358    358         const char *zColor = hash_color(zBr);
   359    359         @ <li><span style="background-color: %s(zColor)">
   360    360         @ %h(zBr) &rarr; %s(zColor)</span></li>
   361         -    }else if( g.perm.History ){
   362         -      @ <li><a href="%s(g.zTop)/timeline?r=%T(zBr)")>%h(zBr)</a></li>
   363    361       }else{
   364         -      @ <li><b>%h(zBr)</b></li>
          362  +      @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li>
   365    363       }
   366    364     }
   367    365     if( cnt ){
   368    366       @ </ul>
   369    367     }
   370    368     db_finalize(&q);
   371    369     @ <script  type="text/JavaScript">
................................................................................
   380    378   /*
   381    379   ** This routine is called while for each check-in that is rendered by
   382    380   ** the timeline of a "brlist" page.  Add some additional hyperlinks
   383    381   ** to the end of the line.
   384    382   */
   385    383   static void brtimeline_extra(int rid){
   386    384     Stmt q;
   387         -  if( !g.perm.History ) return;
          385  +  if( !g.perm.Hyperlink ) return;
   388    386     db_prepare(&q, 
   389    387       "SELECT substr(tagname,5) FROM tagxref, tag"
   390    388       " WHERE tagxref.rid=%d"
   391    389       "   AND tagxref.tagid=tag.tagid"
   392    390       "   AND tagxref.tagtype>0"
   393    391       "   AND tag.tagname GLOB 'sym-*'",
   394    392       rid
   395    393     );
   396    394     while( db_step(&q)==SQLITE_ROW ){
   397    395       const char *zTagName = db_column_text(&q, 0);
   398         -    @ <a href="%s(g.zTop)/timeline?r=%T(zTagName)">[timeline]</a>
          396  +    @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a>
   399    397     }
   400    398     db_finalize(&q);
   401    399   }
   402    400   
   403    401   /*
   404    402   ** WEBPAGE: brtimeline
   405    403   **

Changes to src/browse.c.

    75     75   */
    76     76   void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
    77     77     int i, j;
    78     78     char *zSep = "";
    79     79   
    80     80     for(i=0; zPath[i]; i=j){
    81     81       for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    82         -    if( zPath[j] && g.perm.History ){
           82  +    if( zPath[j] && g.perm.Hyperlink ){
    83     83         if( zCI ){
    84         -        blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&amp;name=%#T\">%#h</a>", 
    85         -                     zSep, g.zTop, zCI, j, zPath, j-i, &zPath[i]);
           84  +        char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
           85  +        blob_appendf(pOut, "%s%z%#h</a>", 
           86  +                     zSep, zLink, j-i, &zPath[i]);
    86     87         }else{
    87         -        blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", 
    88         -                     zSep, g.zTop, j, zPath, j-i, &zPath[i]);
           88  +        char *zLink = href("%R/dir?name=%#T", j, zPath);
           89  +        blob_appendf(pOut, "%s%z%#h</a>", 
           90  +                     zSep, zLink, j-i, &zPath[i]);
    89     91         }
    90     92       }else{
    91     93         blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
    92     94       }
    93     95       zSep = "/";
    94     96       while( zPath[j]=='/' ){ j++; }
    95     97     }
................................................................................
   116    118     int rid = 0;
   117    119     char *zUuid = 0;
   118    120     Blob dirname;
   119    121     Manifest *pM = 0;
   120    122     const char *zSubdirLink;
   121    123   
   122    124     login_check_credentials();
   123         -  if( !g.perm.History ){ login_needed(); return; }
          125  +  if( !g.perm.Hyperlink ){ login_needed(); return; }
   124    126     while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
   125    127     style_header("File List");
   126    128     sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
   127    129                             pathelementFunc, 0, 0);
   128    130   
   129    131     /* If the name= parameter is an empty string, make it a NULL pointer */
   130    132     if( zD && strlen(zD)==0 ){ zD = 0; }
................................................................................
   153    155       blob_append(&dirname, "in the top-level directory", -1);
   154    156       zPrefix = "";
   155    157     }
   156    158     if( zCI ){
   157    159       char zShort[20];
   158    160       memcpy(zShort, zUuid, 10);
   159    161       zShort[10] = 0;
   160         -    @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>]
          162  +    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
   161    163       @ %s(blob_str(&dirname))</h2>
   162         -    zSubdirLink = mprintf("%s/dir?ci=%S&amp;name=%T", g.zTop, zUuid, zPrefix);
          164  +    zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
   163    165       if( zD ){
   164         -      style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid);
   165         -      style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD);
          166  +      style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
          167  +      style_submenu_element("All", "All", "%R/dir?name=%t", zD);
   166    168       }else{
   167         -      style_submenu_element("All", "All", "%s/dir", g.zTop);
          169  +      style_submenu_element("All", "All", "%R/dir");
   168    170       }
   169    171     }else{
   170    172       int hasTrunk;
   171    173       @ <h2>The union of all files from all check-ins
   172    174       @ %s(blob_str(&dirname))</h2>
   173    175       hasTrunk = db_exists(
   174    176                     "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'",
   175    177                     TAG_BRANCH);
   176         -    zSubdirLink = mprintf("%s/dir?name=%T", g.zTop, zPrefix);
          178  +    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
   177    179       if( zD ){
   178         -      style_submenu_element("Top", "Top", "%s/dir", g.zTop);
   179         -      style_submenu_element("Tip", "Tip", "%s/dir?name=%t&amp;ci=tip",
   180         -                            g.zTop, zD);
          180  +      style_submenu_element("Top", "Top", "%R/dir");
          181  +      style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
   181    182         if( hasTrunk ){
   182         -        style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;ci=trunk",
   183         -                               g.zTop,zD);
          183  +        style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
          184  +                               zD);
   184    185         }
   185    186       }else{
   186         -      style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zTop);
          187  +      style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
   187    188         if( hasTrunk ){
   188         -        style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zTop);
          189  +        style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");
   189    190         }
   190    191       }
   191    192     }
   192    193   
   193    194     /* Compute the temporary table "localfiles" containing the names
   194    195     ** of all files and subdirectories in the zD[] directory.  
   195    196     **
................................................................................
   276    277         @ </ul></td><td class="browser"><ul class="browser">
   277    278         i = 0;
   278    279       }
   279    280       i++;
   280    281       zFN = db_column_text(&q, 0);
   281    282       if( zFN[0]=='/' ){
   282    283         zFN++;
   283         -      @ <li><a href="%s(zSubdirLink)%T(zFN)">%h(zFN)/</a></li>
          284  +      @ <li>%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
   284    285       }else if( zCI ){
   285    286         const char *zUuid = db_column_text(&q, 1);
   286         -      @ <li><a href="%s(g.zTop)/artifact/%s(zUuid)">%h(zFN)</a></li>
          287  +      @ <li>%z(href("%R/artifact/%s",zUuid))%h(zFN)</a></li>
   287    288       }else{
   288         -      @ <li><a href="%s(g.zTop)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)
          289  +      @ <li>%z(href("%R/finfo?name=%T%T",zPrefix,zFN))%h(zFN)
   289    290         @     </a></li>
   290    291       }
   291    292     }
   292    293     db_finalize(&q);
   293    294     manifest_destroy(pM);
   294    295     @ </ul></td></tr></table>
   295    296     style_footer();
   296    297   }

Changes to src/diff.c.

  1776   1776     while( db_step(&q)==SQLITE_ROW ){
  1777   1777       int pid = db_column_int(&q, 0);
  1778   1778       const char *zUuid = db_column_text(&q, 1);
  1779   1779       const char *zDate = db_column_text(&q, 2);
  1780   1780       const char *zUser = db_column_text(&q, 3);
  1781   1781       if( webLabel ){
  1782   1782         zLabel = mprintf(
  1783         -          "<a href='%s/info/%s' target='infowindow'>%.10s</a> %s %13.13s", 
  1784         -          g.zTop, zUuid, zUuid, zDate, zUser
         1783  +          "<a href='%R/info/%s' target='infowindow'>%.10s</a> %s %13.13s", 
         1784  +          zUuid, zUuid, zDate, zUser
  1785   1785         );
  1786   1786       }else{
  1787   1787         zLabel = mprintf("%.10s %s %13.13s", zUuid, zDate, zUser);
  1788   1788       }
  1789   1789       p->nVers++;
  1790   1790       p->azVers = fossil_realloc(p->azVers, p->nVers*sizeof(p->azVers[0]) );
  1791   1791       p->azVers[p->nVers-1] = zLabel;
................................................................................
  1819   1819     if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
  1820   1820     iLimit = atoi(PD("limit","-1"));
  1821   1821     if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
  1822   1822       fossil_redirect_home();
  1823   1823     }
  1824   1824     style_header("File Annotation");
  1825   1825     if( P("filevers") ) annFlags |= ANN_FILE_VERS;
  1826         -  annotate_file(&ann, fnid, mid, g.perm.History, iLimit, annFlags);
         1826  +  annotate_file(&ann, fnid, mid, g.perm.Hyperlink, iLimit, annFlags);
  1827   1827     if( P("log") ){
  1828   1828       int i;
  1829   1829       @ <h2>Versions analyzed:</h2>
  1830   1830       @ <ol>
  1831   1831       for(i=0; i<ann.nVers; i++){
  1832   1832         @ <li><tt>%s(ann.azVers[i])</tt></li>
  1833   1833       }

Changes to src/diffcmd.c.

   539    539         diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
   540    540       }
   541    541     }
   542    542   }
   543    543   
   544    544   /*
   545    545   ** WEBPAGE: vpatch
   546         -** URL vpatch?from=UUID&amp;to=UUID
          546  +** URL vpatch?from=UUID&to=UUID
   547    547   */
   548    548   void vpatch_page(void){
   549    549     const char *zFrom = P("from");
   550    550     const char *zTo = P("to");
   551    551     login_check_credentials();
   552    552     if( !g.perm.Read ){ login_needed(); return; }
   553    553     if( zFrom==0 || zTo==0 ) fossil_redirect_home();
   554    554   
   555    555     cgi_set_content_type("text/plain");
   556    556     diff_all_two_versions(zFrom, zTo, 0, DIFF_NEWFILE);
   557    557   }

Changes to src/event.c.

    29     29   #include "event.h"
    30     30   
    31     31   /*
    32     32   ** Output a hyperlink to an event given its tagid.
    33     33   */
    34     34   void hyperlink_to_event_tagid(int tagid){
    35     35     char *zEventId;
    36         -  char zShort[12];
    37         -
    38     36     zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
    39     37                        tagid);
    40         -  sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zEventId);
    41         -  if( g.perm.History ){
    42         -    @ [<a href="%s(g.zTop)/event?name=%s(zEventId)">%s(zShort)</a>]
    43         -  }else{
    44         -    @ [%s(zShort)]
    45         -  }
           38  +  @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>]
    46     39     free(zEventId);
    47     40   }
    48     41   
    49     42   /*
    50     43   ** WEBPAGE: event
    51     44   ** URL: /event
    52     45   ** PARAMETERS:
................................................................................
   128    121     if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
   129    122       style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
   130    123                             g.zTop, zEventId);
   131    124     }
   132    125     zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
   133    126     style_submenu_element("Context", "Context", "%s/timeline?c=%T",
   134    127                           g.zTop, zETime);
   135         -  if( g.perm.History ){
          128  +  if( g.perm.Hyperlink ){
   136    129       if( showDetail ){
   137         -      style_submenu_element("Plain", "Plain", "%s/event?name=%s&amp;aid=%s",
          130  +      style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
   138    131                               g.zTop, zEventId, zUuid);
   139    132         if( nextRid ){
   140    133           char *zNext;
   141    134           zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
   142    135           style_submenu_element("Next", "Next",
   143         -                              "%s/event?name=%s&amp;aid=%s&amp;detail=1",
          136  +                              "%s/event?name=%s&aid=%s&detail=1",
   144    137                                 g.zTop, zEventId, zNext);
   145    138           free(zNext);
   146    139         }
   147    140         if( prevRid ){
   148    141           char *zPrev;
   149    142           zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
   150    143           style_submenu_element("Prev", "Prev",
   151         -                              "%s/event?name=%s&amp;aid=%s&amp;detail=1",
          144  +                              "%s/event?name=%s&aid=%s&detail=1",
   152    145                                 g.zTop, zEventId, zPrev);
   153    146           free(zPrev);
   154    147         }
   155    148       }else{
   156    149         style_submenu_element("Detail", "Detail",
   157         -                            "%s/event?name=%s&amp;aid=%s&amp;detail=1",
          150  +                            "%s/event?name=%s&aid=%s&detail=1",
   158    151                               g.zTop, zEventId, zUuid);
   159    152       }
   160    153     }
   161    154   
   162         -  if( showDetail && g.perm.History ){
          155  +  if( showDetail && g.perm.Hyperlink ){
   163    156       int i;
   164    157       const char *zClr = 0;
   165    158       Blob comment;
   166    159   
   167    160       zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
   168         -    @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at
   169         -    @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>]
          161  +    @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at
          162  +    @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
   170    163       @ entered by user <b>%h(pEvent->zUser)</b> on
   171         -    @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p>
          164  +    @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
   172    165       @ <blockquote>
   173    166       for(i=0; i<pEvent->nTag; i++){
   174    167         if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
   175    168           zClr = pEvent->aTag[i].zValue;
   176    169         }
   177    170       }
   178    171       if( zClr && zClr[0]==0 ) zClr = 0;

Changes to src/finfo.c.

   303    303         @ <tr><td>
   304    304         @   <div class="divider">%s(zPrevDate)</div>
   305    305         @ </td></tr>
   306    306       }
   307    307       memcpy(zTime, &zDate[11], 5);
   308    308       zTime[5] = 0;
   309    309       @ <tr><td class="timelineTime">
   310         -    @ <a href="%s(g.zTop)/timeline?c=%t(zDate)">%s(zTime)</a></td>
          310  +    @ %z(href("%R/timeline?c=%t",zDate))%s(zTime)</a></td>
   311    311       @ <td class="timelineGraph"><div id="m%d(gidx)"></div></td>
   312    312       if( zBgClr && zBgClr[0] ){
   313    313         @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
   314    314       }else{
   315    315         @ <td class="timelineTableCell">
   316    316       }
   317    317       sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
   318    318       sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
   319    319       if( zUuid ){
   320         -      if( g.perm.History ){
   321         -        @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%S(zUuid)]</a>
   322         -      }else{
   323         -        @ [%S(zUuid)]
   324         -      }
   325         -      @ part of check-in
          320  +      @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a> part of check-in
   326    321       }else{
   327    322         @ <b>Deleted</b> by check-in
   328    323       }
   329    324       hyperlink_to_uuid(zShortCkin);
   330    325       @ %h(zCom) (user: 
   331    326       hyperlink_to_user(zUser, zDate, "");
   332    327       @ branch: %h(zBr))
   333         -    if( g.perm.History && zUuid ){
          328  +    if( g.perm.Hyperlink && zUuid ){
   334    329         const char *z = zFilename;
   335    330         if( fpid ){
   336         -        @ <a href="%s(g.zTop)/fdiff?v1=%s(zPUuid)&amp;v2=%s(zUuid)">[diff]</a>
          331  +        @ %z(href("%R/fdiff?v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
   337    332         }
   338         -      @ <a href="%s(g.zTop)/annotate?checkin=%S(zCkin)&amp;filename=%h(z)">
          333  +      @ %z(href("%R/annotate?checkin=%S&filename=%h",zCkin,z))
   339    334         @ [annotate]</a>
   340    335       }
   341    336       @ </td></tr>
   342    337     }
   343    338     db_finalize(&q);
   344    339     if( pGraph ){
   345    340       graph_finish(pGraph, 0);

Changes to src/info.c.

   259    259           hyperlink_to_uuid(zOrigUuid);
   260    260         }else{
   261    261           @ propagates to descendants
   262    262         }
   263    263   #if 0
   264    264         if( zValue && fossil_strcmp(zTagname,"branch")==0 ){
   265    265           @ &nbsp;&nbsp;
   266         -        @ <a href="%s(g.zTop)/timeline?r=%T(zValue)">branch timeline</a>
          266  +        @ %z(href("%R/timeline?r=%T",zValue))branch timeline</a>
   267    267         }
   268    268   #endif
   269    269       }
   270    270       if( zSrcUuid && zSrcUuid[0] ){
   271    271         if( tagtype==0 ){
   272    272           @ by
   273    273         }else{
................................................................................
   331    331     const char *zName,    /* Name of the file that has changed */
   332    332     const char *zOld,     /* blob.uuid before change.  NULL for added files */
   333    333     const char *zNew,     /* blob.uuid after change.  NULL for deletes */
   334    334     const char *zOldName, /* Prior name.  NULL if no name change. */
   335    335     int diffFlags,        /* Flags for text_diff().  Zero to omit diffs */
   336    336     int mperm             /* executable or symlink permission for zNew */
   337    337   ){
   338         -  if( !g.perm.History ){
          338  +  if( !g.perm.Hyperlink ){
   339    339       if( zNew==0 ){
   340    340         @ <p>Deleted %h(zName)</p>
   341    341       }else if( zOld==0 ){
   342    342         @ <p>Added %h(zName)</p>
   343    343       }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
   344    344         @ <p>Name change from %h(zOldName) to %h(zName)
   345    345       }else if( fossil_strcmp(zNew, zOld)==0 ){
................................................................................
   352    352         @ <pre style="white-space:pre;">
   353    353         append_diff(zOld, zNew, diffFlags);
   354    354         @ </pre>
   355    355       }
   356    356     }else{
   357    357       if( zOld && zNew ){
   358    358         if( fossil_strcmp(zOld, zNew)!=0 ){
   359         -        @ <p>Modified <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
   360         -        @ from <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
   361         -        @ to <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)].</a>
          359  +        @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
          360  +        @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
          361  +        @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)].</a>
   362    362         }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
   363    363           @ <p>Name change from
   364         -        @ from <a href="%s(g.zTop)/finfo?name=%T(zOldName)">%h(zOldName)</a>
   365         -        @ to <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>.
          364  +        @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
          365  +        @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
   366    366         }else{
   367    367           @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
   368         -        @ <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
          368  +        @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
   369    369         }
   370    370       }else if( zOld ){
   371         -      @ <p>Deleted <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
   372         -      @ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
          371  +      @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
          372  +      @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
   373    373       }else{
   374         -      @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
   375         -      @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
          374  +      @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
          375  +      @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
   376    376       }
   377    377       if( diffFlags ){
   378    378         @ <pre style="white-space:pre;">
   379    379         append_diff(zOld, zNew, diffFlags);
   380    380         @ </pre>
   381    381       }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
   382    382         @ &nbsp;&nbsp;
   383         -      @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a>
          383  +      @ %z(href("%R/fdiff?v1=%S&v2=%S",zOld,zNew))[diff]</a>
   384    384       }
   385    385       @ </p>
   386    386     }
   387    387   }
   388    388   
   389    389   /*
   390    390   ** Construct an appropriate diffFlag for text_diff() based on query
................................................................................
   533    533           const char *zDate = db_column_text(&q, 2);
   534    534           if( zUser==0 || zUser[0]==0 ) zUser = "unknown";
   535    535           @ <tr><th>Received&nbsp;From:</th>
   536    536           @ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate)</td></tr>
   537    537         }
   538    538         db_finalize(&q);
   539    539       }
   540         -    if( g.perm.History ){
          540  +    if( g.perm.Hyperlink ){
   541    541         const char *zProjName = db_get("project-name", "unnamed");
   542    542         @ <tr><th>Timelines:</th><td>
   543         -      @   <a href="%s(g.zTop)/timeline?f=%S(zUuid)">family</a>
          543  +      @   %z(href("%R/timeline?f=%S",zUuid))family</a>
   544    544         if( zParent ){
   545         -        @ | <a href="%s(g.zTop)/timeline?p=%S(zUuid)">ancestors</a>
          545  +        @ | %z(href("%R/timeline?p=%S",zUuid))ancestors</a>
   546    546         }
   547    547         if( !isLeaf ){
   548         -        @ | <a href="%s(g.zTop)/timeline?d=%S(zUuid)">descendants</a>
          548  +        @ | %z(href("%R/timeline?d=%S",zUuid))descendants</a>
   549    549         }
   550    550         if( zParent && !isLeaf ){
   551         -        @ | <a href="%s(g.zTop)/timeline?dp=%S(zUuid)">both</a>
          551  +        @ | %z(href("%R/timeline?dp=%S",zUuid))both</a>
   552    552         }
   553    553         db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag "
   554    554                        " WHERE rid=%d AND tagtype>0 "
   555    555                        "   AND tag.tagid=tagxref.tagid "
   556    556                        "   AND +tag.tagname GLOB 'sym-*'", rid);
   557    557         while( db_step(&q)==SQLITE_ROW ){
   558    558           const char *zTagName = db_column_text(&q, 0);
   559         -        @  | <a href="%s(g.zTop)/timeline?r=%T(zTagName)">%h(zTagName)</a>
          559  +        @  | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName)</a>
   560    560         }
   561    561         db_finalize(&q);
   562    562         @ </td></tr>
   563    563         @ <tr><th>Other&nbsp;Links:</th>
   564    564         @   <td>
   565         -      @     <a href="%s(g.zTop)/dir?ci=%S(zUuid)">files</a>
          565  +      @     %z(href("%R/dir?ci=%S",zUuid))files</a>
   566    566         if( g.perm.Zip ){
   567         -        char *zUrl = mprintf("%s/tarball/%s-%S.tar.gz?uuid=%s",
   568         -                             g.zTop, zProjName, zUuid, zUuid);
   569         -        @ | <a href="%s(zUrl)">Tarball</a>
   570         -        @ | <a href="%s(g.zTop)/zip/%s(zProjName)-%S(zUuid).zip?uuid=%s(zUuid)">
          567  +        char *zUrl = mprintf("%R/tarball/%s-%S.tar.gz?uuid=%s",
          568  +                             zProjName, zUuid, zUuid);
          569  +        @ | %z(href("%s",zUrl))Tarball</a>
          570  +        @ | %z(href("%R/zip/%s-%S.zip?uuid=%s",zProjName,zUuid,zUuid))
   571    571           @         ZIP archive</a>
   572    572           fossil_free(zUrl);
   573    573         }
   574         -      @   | <a href="%s(g.zTop)/artifact/%S(zUuid)">manifest</a>
          574  +      @   | %z(href("%R/artifact/%S",zUuid))manifest</a>
   575    575         if( g.perm.Write ){
   576         -        @   | <a href="%s(g.zTop)/ci_edit?r=%S(zUuid)">edit</a>
          576  +        @   | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
   577    577         }
   578    578         @   </td>
   579    579         @ </tr>
   580    580       }
   581    581       @ </table>
   582    582     }else{
   583    583       style_header("Check-in Information");
................................................................................
   588    588     if( zParent ){
   589    589       @ <div class="section">Changes</div>
   590    590       @ <div class="sectionmenu">
   591    591       showDiff = g.zPath[0]!='c';
   592    592       if( db_get_boolean("show-version-diffs", 0)==0 ){
   593    593         showDiff = !showDiff;
   594    594         if( showDiff ){
   595         -        @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)">
          595  +        @ %z(xhref("class='button'","%R/vinfo/%T",zName)))
   596    596           @ hide&nbsp;diffs</a>
   597    597           if( sideBySide ){
   598         -          @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
          598  +          @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
   599    599             @ unified&nbsp;diffs</a>
   600    600           }else{
   601         -          @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
          601  +          @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
   602    602             @ side-by-side&nbsp;diffs</a>
   603    603           }
   604    604         }else{
   605         -        @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=0">
          605  +        @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
   606    606           @ show&nbsp;unified&nbsp;diffs</a>
   607         -        @ <a class="button" href="%s(g.zTop)/ci/%T(zName)?sbs=1">
          607  +        @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
   608    608           @ show&nbsp;side-by-side&nbsp;diffs</a>
   609    609         }
   610    610       }else{
   611    611         if( showDiff ){
   612         -        @ <a class="button" href="%s(g.zTop)/ci/%T(zName)">hide&nbsp;diffs</a>
          612  +        @ %z(xhref("class='button'","%R/ci/%T",zName))hide&nbsp;diffs</a>
   613    613           if( sideBySide ){
   614         -          @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=0">
          614  +          @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName))
   615    615             @ unified&nbsp;diffs</a>
   616    616           }else{
   617         -          @ <a class="button" href="%s(g.zTop)/info/%T(zName)?sbs=1">
          617  +          @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName))
   618    618             @ side-by-side&nbsp;diffs</a>
   619    619           }
   620    620         }else{
   621         -        @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
          621  +        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName))
   622    622           @ show&nbsp;unified&nbsp;diffs</a>
   623         -        @ <a class="button" href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
          623  +        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName))
   624    624           @ show&nbsp;side-by-side&nbsp;diffs</a>
   625    625         }
   626    626       }
   627         -    @ <a class="button" href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">
          627  +    @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid))
   628    628       @ patch</a></div>
   629    629       db_prepare(&q,
   630    630          "SELECT name,"
   631    631          "       mperm,"
   632    632          "       (SELECT uuid FROM blob WHERE rid=mlink.pid),"
   633    633          "       (SELECT uuid FROM blob WHERE rid=mlink.fid),"
   634    634          "       (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
................................................................................
   696    696       @ <tr><th>Date:</th><td>
   697    697       hyperlink_to_date(zDate, "</td></tr>");
   698    698       if( g.perm.Setup ){
   699    699         @ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
   700    700       }
   701    701       @ <tr><th>Original&nbsp;User:</th><td>
   702    702       hyperlink_to_user(zUser, zDate, "</td></tr>");
   703         -    if( g.perm.History ){
          703  +    if( g.perm.Hyperlink ){
   704    704         @ <tr><th>Commands:</th>
   705    705         @   <td>
   706         -      @     <a href="%s(g.zTop)/whistory?name=%t(zName)">history</a>
   707         -      @     | <a href="%s(g.zTop)/artifact/%S(zUuid)">raw-text</a>
          706  +      @     &z(href("%R/whistory?name=%t",zName))history</a>
          707  +      @     | %z(href("%R/artifact/%S",zUuid))raw-text</a>
   708    708         @   </td>
   709    709         @ </tr>
   710    710       }
   711    711       @ </table></p>
   712    712     }else{
   713    713       style_header("Wiki Information");
   714    714       rid = 0;
................................................................................
   790    790     }
   791    791     db_finalize(&q);
   792    792   }
   793    793   
   794    794   
   795    795   /*
   796    796   ** WEBPAGE: vdiff
   797         -** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN;sbs=BOOLEAN
          797  +** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN
   798    798   **
   799    799   ** Show all differences between two checkins.  
   800    800   */
   801    801   void vdiff_page(void){
   802    802     int ridFrom, ridTo;
   803    803     int showDetail = 0;
   804    804     int sideBySide = 0;
................................................................................
   934    934         if( mPerm==PERM_LNK ){
   935    935           @ <li>Symbolic link
   936    936         }else if( mPerm==PERM_EXE ){
   937    937           @ <li>Executable file
   938    938         }else{
   939    939           @ <li>File        
   940    940         }
   941         -      if( g.perm.History ){
   942         -        @ <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
   943         -      }else{
   944         -        @ %h(zName)
   945         -      }
          941  +      @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
   946    942         @ <ul>
   947    943         prevName = fossil_strdup(zName);
   948    944       }
   949    945       @ <li>
   950    946       hyperlink_to_date(zDate,"");
   951    947       @ - part of checkin
   952    948       hyperlink_to_uuid(zVers);
   953    949       if( zBr && zBr[0] ){
   954         -      if( g.perm.History ){
   955         -        @ on branch <a href="%s(g.zTop)/timeline?r=%T(zBr)">%h(zBr)</a>
   956         -      }else{
   957         -        @ on branch %h(zBr)
   958         -      }
          950  +      @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
   959    951       }
   960    952       @ - %w(zCom) (user:
   961    953       hyperlink_to_user(zUser,zDate,"");
   962    954       @ )
   963         -    if( g.perm.History ){
   964         -      @ <a href="%s(g.zTop)/annotate?checkin=%S(zVers)&filename=%T(zName)">
          955  +    if( g.perm.Hyperlink ){
          956  +      @ %z(href("%R/annotate?checkin=%S&filename=%T",zVers,zName))
   965    957         @ [annotate]</a>
   966    958       }
   967    959       cnt++;
   968    960       if( pDownloadName && blob_size(pDownloadName)==0 ){
   969    961         blob_append(pDownloadName, zName, -1);
   970    962       }
   971    963     }
................................................................................
   987    979       const char *zDate = db_column_text(&q, 1);
   988    980       const char *zUser = db_column_text(&q, 2);
   989    981       if( cnt>0 ){
   990    982         @ Also wiki page
   991    983       }else{
   992    984         @ Wiki page
   993    985       }
   994         -    if( g.perm.History ){
   995         -      @ [<a href="%s(g.zTop)/wiki?name=%t(zPagename)">%h(zPagename)</a>]
   996         -    }else{
   997         -      @ [%h(zPagename)]
   998         -    }
   999         -    @ by
          986  +    @ [%z(href("%R/wiki?name=%t",zPagename))%h(zPagename)</a>] by
  1000    987       hyperlink_to_user(zUser,zDate," on");
  1001    988       hyperlink_to_date(zDate,".");
  1002    989       nWiki++;
  1003    990       cnt++;
  1004    991       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1005    992         blob_appendf(pDownloadName, "%s.wiki", zPagename);
  1006    993       }
................................................................................
  1063   1050       /* const char *zSrc = db_column_text(&q, 4); */
  1064   1051       if( cnt>0 ){
  1065   1052         @ Also attachment "%h(zFilename)" to
  1066   1053       }else{
  1067   1054         @ Attachment "%h(zFilename)" to
  1068   1055       }
  1069   1056       if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
  1070         -      if( g.perm.History && g.perm.RdTkt ){
  1071         -        @ ticket [<a href="%s(g.zTop)/tktview?name=%S(zTarget)">%S(zTarget)</a>]
         1057  +      if( g.perm.Hyperlink && g.perm.RdTkt ){
         1058  +        @ ticket [%z(href("%R/tktview?name=%S",zTarget))%S(zTarget)</a>]
  1072   1059         }else{
  1073   1060           @ ticket [%S(zTarget)]
  1074   1061         }
  1075   1062       }else{
  1076         -      if( g.perm.History && g.perm.RdWiki ){
  1077         -        @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>]
         1063  +      if( g.perm.Hyperlink && g.perm.RdWiki ){
         1064  +        @ wiki page [%z(href("%R/wiki?name=%t",zTarget)))%h(zTarget)</a>]
  1078   1065         }else{
  1079   1066           @ wiki page [%h(zTarget)]
  1080   1067         }
  1081   1068       }
  1082   1069       @ added by
  1083   1070       hyperlink_to_user(zUser,zDate," on");
  1084   1071       hyperlink_to_date(zDate,".");
................................................................................
  1089   1076     }
  1090   1077     db_finalize(&q);
  1091   1078     if( cnt==0 ){
  1092   1079       @ Control artifact.
  1093   1080       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1094   1081         blob_appendf(pDownloadName, "%.10s.txt", zUuid);
  1095   1082       }
  1096         -  }else if( linkToView && g.perm.History ){
  1097         -    @ <a href="%s(g.zTop)/artifact/%S(zUuid)">[view]</a>
         1083  +  }else if( linkToView && g.perm.Hyperlink ){
         1084  +    @ %z(href("%R/artifact/%S",zUuid))[view]</a>
  1098   1085     }
  1099   1086   }
  1100   1087   
  1101   1088   
  1102   1089   /*
  1103   1090   ** WEBPAGE: fdiff
  1104   1091   ** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN
................................................................................
  1158   1145         style_submenu_element("Unified Diff", "udiff",
  1159   1146                               "%s/fdiff?v1=%T&v2=%T&sbs=0",
  1160   1147                               g.zTop, P("v1"), P("v2"));
  1161   1148       }
  1162   1149   
  1163   1150       if( P("smhdr")!=0 ){
  1164   1151         @ <h2>Differences From Artifact
  1165         -      @ <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a> To
  1166         -      @ <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>.</h2>
         1152  +      @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
         1153  +      @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
  1167   1154       }else{
  1168   1155         @ <h2>Differences From
  1169         -      @ Artifact <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a>:</h2>
         1156  +      @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
  1170   1157         object_description(v1, 0, 0);
  1171         -      @ <h2>To Artifact
  1172         -      @ <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>:</h2>
         1158  +      @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
  1173   1159         object_description(v2, 0, 0);
  1174   1160       }
  1175   1161       @ <hr />
  1176   1162       @ <div class="%s(zStyle)">
  1177   1163       @ %s(blob_str(&diff))
  1178   1164       @ </div>
  1179   1165       blob_reset(&diff);
................................................................................
  1270   1256     rid = name_to_rid_www("name");
  1271   1257     login_check_credentials();
  1272   1258     if( !g.perm.Read ){ login_needed(); return; }
  1273   1259     if( rid==0 ) fossil_redirect_home();
  1274   1260     if( g.perm.Admin ){
  1275   1261       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1276   1262       if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
  1277         -      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
         1263  +      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
  1278   1264               g.zTop, zUuid);
  1279   1265       }else{
  1280   1266         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1281   1267               g.zTop, zUuid);
  1282   1268       }
  1283   1269     }
  1284   1270     style_header("Hex Artifact Content");
................................................................................
  1417   1403   
  1418   1404     login_check_credentials();
  1419   1405     if( !g.perm.Read ){ login_needed(); return; }
  1420   1406     if( rid==0 ) fossil_redirect_home();
  1421   1407     if( g.perm.Admin ){
  1422   1408       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1423   1409       if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
  1424         -      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
         1410  +      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
  1425   1411               g.zTop, zUuid);
  1426   1412       }else{
  1427   1413         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1428   1414               g.zTop, zUuid);
  1429   1415       }
  1430   1416     }
  1431   1417     style_header("Artifact Content");
................................................................................
  1476   1462           output_text_with_line_numbers(z, zLn);
  1477   1463         }else{
  1478   1464           @ <pre>
  1479   1465           @ %h(z)
  1480   1466           @ </pre>
  1481   1467         }
  1482   1468       }else if( strncmp(zMime, "image/", 6)==0 ){
  1483         -      @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&amp;m=%s(zMime)"></img>
         1469  +      @ <img src="%s(g.zTop)/raw?name=%s(zUuid)&m=%s(zMime)"></img>
  1484   1470       }else{
  1485   1471         @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
  1486   1472       }
  1487   1473       @ </blockquote>
  1488   1474     }
  1489   1475     style_footer();
  1490   1476   }  
................................................................................
  1505   1491     login_check_credentials();
  1506   1492     if( !g.perm.RdTkt ){ login_needed(); return; }
  1507   1493     rid = name_to_rid_www("name");
  1508   1494     if( rid==0 ){ fossil_redirect_home(); }
  1509   1495     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1510   1496     if( g.perm.Admin ){
  1511   1497       if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
  1512         -      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
         1498  +      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1",
  1513   1499               g.zTop, zUuid);
  1514   1500       }else{
  1515   1501         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1516   1502               g.zTop, zUuid);
  1517   1503       }
  1518   1504     }
  1519   1505     pTktChng = manifest_get(rid, CFTYPE_TICKET);
................................................................................
  1520   1506     if( pTktChng==0 ){
  1521   1507       fossil_redirect_home();
  1522   1508     }
  1523   1509     style_header("Ticket Change Details");
  1524   1510     zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate);
  1525   1511     memcpy(zTktName, pTktChng->zTicketUuid, 10);
  1526   1512     zTktName[10] = 0;
  1527         -  if( g.perm.History ){
         1513  +  if( g.perm.Hyperlink ){
  1528   1514       @ <h2>Changes to ticket
  1529         -    @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2>
         1515  +    @ %z(href("%R/tktview/%s",pTktChng->zTicketUuid)))%s(zTktName)</a></h2>
  1530   1516       @
  1531   1517       @ <p>By %h(pTktChng->zUser) on %s(zDate).  See also:
  1532         -    @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and
  1533         -    @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket
  1534         -    @ history</a></p>
         1518  +    @ %z(href("%R/artifact/%T",zUuid))artifact content</a>, and
         1519  +    @ %z(href("%R/tkthistory/%s",pTktChng->zTicketUuid))ticket history</a></p>
  1535   1520     }else{
  1536   1521       @ <h2>Changes to ticket %s(zTktName)</h2>
  1537   1522       @
  1538   1523       @ <p>By %h(pTktChng->zUser) on %s(zDate).
  1539   1524       @ </p>
  1540   1525     }
  1541   1526     @
................................................................................
  1963   1948       @ %s(blob_str(&suffix))
  1964   1949       @ </td></tr></table>
  1965   1950       @ </blockquote>
  1966   1951       @ <hr />
  1967   1952       blob_reset(&suffix);
  1968   1953     }
  1969   1954     @ <p>Make changes to attributes of check-in
  1970         -  @ [<a href="ci?name=%s(zUuid)">%s(zUuid)</a>]:</p>
         1955  +  @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
  1971   1956     @ <form action="%s(g.zTop)/ci_edit" method="post"><div>
  1972   1957     login_insert_csrf_secret();
  1973   1958     @ <input type="hidden" name="r" value="%S(zUuid)" />
  1974   1959     @ <table border="0" cellspacing="10">
  1975   1960   
  1976   1961     @ <tr><td align="right" valign="top"><b>User:</b></td>
  1977   1962     @ <td valign="top">

Changes to src/json_dir.c.

    64     64     char const * zDX = NULL;
    65     65     int nD;
    66     66     char * zUuid = NULL;
    67     67     char const * zCI = NULL;
    68     68     Manifest * pM = NULL;
    69     69     Stmt q = empty_Stmt;
    70     70     int rid = 0;
    71         -  if( !g.perm.History ){
           71  +  if( !g.perm.Hyperlink ){
    72     72       json_set_err(FSL_JSON_E_DENIED, "Requires 'h' permissions.");
    73     73       return NULL;
    74     74     }
    75     75     zCI = json_find_option_cstr("checkin",NULL,"ci" );
    76     76   
    77     77     /* If a specific check-in is requested, fetch and parse it.  If the
    78     78     ** specific check-in does not exist, clear zCI.  zCI==0 will cause all

Changes to src/json_timeline.c.

    51     51   */
    52     52   cson_value * json_page_timeline(){
    53     53   #if 0
    54     54     /* The original timeline code does not require 'h' access,
    55     55        but it arguably should. For JSON mode i think one could argue
    56     56        that History permissions are required.
    57     57     */
    58         -  if(! g.perm.History && !g.perm.Read ){
           58  +  if(! g.perm.Hyperlink && !g.perm.Read ){
    59     59       json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access.");
    60     60       return NULL;
    61     61     }
    62     62   #endif
    63     63     return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]);
    64     64   }
    65     65   
................................................................................
   424    424     cson_value * listV = NULL;
   425    425     cson_array * list = NULL;
   426    426     int check = 0;
   427    427     char showFiles = -1/*magic number*/;
   428    428     Stmt q = empty_Stmt;
   429    429     char warnRowToJsonFailed = 0;
   430    430     Blob sql = empty_blob;
   431         -  if( !g.perm.History ){
          431  +  if( !g.perm.Hyperlink ){
   432    432       /* Reminder to self: HTML impl requires 'o' (Read)
   433    433          rights.
   434    434       */
   435    435       json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'h' access." );
   436    436       return NULL;
   437    437     }
   438    438     showFiles = json_find_option_bool("files",NULL,"f",0);

Changes to src/json_wiki.c.

   490    490     int argPos = g.json.dispatchDepth;
   491    491     int r1 = 0, r2 = 0;
   492    492     Manifest * pW1 = NULL, *pW2 = NULL;
   493    493     Blob w1 = empty_blob, w2 = empty_blob, d = empty_blob;
   494    494     char const * zErrTag = NULL;
   495    495     int diffFlags;
   496    496     char * zUuid = NULL;
   497         -  if( !g.perm.History ){
          497  +  if( !g.perm.Hyperlink ){
   498    498       json_set_err(FSL_JSON_E_DENIED,
   499    499                    "Requires 'h' permissions.");
   500    500       return NULL;
   501    501     }
   502    502   
   503    503     
   504    504     zV1 = json_find_option_cstr2( "v1",NULL, NULL, ++argPos );

Changes to src/login.c.

   560    560         */
   561    561         login_set_user_cookie(zUsername, uid, NULL);
   562    562         redirect_to_g();
   563    563       }
   564    564     }
   565    565     style_header("Login/Logout");
   566    566     @ %s(zErrMsg)
   567         -  if( zGoto ){
          567  +  if( zGoto && P("anon")==0 ){
   568    568       @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
   569    569     }
   570    570     @ <form action="login" method="post">
   571    571     if( zGoto ){
   572    572       @ <input type="hidden" name="g" value="%h(zGoto)" />
   573    573     }
   574    574     @ <table class="login_out">
................................................................................
   909    909     if( fossil_strcmp(g.zLogin,"nobody")==0 ){
   910    910       g.zLogin = 0;
   911    911     }
   912    912   
   913    913     /* Set the capabilities */
   914    914     login_replace_capabilities(zCap, 0);
   915    915     login_set_anon_nobody_capabilities();
   916         -  if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1)
          916  +  if( zCap[0] && !g.perm.Hyperlink
          917  +   && db_get_boolean("auto-enable-hyperlinks",1)
   917    918         && isHuman(P("HTTP_USER_AGENT")) ){
   918         -    g.perm.History = 1;
          919  +    g.perm.Hyperlink = 1;
          920  +    g.javascriptHyperlink = 1;
   919    921     }
   920    922   
   921    923     /* If the public-pages glob pattern is defined and REQUEST_URI matches
   922    924     ** one of the globs in public-pages, then also add in all default-perms
   923    925     ** permissions.
   924    926     */
   925    927     zPublicPages = db_get("public-pages",0);
................................................................................
   971    973     if(NULL==zCap){
   972    974       return;
   973    975     }
   974    976     for(i=0; zCap[i]; i++){
   975    977       switch( zCap[i] ){
   976    978         case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
   977    979         case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
   978         -                              g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
   979         -                              g.perm.ApndWiki = g.perm.History = g.perm.Clone = 
   980         -                              g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
   981         -                              g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1;
   982         -                              /* Fall thru into Read/Write */
          980  +                           g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
          981  +                           g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone = 
          982  +                           g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
          983  +                           g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1;
          984  +                           /* Fall thru into Read/Write */
   983    985         case 'i':   g.perm.Read = g.perm.Write = 1;                     break;
   984    986         case 'o':   g.perm.Read = 1;                                 break;
   985    987         case 'z':   g.perm.Zip = 1;                                  break;
   986    988   
   987    989         case 'd':   g.perm.Delete = 1;                               break;
   988         -      case 'h':   g.perm.History = 1;                              break;
          990  +      case 'h':   g.perm.Hyperlink = 1;                            break;
   989    991         case 'g':   g.perm.Clone = 1;                                break;
   990    992         case 'p':   g.perm.Password = 1;                             break;
   991    993   
   992    994         case 'j':   g.perm.RdWiki = 1;                               break;
   993    995         case 'k':   g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1;    break;
   994    996         case 'm':   g.perm.ApndWiki = 1;                             break;
   995    997         case 'f':   g.perm.NewWiki = 1;                              break;
................................................................................
  1051   1053         case 'a':  rc = g.perm.Admin;     break;
  1052   1054         case 'b':  rc = g.perm.Attach;    break;
  1053   1055         case 'c':  rc = g.perm.ApndTkt;   break;
  1054   1056         case 'd':  rc = g.perm.Delete;    break;
  1055   1057         case 'e':  rc = g.perm.RdAddr;    break;
  1056   1058         case 'f':  rc = g.perm.NewWiki;   break;
  1057   1059         case 'g':  rc = g.perm.Clone;     break;
  1058         -      case 'h':  rc = g.perm.History;   break;
         1060  +      case 'h':  rc = g.perm.Hyperlink; break;
  1059   1061         case 'i':  rc = g.perm.Write;     break;
  1060   1062         case 'j':  rc = g.perm.RdWiki;    break;
  1061   1063         case 'k':  rc = g.perm.WrWiki;    break;
  1062         -      /* case 'l': */
  1063   1064         case 'm':  rc = g.perm.ApndWiki;  break;
  1064   1065         case 'n':  rc = g.perm.NewTkt;    break;
  1065   1066         case 'o':  rc = g.perm.Read;      break;
  1066   1067         case 'p':  rc = g.perm.Password;  break;
  1067   1068         /* case 'q': */
  1068   1069         case 'r':  rc = g.perm.RdTkt;     break;
  1069   1070         case 's':  rc = g.perm.Setup;     break;
................................................................................
  1127   1128       cgi_redirect(mprintf("login?g=%T", zUrl));
  1128   1129       /* NOTREACHED */
  1129   1130       assert(0);
  1130   1131     }
  1131   1132   }
  1132   1133   
  1133   1134   /*
  1134         -** Call this routine if the user lacks okHistory permission.  If
  1135         -** the anonymous user has okHistory permission, then paint a mesage
         1135  +** Call this routine if the user lacks g.perm.Hyperlink permission.  If
         1136  +** the anonymous user has Hyperlink permission, then paint a mesage
  1136   1137   ** to inform the user that much more information is available by
  1137   1138   ** logging in as anonymous.
  1138   1139   */
  1139   1140   void login_anonymous_available(void){
  1140         -  if( !g.perm.History &&
         1141  +  if( !g.perm.Hyperlink &&
  1141   1142         db_exists("SELECT 1 FROM user"
  1142   1143                   " WHERE login='anonymous'"
  1143   1144                   "   AND cap LIKE '%%h%%'") ){
  1144   1145       const char *zUrl = PD("REQUEST_URI", "index");
  1145   1146       @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
  1146         -    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
         1147  +    @ Use <a href="%s(g.zTop)/login?anon=1&g=%T(zUrl)">anonymous login</a>
  1147   1148       @ to enable hyperlinks.</p>
  1148   1149     }
  1149   1150   }
  1150   1151   
  1151   1152   /*
  1152   1153   ** While rendering a form, call this routine to add the Anti-CSRF token
  1153   1154   ** as a hidden element of the form.

Changes to src/main.c.

    58     58     char Setup;            /* s: use Setup screens on web interface */
    59     59     char Admin;            /* a: administrative permission */
    60     60     char Delete;           /* d: delete wiki or tickets */
    61     61     char Password;         /* p: change password */
    62     62     char Query;            /* q: create new reports */
    63     63     char Write;            /* i: xfer inbound. checkin */
    64     64     char Read;             /* o: xfer outbound. checkout */
    65         -  char History;          /* h: access historical information. */
           65  +  char Hyperlink;        /* h: enable the display of hyperlinks */
    66     66     char Clone;            /* g: clone */
    67     67     char RdWiki;           /* j: view wiki via web */
    68     68     char NewWiki;          /* f: create new wiki via web */
    69     69     char ApndWiki;         /* m: append to wiki via web */
    70     70     char WrWiki;           /* k: edit wiki via web */
    71     71     char RdTkt;            /* r: view tickets via web */
    72     72     char NewTkt;           /* n: create new tickets */
................................................................................
   133    133     FILE *httpIn;           /* Accept HTTP input from here */
   134    134     FILE *httpOut;          /* Send HTTP output here */
   135    135     int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
   136    136     int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
   137    137     int *aCommitFile;       /* Array of files to be committed */
   138    138     int markPrivate;        /* All new artifacts are private if true */
   139    139     int clockSkewSeen;      /* True if clocks on client and server out of sync */
   140         -  int isHTTP;             /* True if running in server/CGI modes, else assume CLI. */
          140  +  char isHTTP;            /* True if erver/CGI modes, else assume CLI. */
          141  +  char javascriptHyperlink; /* If true, set href= using script, not HTML */
   141    142   
   142    143     int urlIsFile;          /* True if a "file:" url */
   143    144     int urlIsHttps;         /* True if a "https:" url */
   144    145     int urlIsSsh;           /* True if an "ssh:" url */
   145    146     char *urlName;          /* Hostname for http: or filename for file: */
   146    147     char *urlHostname;      /* The HOST: parameter on http headers */
   147    148     char *urlProtocol;      /* "http" or "https" */

Changes to src/printf.c.

    45     45   #define etHTTPIZE    17 /* Make text safe for HTTP.  "/" encoded as %2f */
    46     46   #define etURLIZE     18 /* Make text safe for HTTP.  "/" not encoded */
    47     47   #define etFOSSILIZE  19 /* The fossil header encoding format. */
    48     48   #define etPATH       20 /* Path type */
    49     49   #define etWIKISTR    21 /* Wiki text rendered from a char*: %w */
    50     50   #define etWIKIBLOB   22 /* Wiki text rendered from a Blob*: %W */
    51     51   #define etSTRINGID   23 /* String with length limit for a UUID prefix: %S */
           52  +#define etROOT       24 /* String value of g.zTop: % */
    52     53   
    53     54   
    54     55   /*
    55     56   ** An "etByte" is an 8-bit unsigned value.
    56     57   */
    57     58   typedef unsigned char etByte;
    58     59   
................................................................................
    91     92     {  'q',  0, 4, etSQLESCAPE,  0,  0 },
    92     93     {  'Q',  0, 4, etSQLESCAPE2, 0,  0 },
    93     94     {  'b',  0, 2, etBLOB,       0,  0 },
    94     95     {  'B',  0, 2, etBLOBSQL,    0,  0 },
    95     96     {  'w',  0, 2, etWIKISTR,    0,  0 },
    96     97     {  'W',  0, 2, etWIKIBLOB,   0,  0 },
    97     98     {  'h',  0, 4, etHTMLIZE,    0,  0 },
           99  +  {  'R',  0, 0, etROOT,       0,  0 },
    98    100     {  't',  0, 4, etHTTPIZE,    0,  0 },  /* "/" -> "%2F" */
    99    101     {  'T',  0, 4, etURLIZE,     0,  0 },  /* "/" unchanged */
   100    102     {  'F',  0, 4, etFOSSILIZE,  0,  0 },
   101    103     {  'S',  0, 4, etSTRINGID,   0,  0 },
   102    104     {  'c',  0, 0, etCHARX,      0,  0 },
   103    105     {  'o',  8, 0, etRADIX,      0,  2 },
   104    106     {  'u', 10, 0, etRADIX,      0,  0 },
................................................................................
   568    570               bufpt[i]='/';
   569    571             }else{
   570    572               bufpt[i]=e[i];
   571    573             }
   572    574           }
   573    575           bufpt[length]='\0';
   574    576           break;
          577  +      }
          578  +      case etROOT: {
          579  +        bufpt = g.zTop;
          580  +        length = (int)strlen(bufpt);
          581  +        break;
   575    582         }
   576    583         case etSTRINGID: {
   577    584           precision = 16;
   578    585           /* Fall through */
   579    586         }
   580    587         case etSTRING:
   581    588         case etDYNSTRING: {

Changes to src/report.c.

    54     54       }
    55     55       rn = db_column_int(&q, 0);
    56     56       cnt++;
    57     57       blob_appendf(&ril, "<li>");
    58     58       if( zTitle[0] == '_' ){
    59     59         blob_appendf(&ril, "%s", zTitle);
    60     60       } else {
    61         -      blob_appendf(&ril, "<a href=\"rptview?rn=%d\" rel=\"nofollow\">%h</a>", rn, zTitle);
           61  +      blob_appendf(&ril, "%z%h</a>", href("%R/rptview?rn=%d", rn), zTitle);
    62     62       }
    63     63       blob_appendf(&ril, "&nbsp;&nbsp;&nbsp;");
    64     64       if( g.perm.Write && zOwner && zOwner[0] ){
    65     65         blob_appendf(&ril, "(by <i>%h</i></i>) ", zOwner);
    66     66       }
    67     67       if( g.perm.TktFmt ){
    68         -      blob_appendf(&ril, "[<a href=\"rptedit?rn=%d&amp;copy=1\" rel=\"nofollow\">copy</a>] ", rn);
           68  +      blob_appendf(&ril, "[%zcopy</a>] ",
           69  +                   href("%R/rptedit?rn=%d&copy=1", rn));
    69     70       }
    70     71       if( g.perm.Admin 
    71     72        || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0)
    72     73       ){
    73         -      blob_appendf(&ril, "[<a href=\"rptedit?rn=%d\" rel=\"nofollow\">edit</a>] ", rn);
           74  +      blob_appendf(&ril, "[%zedit</a>]", 
           75  +                         href("%R/rptedit?rn=%d", rn));
    74     76       }
    75     77       if( g.perm.TktFmt ){
    76         -      blob_appendf(&ril, "[<a href=\"rptsql?rn=%d\" rel=\"nofollow\">sql</a>] ", rn);
           78  +      blob_appendf(&ril, "[%zsql</a>]",
           79  +                         href("%R/rptsql?rn=%d", rn));
    77     80       }
    78     81       blob_appendf(&ril, "</li>\n");
    79     82     }
    80     83   
    81     84     Th_Store("report_items", blob_str(&ril));
    82     85     
    83     86     Th_Render(zScript);
................................................................................
   414    417         zTitle = mprintf("Copy Of %s", zTitle);
   415    418         zOwner = g.zLogin;
   416    419       }
   417    420     }
   418    421     if( zOwner==0 ) zOwner = g.zLogin;
   419    422     style_submenu_element("Cancel", "Cancel", "reportlist");
   420    423     if( rn>0 ){
   421         -    style_submenu_element("Delete", "Delete", "rptedit?rn=%d&amp;del1=1", rn);
          424  +    style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn);
   422    425     }
   423    426     style_header(rn>0 ? "Edit Report Format":"Create New Report Format");
   424    427     if( zErr ){
   425    428       @ <blockquote class="reportError">%h(zErr)</blockquote>
   426    429     }
   427    430     @ <form action="rptedit" method="post"><div>
   428    431     @ <input type="hidden" name="rn" value="%d(rn)" />
................................................................................
   718    721     for(i=0; i<nArg; i++){
   719    722       char *zData;
   720    723       if( i==pState->iBg ) continue;
   721    724       zData = azArg[i];
   722    725       if( zData==0 ) zData = "";
   723    726       if( pState->iNewRow>=0 && i>=pState->iNewRow ){
   724    727         if( zTid && g.perm.Write ){
   725         -        @ <td valign="top"><a href="tktedit/%h(zTid)">edit</a></td>
          728  +        @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
   726    729           zTid = 0;
   727    730         }
   728    731         if( zData[0] ){
   729    732           Blob content;
   730    733           @ </tr><tr style="background-color:%h(zBg)"><td colspan=%d(pState->nCol)>
   731    734           blob_init(&content, zData, -1);
   732    735           wiki_convert(&content, 0, 0);
   733    736           blob_reset(&content);
   734    737         }
   735    738       }else if( azName[i][0]=='#' ){
   736    739         zTid = zData;
   737         -      if( g.perm.History ){
   738         -        @ <td valign="top"><a href="tktview?name=%h(zData)">%h(zData)</a></td>
   739         -      }else{
   740         -        @ <td valign="top">%h(zData)</td>
   741         -      }
          740  +      @ <td valign="top">%z(href("%R/tktview?name=%h",zData))%h(zData)</a></td>
   742    741       }else if( zData[0]==0 ){
   743    742         @ <td valign="top">&nbsp;</td>
   744    743       }else{
   745    744         @ <td valign="top">
   746    745         @ %h(zData)
   747    746         @ </td>
   748    747       }
   749    748     }
   750    749     if( zTid && g.perm.Write ){
   751         -    @ <td valign="top"><a href="tktedit/%h(zTid)">edit</a></td>
          750  +    @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
   752    751     }
   753    752     @ </tr>
   754    753     return 0;
   755    754   }
   756    755   
   757    756   /*
   758    757   ** Output the text given in the argument.  Convert tabs and newlines into
................................................................................
   947    946   
   948    947     count = 0;
   949    948     if( !tabs ){
   950    949       struct GenerateHTML sState;
   951    950   
   952    951       db_multi_exec("PRAGMA empty_result_callbacks=ON");
   953    952       style_submenu_element("Raw", "Raw", 
   954         -      "rptview?tablist=1&amp;%h", PD("QUERY_STRING",""));
          953  +      "rptview?tablist=1&%h", PD("QUERY_STRING",""));
   955    954       if( g.perm.Admin 
   956    955          || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
   957    956         style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn);
   958    957       }
   959    958       if( g.perm.TktFmt ){
   960    959         style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
   961    960       }

Changes to src/setup.c.

   522    522     }
   523    523     @    <input type="checkbox" name="aa"%s(oaa) />%s(B('a'))Admin<br />
   524    524     @    <input type="checkbox" name="ad"%s(oad) />%s(B('d'))Delete<br />
   525    525     @    <input type="checkbox" name="ae"%s(oae) />%s(B('e'))Email<br />
   526    526     @    <input type="checkbox" name="ap"%s(oap) />%s(B('p'))Password<br />
   527    527     @    <input type="checkbox" name="ai"%s(oai) />%s(B('i'))Check-In<br />
   528    528     @    <input type="checkbox" name="ao"%s(oao) />%s(B('o'))Check-Out<br />
   529         -  @    <input type="checkbox" name="ah"%s(oah) />%s(B('h'))History<br />
          529  +  @    <input type="checkbox" name="ah"%s(oah) />%s(B('h'))Hyperlinks<br />
   530    530     @    <input type="checkbox" name="au"%s(oau) />%s(B('u'))Reader<br />
   531    531     @    <input type="checkbox" name="av"%s(oav) />%s(B('v'))Developer<br />
   532    532     @    <input type="checkbox" name="ag"%s(oag) />%s(B('g'))Clone<br />
   533    533     @    <input type="checkbox" name="aj"%s(oaj) />%s(B('j'))Read Wiki<br />
   534    534     @    <input type="checkbox" name="af"%s(oaf) />%s(B('f'))New Wiki<br />
   535    535     @    <input type="checkbox" name="am"%s(oam) />%s(B('m'))Append Wiki<br />
   536    536     @    <input type="checkbox" name="ak"%s(oak) />%s(B('k'))Write Wiki<br />
................................................................................
   623    623     @ by anonymous users.  This capability is intended for deletion of spam. 
   624    624     @ The delete capability is only in effect for 24 hours after the item
   625    625     @ is first posted.  The <span class="usertype">Setup</span> user can
   626    626     @ delete anything at any time.
   627    627     @ </p></li>
   628    628     @
   629    629     @ <li><p>
   630         -  @ The <span class="capability">History</span> privilege allows a user
          630  +  @ The <span class="capability">Hyperlinks</span> privilege allows a user
   631    631     @ to see most hyperlinks. This is recommended ON for most logged-in users
   632    632     @ but OFF for user "nobody" to avoid problems with spiders trying to walk
   633         -  @ every historical version of every baseline and file.
          633  +  @ every diff and annotation of every historical check-in and file.
   634    634     @ </p></li>
   635    635     @
   636    636     @ <li><p>
   637    637     @ The <span class="capability">Zip</span> privilege allows a user to
   638    638     @ see the "download as ZIP"
   639    639     @ hyperlink and permits access to the <tt>/zip</tt> page.  This allows
   640    640     @ users to download ZIP archives without granting other rights like
   641    641     @ <span class="capability">Read</span> or
   642         -  @ <span class="capability">History</span>.  This privilege is recommended for
   643         -  @ user <span class="usertype">nobody</span> so that automatic package
          642  +  @ <span class="capability">Hyperlink</span>.  The "z" privilege is recommended
          643  +  @ for user <span class="usertype">nobody</span> so that automatic package
   644    644     @ downloaders can obtain the sources without going through the login
   645    645     @ procedure.
   646    646     @ </p></li>
   647    647     @
   648    648     @ <li><p>
   649    649     @ The <span class="capability">Check-in</span> privilege allows remote
   650    650     @ users to "push". The <span class="capability">Check-out</span> privilege
................................................................................
   702    702     @ capabilities of the <span class="usertype">nobody</span> user are
   703    703     @ inherited by all users, regardless of whether or not they are logged in.
   704    704     @ To disable universal access to the repository, make sure no user named 
   705    705     @ <span class="usertype">nobody</span> exists or that the
   706    706     @ <span class="usertype">nobody</span> user has no capabilities
   707    707     @ enabled. The password for <span class="usertype">nobody</span> is ignore.
   708    708     @ To avoid problems with spiders overloading the server, it is recommended
   709         -  @ that the <span class="capability">h</span> (History) capability be turned 
   710         -  @ off for the <span class="usertype">nobody</span> user.
          709  +  @ that the <span class="capability">h</span> (Hyperlinks) capability be
          710  +  @ turned off for the <span class="usertype">nobody</span> user.
   711    711     @ </p></li>
   712    712     @
   713    713     @ <li><p>
   714    714     @ Login is required for user <span class="usertype">anonymous</span> but the
   715    715     @ password is displayed on the login screen beside the password entry box
   716    716     @ so anybody who can read should be able to login as anonymous.
   717    717     @ On the other hand, spiders and web-crawlers will typically not
................................................................................
   889    889     @ <p>Fossil tries to limit out-bound sync, clone, and pull packets
   890    890     @ to this many bytes, uncompressed.  If the client requires more data
   891    891     @ than this, then the client will issue multiple HTTP requests.
   892    892     @ Values below 1 million are not recommended.  5 million is a
   893    893     @ reasonable number.</p>
   894    894   
   895    895     @ <hr />
   896         -  onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent",
   897         -                  "auto-enable-hyperlinks", "autohyperlink", 1);
          896  +  onoff_attribute(
          897  +      "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
          898  +      "auto-enable-hyperlinks", "autohyperlink", 1);
   898    899     @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
   899         -  @ including user "nobody", as long as the User-Agent string in the HTTP header
   900         -  @ indicates that the request is coming from an actual human being and not a
   901         -  @ a robot or script.  Note:  Bots can specify whatever User-Agent string they
   902         -  @ that want.  So a bot that wants to impersonate a human can easily do so.
   903         -  @ Hence, this technique does not necessarily exclude malicious bots.
   904         -  @ </p>
          900  +  @ including user "nobody", as long as (1) the User-Agent string in the
          901  +  @ HTTP header indicates that the request is coming from an actual human
          902  +  @ being and not a a robot or spider and (2) the user agent is able to
          903  +  @ run Javascript in order to set the href= attribute of hyperlinks.  Bots
          904  +  @ and spiders can specify whatever User-Agent string they that want and
          905  +  @ they can run javascript just like browsers.  But most bots don't go to
          906  +  @ that much trouble so this is normally an effective defense.</p>
          907  +  @
          908  +  @ <p>You do not normally want a bot to walk your entire repository because
          909  +  @ if it does, your server will end up computing diffs and annotations for
          910  +  @ every historical version of every file and creating ZIPs and tarballs of
          911  +  @ every historical check-in, which can use a lot of CPU and bandwidth
          912  +  @ even for relatively small projects.</p>
   905    913   
   906    914     @ <hr />
   907    915     entry_attribute("Public pages", 30, "public-pages",
   908    916                     "pubpage", "");
   909    917     @ <p>A comma-separated list of glob patterns for pages that are accessible
   910    918     @ without needing a login and using the privileges given by the
   911    919     @ "Default privileges" setting below.  Example use case: Set this field

Changes to src/style.c.

    43     43   static int headerHasBeenGenerated = 0;
    44     44   
    45     45   /*
    46     46   ** remember, if a sidebox was used
    47     47   */
    48     48   static int sideboxUsed = 0;
    49     49   
           50  +
           51  +/*
           52  +** List of hyperlinks that need to be resolved by javascript in
           53  +** the footer.
           54  +*/
           55  +char **aHref = 0;
           56  +int nHref = 0;
           57  +int nHrefAlloc = 0;
           58  +
           59  +/*
           60  +** Generate and return a anchor tag like this:
           61  +**
           62  +**        <a href="URL">
           63  +**  or    <a id="ID">
           64  +**
           65  +** The form of the anchor tag is determined by the g.javascriptHyperlink
           66  +** variable.  The href="URL" form is used if g.javascriptHyperlink is false.
           67  +** If g.javascriptHyperlink is true then the
           68  +** id="ID" form is used and javascript is generated in the footer to cause
           69  +** href values to be inserted after the page has loaded.  If 
           70  +** g.perm.History is false, then the <a id="ID"> form is still
           71  +** generated but the javascript is not generated so the links never
           72  +** activate.
           73  +**
           74  +** Filling in the href="URL" using javascript is a defense against bots.
           75  +**
           76  +** The name of this routine is deliberately kept short so that can be
           77  +** easily used within @-lines.  Example:
           78  +**
           79  +**      @ %z(href("%R/artifact/%s",zUuid))%h(zFN)</a>
           80  +**
           81  +** Note %z format.  The string returned by this function is always
           82  +** obtained from fossil_malloc() so rendering it with %z will reclaim
           83  +** that memory space.
           84  +**
           85  +** There are two versions of this routine: href() does a plain hyperlink
           86  +** and xhref() adds extra attribute text.
           87  +*/
           88  +char *xhref(const char *zExtra, const char *zFormat, ...){
           89  +  char *zUrl;
           90  +  va_list ap;
           91  +  va_start(ap, zFormat);
           92  +  zUrl = vmprintf(zFormat, ap);
           93  +  va_end(ap);
           94  +  if( g.perm.Hyperlink && !g.javascriptHyperlink ){
           95  +    return mprintf("<a %s href=\"%z\">", zExtra, zUrl);
           96  +  }
           97  +  if( nHref>=nHrefAlloc ){
           98  +    nHrefAlloc = nHrefAlloc*2 + 10;
           99  +    aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0]));
          100  +  }
          101  +  aHref[nHref++] = zUrl;
          102  +  return mprintf("<a %s id=%d>", zExtra, nHref);
          103  +}
          104  +char *href(const char *zFormat, ...){
          105  +  char *zUrl;
          106  +  va_list ap;
          107  +  va_start(ap, zFormat);
          108  +  zUrl = vmprintf(zFormat, ap);
          109  +  va_end(ap);
          110  +  if( g.perm.Hyperlink && !g.javascriptHyperlink ){
          111  +    return mprintf("<a href=\"%z\">", zUrl);
          112  +  }
          113  +  if( nHref>=nHrefAlloc ){
          114  +    nHrefAlloc = nHrefAlloc*2 + 10;
          115  +    aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0]));
          116  +  }
          117  +  aHref[nHref++] = zUrl;
          118  +  return mprintf("<a id=%d>", nHref);
          119  +}
          120  +
          121  +/*
          122  +** Generate javascript that will set the href= attribute on all anchors.
          123  +*/
          124  +void style_resolve_href(void){
          125  +  int i;
          126  +  if( !g.perm.Hyperlink || !g.javascriptHyperlink || nHref==0 ) return;
          127  +  @ <script>
          128  +  for(i=0; i<nHref; i++){
          129  +    @ document.getElementById(%d(i+1)).href="%s(aHref[i])";
          130  +  }
          131  +  @ </script>
          132  +}
          133  +
    50    134   /*
    51    135   ** Add a new element to the submenu
    52    136   */
    53    137   void style_submenu_element(
    54    138     const char *zLabel,
    55    139     const char *zTitle,
    56    140     const char *zLink,
................................................................................
   162    246     
   163    247     /* Render trace log if TH1 tracing is enabled. */
   164    248     if( g.thTrace ){
   165    249       cgi_append_content("<span class=\"thTrace\"><hr />\n", -1);
   166    250       cgi_append_content(blob_str(&g.thLog), blob_size(&g.thLog));
   167    251       cgi_append_content("</span>\n", -1);
   168    252     }
          253  +
          254  +  /* Set the href= field on hyperlinks */
          255  +  style_resolve_href();
   169    256   }
   170    257   
   171    258   /*
   172    259   ** Begin a side-box on the right-hand side of a page.  The title and
   173    260   ** the width of the box are given as arguments.  The width is usually
   174    261   ** a percentage of total screen width.
   175    262   */

Changes to src/tag.c.

   546    546       "                 AND tagtype=1)"
   547    547       " AND tagname GLOB 'sym-*'"
   548    548       " ORDER BY tagname"
   549    549     );
   550    550     @ <ul>
   551    551     while( db_step(&q)==SQLITE_ROW ){
   552    552       const char *zName = db_column_text(&q, 0);
   553         -    if( g.perm.History ){
   554         -      @ <li><a class="tagLink" href="%s(g.zTop)/timeline?t=%T(zName)">
          553  +    if( g.perm.Hyperlink ){
          554  +      @ <li>%z(xhref("class='taglink'","%R/timeline?t=%T",zName))
   555    555         @ %h(zName)</a></li>
   556    556       }else{
   557    557         @ <li><span class="tagDsp">%h(zName)</span></li>
   558    558       }
   559    559     }
   560    560     @ </ul>
   561    561     db_finalize(&q);

Changes to src/timeline.c.

    45     45   
    46     46   /*
    47     47   ** Generate a hyperlink to a version.
    48     48   */
    49     49   void hyperlink_to_uuid(const char *zUuid){
    50     50     char z[UUID_SIZE+1];
    51     51     shorten_uuid(z, zUuid);
    52         -  if( g.perm.History ){
    53         -    @ <a class="timelineHistLink" href="%s(g.zTop)/info/%s(z)">[%s(z)]</a>
           52  +  if( g.perm.Hyperlink ){
           53  +    @ %z(xhref("class='timelineHistLink'","%R/info/%s",z))[%s(z)]</a>
    54     54     }else{
    55     55       @ <span class="timelineHistDsp">[%s(z)]</span>
    56     56     }
    57     57   }
    58     58   
    59     59   /*
    60     60   ** Generate a hyperlink to a diff between two versions.
    61     61   */
    62     62   void hyperlink_to_diff(const char *zV1, const char *zV2){
    63         -  if( g.perm.History ){
           63  +  if( g.perm.Hyperlink ){
    64     64       if( zV2==0 ){
    65         -      @ <a href="%s(g.zTop)/diff?v2=%s(zV1)">[diff]</a>
           65  +      @ %z(href("%R/diff?v2=%s",zV1))[diff]</a>
    66     66       }else{
    67         -      @ <a href="%s(g.zTop)/diff?v1=%s(zV1)&amp;v2=%s(zV2)">[diff]</a>
           67  +      @ %z(href("%R/diff?v1=%s&v2=%s",zV1,zV2))[diff]</a>
    68     68       }
    69     69     }
    70     70   }
    71     71   
    72     72   /*
    73     73   ** Generate a hyperlink to a date & time.
    74     74   */
    75     75   void hyperlink_to_date(const char *zDate, const char *zSuffix){
    76     76     if( zSuffix==0 ) zSuffix = "";
    77         -  if( g.perm.History ){
    78         -    @ <a href="%s(g.zTop)/timeline?c=%T(zDate)">%s(zDate)</a>%s(zSuffix)
           77  +  if( g.perm.Hyperlink ){
           78  +    @ %z(href("%R/timeline?c=%T",zDate))%s(zDate)</a>%s(zSuffix)
    79     79     }else{
    80     80       @ %s(zDate)%s(zSuffix)
    81     81     }
    82     82   }
    83     83   
    84     84   /*
    85     85   ** Generate a hyperlink to a user.  This will link to a timeline showing
    86     86   ** events by that user.  If the date+time is specified, then the timeline
    87     87   ** is centered on that date+time.
    88     88   */
    89     89   void hyperlink_to_user(const char *zU, const char *zD, const char *zSuf){
    90     90     if( zSuf==0 ) zSuf = "";
    91         -  if( g.perm.History ){
           91  +  if( g.perm.Hyperlink ){
    92     92       if( zD && zD[0] ){
    93         -      @ <a href="%s(g.zTop)/timeline?c=%T(zD)&amp;u=%T(zU)">%h(zU)</a>%s(zSuf)
           93  +      @ %z(href("%R/timeline?c=%T&u=%T",zD,zU))%h(zU)</a>%s(zSuf)
    94     94       }else{
    95         -      @ <a href="%s(g.zTop)/timeline?u=%T(zU)">%h(zU)</a>%s(zSuf)
           95  +      @ %z(href("%R/timeline?u=%T",zU))%h(zU)</a>%s(zSuf)
    96     96       }
    97     97     }else{
    98     98       @ %s(zU)
    99     99     }
   100    100   }
   101    101   
   102    102   /*
................................................................................
   351    351       }
   352    352       blob_reset(&comment);
   353    353   
   354    354       /* Generate the "user: USERNAME" at the end of the comment, together
   355    355       ** with a hyperlink to another timeline for that user.
   356    356       */
   357    357       if( zTagList && zTagList[0]==0 ) zTagList = 0;
   358         -    if( g.perm.History && fossil_strcmp(zUser, zThisUser)!=0 ){
   359         -      char *zLink = mprintf("%s/timeline?u=%h&c=%t&nd",
   360         -                            g.zTop, zUser, zDate);
   361         -      @ (user: <a href="%s(zLink)">%h(zUser)</a>%s(zTagList?",":"\051")
   362         -      fossil_free(zLink);
          358  +    if( g.perm.Hyperlink && fossil_strcmp(zUser, zThisUser)!=0 ){
          359  +      char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zUser, zDate);
          360  +      @ (user: %z(href("%z",zLink))%h(zUser)</a>%s(zTagList?",":"\051")
   363    361       }else{
   364    362         @ (user: %h(zUser)%s(zTagList?",":"\051")
   365    363       }
   366    364   
   367    365       /* Generate a "detail" link for tags. */
   368         -    if( zType[0]=='g' && g.perm.History ){
   369         -      @ [<a href="%s(g.zTop)/info/%S(zUuid)">details</a>]
          366  +    if( zType[0]=='g' && g.perm.Hyperlink ){
          367  +      @ [%z(href("%R/info/%S",zUuid))details</a>]
   370    368       }
   371    369   
   372    370       /* Generate the "tags: TAGLIST" at the end of the comment, together
   373    371       ** with hyperlinks to the tag list.
   374    372       */
   375    373       if( zTagList ){
   376         -      if( g.perm.History ){
          374  +      if( g.perm.Hyperlink ){
   377    375           int i;
   378    376           const char *z = zTagList;
   379    377           Blob links;
   380    378           blob_zero(&links);
   381    379           while( z && z[0] ){
   382    380             for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
   383    381             if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
   384    382               blob_appendf(&links,
   385         -                  "<a href=\"%s/timeline?r=%#t&nd&c=%s\">%#h</a>%.2s",
   386         -                  g.zTop, i, z, zDate, i, z, &z[i]
          383  +                  "%z%#h</a>%.2s",
          384  +                  href("%R/timeline?r=%#t&nd&c=%s",i,z,zDate), i,z, &z[i]
   387    385               );
   388    386             }else{
   389    387               blob_appendf(&links, "%#h", i+2, z);
   390    388             }
   391    389             if( z[i]==0 ) break;
   392    390             z += i+2;
   393    391           }
................................................................................
   401    399   
   402    400       /* Generate extra hyperlinks at the end of the comment */
   403    401       if( xExtra ){
   404    402         xExtra(rid);
   405    403       }
   406    404   
   407    405       /* Generate the file-change list if requested */
   408         -    if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.History ){
          406  +    if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.Hyperlink ){
   409    407         int inUl = 0;
   410    408         if( !fchngQueryInit ){
   411    409           db_prepare(&fchngQuery, 
   412    410             "SELECT (pid==0) AS isnew,"
   413    411             "       (fid==0) AS isdel,"
   414    412             "       (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
   415    413             "       (SELECT uuid FROM blob WHERE rid=fid),"
................................................................................
   431    429           const char *zNew = db_column_text(&fchngQuery, 3);
   432    430           if( !inUl ){
   433    431             @ <ul class="filelist">
   434    432             inUl = 1;
   435    433           }
   436    434           if( isNew ){
   437    435             @ <li> %h(zFilename) (new file) &nbsp;
   438         -          @ <a href="%s(g.zTop)/artifact/%S(zNew)"
   439         -          @ target="diffwindow">[view]</a></li>
          436  +          @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew))
          437  +          @ [view]</a></li>
   440    438           }else if( isDel ){
   441    439             @ <li> %h(zFilename) (deleted)</li>
   442    440           }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
   443    441             @ <li> %h(zOldName) &rarr; %h(zFilename)
   444         -          @ <a href="%s(g.zTop)/artifact/%S(zNew)"
   445         -          @ target="diffwindow">[view]</a></li>
          442  +          @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew))
          443  +          @ [view]</a></li>
   446    444           }else{
   447    445             if( zOldName!=0 ){
   448    446               @ <li> %h(zOldName) &rarr; %h(zFilename)
   449    447             }else{
   450    448               @ <li> %h(zFilename) &nbsp;
   451    449             }
   452         -          @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&v2=%S(zNew)"
   453         -          @ target="diffwindow">[diff]</a></li>
          450  +          @ %z(xhref("target='diffwindow'","%R/fdiff?v1=%S&v2=%S",zOld,zNew))
          451  +          @ [diff]</a></li>
   454    452           }
   455    453         }
   456    454         db_reset(&fchngQuery);
   457    455         if( inUl ){
   458    456           @ </ul>
   459    457         }
   460    458       }
................................................................................
   972    970       while( p ){
   973    971         blob_appendf(&sql, ",%d", p->rid);
   974    972         p = p->u.pTo;
   975    973       }
   976    974       blob_append(&sql, ")", -1);
   977    975       path_reset();
   978    976       blob_append(&desc, "All nodes on the path from ", -1);
   979         -    if( g.perm.History ){
   980         -      blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>",  g.zTop,zFrom,zFrom);
   981         -    }else{
   982         -      blob_appendf(&desc, "[%h]", zFrom);
   983         -    }
          977  +    blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
   984    978       blob_append(&desc, " and ", -1);
   985         -    if( g.perm.History ){
   986         -      blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>.",  g.zTop, zTo, zTo);
   987         -    }else{
   988         -      blob_appendf(&desc, "[%h].", zTo);
   989         -    }
          979  +    blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h",zTo), zTo);
   990    980       tmFlags |= TIMELINE_DISJOINT;
   991    981       db_multi_exec("%s", blob_str(&sql));
   992    982     }else if( (p_rid || d_rid) && g.perm.Read ){
   993    983       /* If p= or d= is present, ignore all other parameters other than n= */
   994    984       char *zUuid;
   995    985       int np, nd;
   996    986   
................................................................................
  1019   1009         if( np>0 ){
  1020   1010           if( nd>0 ) blob_appendf(&desc, " and ");
  1021   1011           blob_appendf(&desc, "%d ancestors", np);
  1022   1012           db_multi_exec("%s", blob_str(&sql));
  1023   1013         }
  1024   1014         if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
  1025   1015       }
  1026         -    if( g.perm.History ){
  1027         -      blob_appendf(&desc, " of <a href='%s/info/%s'>[%.10s]</a>",
  1028         -                   g.zTop, zUuid, zUuid);
  1029         -    }else{
  1030         -      blob_appendf(&desc, " of check-in [%.10s]", zUuid);
  1031         -    }
         1016  +    blob_appendf(&desc, " of %z[%.10s]</a>",
         1017  +                   href("%R/info/%s", zUuid), zUuid);
  1032   1018     }else if( f_rid && g.perm.Read ){
  1033   1019       /* If f= is present, ignore all other parameters other than n= */
  1034   1020       char *zUuid;
  1035   1021       db_multi_exec(
  1036   1022          "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
  1037   1023          "INSERT INTO ok VALUES(%d);"
  1038   1024          "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
................................................................................
  1040   1026          f_rid, f_rid, f_rid
  1041   1027       );
  1042   1028       blob_appendf(&sql, " AND event.objid IN ok");
  1043   1029       db_multi_exec("%s", blob_str(&sql));
  1044   1030       if( useDividers ) timeline_add_dividers(0, f_rid);
  1045   1031       blob_appendf(&desc, "Parents and children of check-in ");
  1046   1032       zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
  1047         -    if( g.perm.History ){
  1048         -      blob_appendf(&desc, "<a href='%s/info/%s'>[%.10s]</a>",
  1049         -                   g.zTop, zUuid, zUuid);
  1050         -    }else{
  1051         -      blob_appendf(&desc, "[%.10s]", zUuid);
  1052         -    }
         1033  +    blob_appendf(&desc, "%z[%.10s]</a>", href("%R/info/%s", zUuid), zUuid);
  1053   1034     }else{
  1054   1035       /* Otherwise, a timeline based on a span of time */
  1055   1036       int n;
  1056   1037       const char *zEType = "timeline item";
  1057   1038       char *zDate;
  1058   1039       char *zNEntry = mprintf("%d", nEntry);
  1059   1040       url_add_parameter(&url, "n", zNEntry);
................................................................................
  1217   1198         blob_appendf(&desc, " occurring on or before %h.<br />", zBefore);
  1218   1199       }else if( zCirca ){
  1219   1200         blob_appendf(&desc, " occurring around %h.<br />", zCirca);
  1220   1201       }
  1221   1202       if( zSearch ){
  1222   1203         blob_appendf(&desc, " matching \"%h\"", zSearch);
  1223   1204       }
  1224         -    if( g.perm.History ){
         1205  +    if( g.perm.Hyperlink ){
  1225   1206         if( zAfter || n==nEntry ){
  1226   1207           zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
  1227   1208           timeline_submenu(&url, "Older", "b", zDate, "a");
  1228   1209           free(zDate);
  1229   1210         }
  1230   1211         if( zBefore || (zAfter && n==nEntry) ){
  1231   1212           zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
................................................................................
  1621   1602   /*
  1622   1603   ** WEBPAGE: test_timewarps
  1623   1604   */
  1624   1605   void test_timewarp_page(void){
  1625   1606     Stmt q;
  1626   1607   
  1627   1608     login_check_credentials();
  1628         -  if( !g.perm.Read || !g.perm.History ){ login_needed(); return; }
         1609  +  if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; }
  1629   1610     style_header("Instances of timewarp");
  1630   1611     @ <ul>
  1631   1612     db_prepare(&q,
  1632   1613        "SELECT blob.uuid "
  1633   1614        "  FROM plink p, plink c, blob"
  1634   1615        " WHERE p.cid=c.pid  AND p.mtime>c.mtime"
  1635   1616        "   AND blob.rid=c.cid"
  1636   1617     );
  1637   1618     while( db_step(&q)==SQLITE_ROW ){
  1638   1619       const char *zUuid = db_column_text(&q, 0);
  1639   1620       @ <li>
  1640         -    @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&amp;d=%S(zUuid)">%S(zUuid)</a>
         1621  +    @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&d=%S(zUuid)">%S(zUuid)</a>
  1641   1622     }
  1642   1623     db_finalize(&q);
  1643   1624     style_footer();
  1644   1625   }

Changes to src/tkt.c.

   303    303   
   304    304     login_check_credentials();
   305    305     if( !g.perm.RdTkt ){ login_needed(); return; }
   306    306     if( g.perm.WrTkt || g.perm.ApndTkt ){
   307    307       style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
   308    308           g.zTop, PD("name",""));
   309    309     }
   310         -  if( g.perm.History ){
          310  +  if( g.perm.Hyperlink ){
   311    311       style_submenu_element("History", "History Of This Ticket", 
   312    312           "%s/tkthistory/%T", g.zTop, zUuid);
   313    313       style_submenu_element("Timeline", "Timeline Of This Ticket", 
   314    314           "%s/tkttimeline/%T", g.zTop, zUuid);
   315    315       style_submenu_element("Check-ins", "Check-ins Of This Ticket", 
   316    316           "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
   317    317     }
   318    318     if( g.perm.NewTkt ){
   319    319       style_submenu_element("New Ticket", "Create a new ticket",
   320    320           "%s/tktnew", g.zTop);
   321    321     }
   322    322     if( g.perm.ApndTkt && g.perm.Attach ){
   323    323       style_submenu_element("Attach", "Add An Attachment",
   324         -        "%s/attachadd?tkt=%T&amp;from=%s/tktview/%t",
          324  +        "%s/attachadd?tkt=%T&from=%s/tktview/%t",
   325    325           g.zTop, zUuid, g.zTop, zUuid);
   326    326     }
   327    327     style_header("View Ticket");
   328    328     if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
   329    329     ticket_init();
   330    330     initializeVariablesFromDb();
   331    331     zScript = ticket_viewpage_code();
................................................................................
   351    351         const char *zUser = db_column_text(&q, 2);
   352    352         if( cnt==0 ){
   353    353           @ <hr /><h2>Attachments:</h2>
   354    354           @ <ul>
   355    355         }
   356    356         cnt++;
   357    357         @ <li>
   358         -      if( g.perm.Read && g.perm.History ){
   359         -        @ <a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&amp;file=%t(zFile)">
          358  +      if( g.perm.Read && g.perm.Hyperlink ){
          359  +        @ %z(href("%R/attachview?tkt=%s&file=%t",zFullName,zFile))
   360    360           @ %h(zFile)</a>
   361    361         }else{
   362    362           @ %h(zFile)
   363    363         }
   364    364         @ added by %h(zUser) on
   365    365         hyperlink_to_date(zDate, ".");
   366    366         if( g.perm.WrTkt && g.perm.Attach ){
   367         -        @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&amp;file=%t(zFile)&amp;from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>]
          367  +        @ [%z(href("%R/attachdelete?tkt=%s&file=%t&from=%R/tktview%%3fname=%s",zFullName,zFile,zFullName))delete</a>]
   368    368         }
   369    369         @ </li>
   370    370       }
   371    371       if( cnt ){
   372    372         @ </ul>
   373    373       }
   374    374       db_finalize(&q);
................................................................................
   645    645       }
   646    646     }
   647    647     return 0;
   648    648   }
   649    649   
   650    650   /*
   651    651   ** WEBPAGE: tkttimeline
   652         -** URL: /tkttimeline?name=TICKETUUID&amp;y=TYPE
          652  +** URL: /tkttimeline?name=TICKETUUID&y=TYPE
   653    653   **
   654    654   ** Show the change history for a single ticket in timeline format.
   655    655   */
   656    656   void tkttimeline_page(void){
   657    657     Stmt q;
   658    658     char *zTitle;
   659    659     char *zSQL;
................................................................................
   660    660     const char *zUuid;
   661    661     char *zFullUuid;
   662    662     int tagid;
   663    663     char zGlobPattern[50];
   664    664     const char *zType;
   665    665   
   666    666     login_check_credentials();
   667         -  if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; }
          667  +  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
   668    668     zUuid = PD("name","");
   669    669     zType = PD("y","a");
   670    670     if( zType[0]!='c' ){
   671    671       style_submenu_element("Check-ins", "Check-ins",
   672         -       "%s/tkttimeline?name=%T&amp;y=ci", g.zTop, zUuid);
          672  +       "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
   673    673     }else{
   674    674       style_submenu_element("Timeline", "Timeline",
   675    675          "%s/tkttimeline?name=%T", g.zTop, zUuid);
   676    676     }
   677    677     style_submenu_element("History", "History",
   678    678       "%s/tkthistory/%s", g.zTop, zUuid);
   679    679     style_submenu_element("Status", "Status",
................................................................................
   734    734   void tkthistory_page(void){
   735    735     Stmt q;
   736    736     char *zTitle;
   737    737     const char *zUuid;
   738    738     int tagid;
   739    739   
   740    740     login_check_credentials();
   741         -  if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; }
          741  +  if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; }
   742    742     zUuid = PD("name","");
   743    743     zTitle = mprintf("History Of Ticket %h", zUuid);
   744    744     style_submenu_element("Status", "Status",
   745    745       "%s/info/%s", g.zTop, zUuid);
   746    746     style_submenu_element("Check-ins", "Check-ins",
   747         -    "%s/tkttimeline?name=%s&amp;y=ci", g.zTop, zUuid);
          747  +    "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
   748    748     style_submenu_element("Timeline", "Timeline",
   749    749       "%s/tkttimeline?name=%s", g.zTop, zUuid);
   750    750     style_header(zTitle);
   751    751     free(zTitle);
   752    752   
   753    753     tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
   754    754     if( tagid==0 ){
................................................................................
   784    784         if( zSrc==0 || zSrc[0]==0 ){
   785    785           @ 
   786    786           @ <p>Delete attachment "%h(zFile)"
   787    787         }else{
   788    788           @ 
   789    789           @ <p>Add attachment "%h(zFile)"
   790    790         }
   791         -      @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
          791  +      @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
   792    792         @ (rid %d(rid)) by
   793    793         hyperlink_to_user(zUser,zDate," on");
   794    794         hyperlink_to_date(zDate, ".</p>");
   795    795       }else{
   796    796         pTicket = manifest_get(rid, CFTYPE_TICKET);
   797    797         if( pTicket ){
   798    798           @
   799    799           @ <p>Ticket change
   800         -        @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
          800  +        @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
   801    801           @ (rid %d(rid)) by
   802    802           hyperlink_to_user(pTicket->zUser,zDate," on");
   803    803           hyperlink_to_date(zDate, ":");
   804    804           @ </p>
   805    805           ticket_output_change_artifact(pTicket);
   806    806         }
   807    807         manifest_destroy(pTicket);

Changes to src/wiki.c.

   102    102       g.isHome = 1;
   103    103       wiki_page();
   104    104       return;
   105    105     }
   106    106     style_header("Home");
   107    107     @ <p>This is a stub home-page for the project.
   108    108     @ To fill in this page, first go to
   109         -  @ <a href="%s(g.zTop)/setup_config">setup/config</a>
          109  +  @ %z(href("%R/setup_config"))setup/config</a>
   110    110     @ and establish a "Project Name".  Then create a
   111    111     @ wiki page with that name.  The content of that wiki page
   112    112     @ will be displayed in place of this message.</p>
   113    113     style_footer();
   114    114   }
   115    115   
   116    116   /*
................................................................................
   140    140     if( !g.perm.RdWiki ){ login_needed(); return; }
   141    141     zPageName = P("name");
   142    142     if( zPageName==0 ){
   143    143       style_header("Wiki");
   144    144       @ <ul>
   145    145       { char *zHomePageName = db_get("project-name",0);
   146    146         if( zHomePageName ){
   147         -        @ <li> <a href="%s(g.zTop)/wiki?name=%t(zHomePageName)">
          147  +        @ <li> %z(href("%R/wiki?name=%t",zHomePageName))
   148    148           @      %h(zHomePageName)</a> wiki home page.</li>
   149    149         }
   150    150       }
   151         -    @ <li> <a href="%s(g.zTop)/timeline?y=w">Recent changes</a> to wiki
   152         -    @      pages. </li>
   153         -    @ <li> <a href="%s(g.zTop)/wiki_rules">Formatting rules</a> for 
   154         -    @      wiki.</li>
   155         -    @ <li> Use the <a href="%s(g.zTop)/wiki?name=Sandbox">Sandbox</a>
          151  +    @ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li>
          152  +    @ <li> %z(href("%R/wiki_rules"))Formatting rules</a> for wiki.</li>
          153  +    @ <li> Use the %z(href("%R/wiki?name=Sandbox"))Sandbox</a>
   156    154       @      to experiment.</li>
   157    155       if( g.perm.NewWiki ){
   158         -      @ <li>  Create a <a href="%s(g.zTop)/wikinew">new wiki page</a>.</li>
          156  +      @ <li>  Create a %z(href("%R/wikinew"))new wiki page</a>.</li>
   159    157         if( g.perm.Write ){
   160         -        @ <li>   Create a <a href="%s(g.zTop)/eventedit">new event</a>.</li>
          158  +        @ <li>   Create a %z(href("%R/eventedit"))new event</a>.</li>
   161    159         }
   162    160       }
   163         -    @ <li> <a href="%s(g.zTop)/wcontent">List of All Wiki Pages</a>
          161  +    @ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a>
   164    162       @      available on this server.</li>
   165    163       @ <li> <form method="get" action="%s(g.zTop)/wfind"><div>
   166    164       @     Search wiki titles: <input type="text" name="title"/>
   167    165       @  &nbsp; <input type="submit" /></div></form>
   168    166       @ </li>
   169    167       @ </ul>
   170    168       style_footer();
................................................................................
   190    188     if( !g.isHome ){
   191    189       if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
   192    190         style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
   193    191              g.zTop, zPageName);
   194    192       }
   195    193       if( rid && g.perm.ApndWiki && g.perm.Attach ){
   196    194         style_submenu_element("Attach", "Add An Attachment",
   197         -           "%s/attachadd?page=%T&amp;from=%s/wiki%%3fname=%T",
          195  +           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
   198    196              g.zTop, zPageName, g.zTop, zPageName);
   199    197       }
   200    198       if( rid && g.perm.ApndWiki ){
   201    199         style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
   202    200              g.zTop, zPageName);
   203    201       }
   204         -    if( g.perm.History ){
          202  +    if( g.perm.Hyperlink ){
   205    203         style_submenu_element("History", "History", "%s/whistory?name=%T",
   206    204              g.zTop, zPageName);
   207    205       }
   208    206     }
   209    207     style_header(zPageName);
   210    208     blob_init(&wiki, zBody, -1);
   211    209     wiki_convert(&wiki, 0, 0);
................................................................................
   223    221       const char *zUser = db_column_text(&q, 2);
   224    222       if( cnt==0 ){
   225    223         @ <hr /><h2>Attachments:</h2>
   226    224         @ <ul>
   227    225       }
   228    226       cnt++;
   229    227       @ <li>
   230         -    if( g.perm.History && g.perm.Read ){
   231         -      @ <a href="%s(g.zTop)/attachview?page=%s(zPageName)&amp;file=%t(zFile)">
          228  +    if( g.perm.Hyperlink && g.perm.Read ){
          229  +      @ %z(href("%R/attachview?page=%T&file=%t",zPageName,zFile))
   232    230         @ %h(zFile)</a>
   233    231       }else{
   234    232         @ %h(zFile)
   235    233       }
   236    234       @ added by %h(zUser) on
   237    235       hyperlink_to_date(zDate, ".");
   238    236       if( g.perm.WrWiki && g.perm.Attach ){
   239         -      @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&amp;file=%t(zFile)&amp;from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>]
          237  +      @ [%z(href("%R/attachdelete?page=%t&file=%t&from=%R/wiki%%3fname=%f",zPageName,zFile,zPageName))delete</a>]
   240    238       }
   241    239       @ </li>
   242    240     }
   243    241     if( cnt ){
   244    242       @ </ul>
   245    243     }
   246    244     db_finalize(&q);
................................................................................
   542    540   
   543    541   /*
   544    542   ** Function called to output extra text at the end of each line in
   545    543   ** a wiki history listing.
   546    544   */
   547    545   static void wiki_history_extra(int rid){
   548    546     if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){
   549         -    @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&amp;a=%d(rid)">[diff]</a>
          547  +    @ %z(href("%R/wdiff?name=%t&a=%d",zWikiPageName,rid))[diff]</a>
   550    548     }
   551    549   }
   552    550   
   553    551   /*
   554    552   ** WEBPAGE: whistory
   555    553   ** URL: /whistory?name=PAGENAME
   556    554   **
................................................................................
   558    556   */
   559    557   void whistory_page(void){
   560    558     Stmt q;
   561    559     char *zTitle;
   562    560     char *zSQL;
   563    561     const char *zPageName;
   564    562     login_check_credentials();
   565         -  if( !g.perm.History ){ login_needed(); return; }
          563  +  if( !g.perm.Hyperlink ){ login_needed(); return; }
   566    564     zPageName = PD("name","");
   567    565     zTitle = mprintf("History Of %s", zPageName);
   568    566     style_header(zTitle);
   569    567     free(zTitle);
   570    568   
   571    569     zSQL = mprintf("%s AND event.objid IN "
   572    570                    "  (SELECT rid FROM tagxref WHERE tagid="
................................................................................
   595    593     const char *zPageName;
   596    594     Manifest *pW1, *pW2 = 0;
   597    595     Blob w1, w2, d;
   598    596     int diffFlags;
   599    597   
   600    598     login_check_credentials();
   601    599     rid1 = atoi(PD("a","0"));
   602         -  if( !g.perm.History ){ login_needed(); return; }
          600  +  if( !g.perm.Hyperlink ){ login_needed(); return; }
   603    601     if( rid1==0 ) fossil_redirect_home();
   604    602     rid2 = atoi(PD("b","0"));
   605    603     zPageName = PD("name","");
   606    604     zTitle = mprintf("Changes To %s", zPageName);
   607    605     style_header(zTitle);
   608    606     free(zTitle);
   609    607   
................................................................................
   672    670     }
   673    671     @ <ul>
   674    672     wiki_prepare_page_list(&q);
   675    673     while( db_step(&q)==SQLITE_ROW ){
   676    674       const char *zName = db_column_text(&q, 0);
   677    675       int size = db_column_int(&q, 1);
   678    676       if( size>0 ){
   679         -      @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li>
          677  +      @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>
   680    678       }else if( showAll ){
   681         -      @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li>
          679  +      @ <li>%z(href("%R/wiki?name=%T",zName))<s>%h(zName)</s></a></li>
   682    680       }
   683    681     }
   684    682     db_finalize(&q);
   685    683     @ </ul>
   686    684     style_footer();
   687    685   }
   688    686   
................................................................................
   702    700     @ <ul>
   703    701     db_prepare(&q, 
   704    702       "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
   705    703       " ORDER BY lower(tagname) /*sort*/" ,
   706    704   	zTitle);
   707    705     while( db_step(&q)==SQLITE_ROW ){
   708    706       const char *zName = db_column_text(&q, 0);
   709         -    @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li>
          707  +    @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>
   710    708     }
   711    709     db_finalize(&q);
   712    710     @ </ul>
   713    711     style_footer();
   714    712   }
   715    713   
   716    714   /*

Changes to src/wikiformat.c.

  1041   1041      || strncmp(zTarget, "https:", 6)==0
  1042   1042      || strncmp(zTarget, "ftp:", 4)==0
  1043   1043      || strncmp(zTarget, "mailto:", 7)==0
  1044   1044     ){
  1045   1045       blob_appendf(p->pOut, "<a href=\"%s\">", zTarget);
  1046   1046       /* zTerm = "&#x27FE;</a>"; // doesn't work on windows */
  1047   1047     }else if( zTarget[0]=='/' ){
  1048         -    if( 1 /* g.perm.History */ ){
  1049         -      blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
  1050         -    }else{
  1051         -      zTerm = "";
  1052         -    }
         1048  +    blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget);
  1053   1049     }else if( zTarget[0]=='.' || zTarget[0]=='#' ){
  1054         -    if( 1 /* g.perm.History */ ){
         1050  +    if( 1 ){
  1055   1051         blob_appendf(p->pOut, "<a href=\"%h\">", zTarget);
  1056   1052       }else{
  1057   1053         zTerm = "";
  1058   1054       }
  1059   1055     }else if( is_valid_uuid(zTarget) ){
  1060   1056       int isClosed = 0;
  1061   1057       if( is_ticket(zTarget, &isClosed) ){
  1062   1058         /* Special display processing for tickets.  Display the hyperlink
  1063   1059         ** as crossed out if the ticket is closed.
  1064   1060         */
  1065   1061         if( isClosed ){
  1066         -        if( g.perm.History ){
         1062  +        if( g.perm.Hyperlink ){
  1067   1063             blob_appendf(p->pOut,
  1068         -             "<a href=\"%s/info/%s\"><span class=\"wikiTagCancelled\">[",
  1069         -             g.zTop, zTarget
         1064  +             "%z<span class=\"wikiTagCancelled\">[",
         1065  +             href("%R/info/%s",zTarget)
  1070   1066             );
  1071   1067             zTerm = "]</span></a>";
  1072   1068           }else{
  1073   1069             blob_appendf(p->pOut,"<span class=\"wikiTagCancelled\">[");
  1074   1070             zTerm = "]</span>";
  1075   1071           }
  1076   1072         }else{
  1077         -        if( g.perm.History ){
  1078         -          blob_appendf(p->pOut,"<a href=\"%s/info/%s\">[",
  1079         -              g.zTop, zTarget
  1080         -          );
         1073  +        if( g.perm.Hyperlink ){
         1074  +          blob_appendf(p->pOut,"%z[", href("%R/info/%s", zTarget));
  1081   1075             zTerm = "]</a>";
  1082   1076           }else{
  1083   1077             blob_appendf(p->pOut, "[");
  1084   1078             zTerm = "]";
  1085   1079           }
  1086   1080         }
  1087   1081       }else if( !in_this_repo(zTarget) ){
  1088   1082         blob_appendf(p->pOut, "<span class=\"brokenlink\">[", zTarget);
  1089   1083         zTerm = "]</span>";
  1090         -    }else if( g.perm.History ){
  1091         -      blob_appendf(p->pOut, "<a href=\"%s/info/%s\">[", g.zTop, zTarget);
         1084  +    }else if( g.perm.Hyperlink ){
         1085  +      blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget));
  1092   1086         zTerm = "]</a>";
  1093   1087       }
  1094   1088     }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
  1095   1089               && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
  1096   1090       blob_appendf(p->pOut, "<a href=\"%s/timeline?c=%T\">", g.zTop, zTarget);
  1097   1091     }else if( strncmp(zTarget, "wiki:", 5)==0 
  1098   1092           && wiki_name_is_wellformed((const unsigned char*)zTarget) ){